详解Springboot分布式限流实践

详解 Spring Boot 分布式限流实践

简介

随着互联网的快速发展,面对海量的请求,如何保证系统的稳定性和可用性就成为了分布式系统中必须解决的问题之一。限流是一种应对高并发场景的有效手段,只有控制请求流量,才能避免系统的崩溃或服务的瘫痪。本篇文章将详细讲解如何在 Spring Boot 中实现分布式限流。

限流方式

常见的限流方式包括漏桶算法、令牌桶算法和计数器算法等。其中,相对来说比较好理解的是令牌桶算法。所谓令牌桶算法,就是系统会按照一定的速率存入一定数量的令牌到桶中,每当一个请求到达时,就从桶中拿掉一个令牌,如果没有令牌,则拒绝请求,否则就接受请求并消耗一个令牌。在实际应用中,我们可以借助 Redis 来实现分布式限流,Redis 作为分布式缓存,可以很好的解决每个节点的访问限制问题。

实现流程

下面基于 Spring Boot 2.x 版本,结合 Redis,演示如何实现分布式限流。

1. 添加 Redis 依赖

在 pom.xml 文件中添加 Redis 相关依赖,如下所示:

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. 配置 Redis

在 application.yml 文件中添加 Redis 相关配置,如下所示:

spring:
  redis:
    host: localhost
    port: 6379
    password: 
    lettuce:
      pool:
        max-active: 10

3. 自定义注解

定义一个自定义注解,用于标识需要进行访问限制的接口:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimit {

    /**
     * 允许访问的次数
     */
    int times();

    /**
     * 时间段,单位为秒
     */
    int second();

}

4. AOP 切面实现限流

基于 AOP 切面实现访问限制,代码如下所示:

@Aspect
@Component
@Slf4j
public class AccessLimitAspect {

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @Pointcut("@annotation(com.example.demo.annotation.AccessLimit)")
    public void pointCut() {}

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
        int times = accessLimit.times();
        int second = accessLimit.second();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = IpUtils.getIpAddr(request);
        String key = "access-limit:" + ip + ":" + request.getRequestURI();
        Long count = redisTemplate.opsForValue().increment(key, 1);
        if (count == 1) {
            redisTemplate.expire(key, second, TimeUnit.SECONDS);
        }
        if (count > times) {
            throw new Exception("访问次数受限!");
        }
        return joinPoint.proceed();
    }

}

5. 测试

@RestController
@RequestMapping("/demo")
@Slf4j
public class DemoController {

    @GetMapping("/accessLimit")
    @AccessLimit(times = 3, second = 10)
    public String accessLimit() {
        log.info("访问成功!");
        return "访问成功!";
    }

}

在上面的代码中,我们定义了一个 /accessLimit 接口,为该接口添加了 @AccessLimit(times = 3, second = 10) 注解,表示在 10 秒内最多允许访问 3 次。如果超过访问次数则会抛出 Exception 异常,接口无法访问。

示例

假设有两个服务,service-providerservice-consumer,都是基于 Spring Boot 实现,现在需要在 service-provider 中实现限流,限制每个客户端在 60 秒内最多允许请求 100 次 /hello 接口,代码如下所示:

service-provider 端实现

Maven 依赖

service-provider 的 pom.xml 文件中添加以下依赖:

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Redis 配置

service-provider 的 application.yml 文件中添加 Redis 配置:

spring:
  redis:
    host: localhost
    port: 6379
  cache:
    type: redis

访问限制

添加访问限制注解:

@RestController
@Slf4j
public class HelloController {

    @GetMapping("/hello")
    @AccessLimit(times = 100, second = 60)
    public String hello() {
        log.info("Hello!");
        return "Hello!";
    }

}

AOP 切面实现

添加 AOP 切面,实现访问限制:

@Aspect
@Component
@Slf4j
public class AccessLimitAspect {

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @Pointcut("@annotation(com.example.demo.annotation.AccessLimit)")
    public void pointCut() {}

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
        int times = accessLimit.times();
        int second = accessLimit.second();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = IpUtils.getIpAddr(request);
        String key = "access-limit:" + ip + ":" + request.getRequestURI();
        Long count = redisTemplate.opsForValue().increment(key, 1);
        if (count == 1) {
            redisTemplate.expire(key, second, TimeUnit.SECONDS);
        }
        if (count > times) {
            throw new Exception("访问次数受限!");
        }
        return joinPoint.proceed();
    }

}

service-consumer 端调用

service-consumer 的代码中,调用 service-provider/hello 接口,代码如下:

@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
public class HelloServiceTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void hello() {
        for (int i = 0; i < 500; i++) {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity("/hello", String.class);
            log.info("调用次数:" + (i + 1) + ", 返回结果:" + responseEntity.getBody());
        }
    }

}

在上面代码中,我们在 for 循环中,模拟了 500 次调用 /hello 接口,运行结果如下:

2022-01-01 20:30:30.030  INFO  hello-service-test:23 - 调用次数:1, 返回结果:Hello!
...
2022-01-01 20:30:33.331  INFO  hello-service-test:23 - 调用次数:100, 返回结果:Hello!
2022-01-01 20:31:10.098 ERROR  hello-service-test:26 - org.springframework.web.client.HttpClientErrorException$Forbidden: 403 null
2022-01-01 20:31:10.099 ERROR  hello-service-test:26 - org.springframework.web.client.HttpClientErrorException$Forbidden: 403 null
...

可以看到,前 100 次访问都是正常的,当访问次数超过 100 次后,就会抛出 403 错误,访问被拒绝。

总结

通过本文的讲解,我们了解到了分布式限流的实现方法和流程,可以使用类似的方式来实现接口的访问限制。明确接口的业务场景和流量特点,采取对应的限流算法和策略,可以提升服务的可用性,避免服务的瘫痪,从而保证整个系统的稳定性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Springboot分布式限流实践 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • 标记-清除算法的作用是什么?

    以下是关于标记-清除算法的详细讲解: 什么是标记-清除算法? 标记-清除算法是一种常见的垃圾回收算法,它的原理是在程序运行过程中,标记所有不再使用的内存空间,然后清除这些内存空间,从而回收内存空间。标记清除算法分为两个阶段:标记阶段和清除阶段。 标记阶段 在标记阶段,垃圾回收器会遍历所有的对象,标记所有不再使用的对象。标记的方式通常是在对象头中添加一个标记位…

    Java 2023年5月12日
    00
  • 详解Java中ByteArray字节数组的输入输出流的用法

    详解Java中ByteArray字节数组的输入输出流的用法 什么是ByteArray字节数组? 在Java中,字节数组是指由若干个字节所组成的数组。字节一般是指8位二进制数,也就是一个范围在0-255的整数,因此Java中一个字节数组就是由一系列整数所组成的数组。 什么是Java中的输入输出流? Java中的输入输出流是用来实现数据的流动,将数据从输入端流入…

    Java 2023年5月26日
    00
  • SpringBoot参数校验之@Validated的使用详解

    下面就为大家详细讲解“SpringBoot参数校验之@Validated的使用详解”。 什么是@Validated 在Spring框架中,我们经常需要对方法入参的校验,以保证参数的正确性。 SpringBoot基于Hibernate Validator,为开发者提供了方便简洁的实现方式:@Validated。 @Validated 用于校验方法入参,可以将该…

    Java 2023年5月20日
    00
  • 详解在springboot中使用Mybatis Generator的两种方式

    下面我将详细讲解“详解在springboot中使用Mybatis Generator的两种方式”的完整攻略。 一、前置条件 在使用Mybatis Generator之前,我们需要先满足以下几个前置条件: 安装Maven和JDK,在此不再赘述; 在项目中引入依赖mybatis-generator-core和mysql-connector-java,可以在pom…

    Java 2023年5月20日
    00
  • Java中ArrayBlockingQueue和LinkedBlockingQueue

    简介: Java中的BlockingQueue是java.util.concurrent包中的一个接口,是JDK中的并发工具,提供了线程安全的队列,可以用来协调生产者与消费者线程的生产和消费的速度,并且解决了高并发下数据读写的安全问题。BlockingQueue具有阻塞的复杂行为,可以实现生产、消费线程集合的同步。 Java中有两个BlockingQueue…

    Java 2023年5月26日
    00
  • 详解SpringMVC中拦截器的概念及入门案例

    以下是关于“详解SpringMVC中拦截器的概念及入门案例”的完整攻略,其中包含两个示例。 SpringMVC中拦截器的概念 拦截器是SpringMVC中的一个重要组件,它可以在请求到达控制器之前或之后对请求进行拦截和处理。拦截器可以用于实现一些通用的功能,如权限验证、日志记录、性能监控等。 在SpringMVC中,拦截器是通过实现HandlerInterc…

    Java 2023年5月16日
    00
  • Java实现Excel导入导出的步骤详解

    Java实现Excel导入导出的步骤详解 Excel导入导出在日常开发中非常常见,Java语言作为一种非常流行的开发语言,在Excel导入导出方面也提供了很好的支持,本文将为大家详细介绍Java实现Excel导入导出的步骤。 相关技术介绍 在Java语言中,常用的Excel导入导出技术有以下几种: POI技术:免费的Java API,可以新建表格,也可以读写…

    Java 2023年6月15日
    00
  • Java复制文件常用的三种方法

    当需要将一个文件复制到另一个地方时,Java中有许多方法可以复制文件。接下来我将讲解Java中复制文件的常用三种方法。 方法一: 使用Java IO的流来复制文件 最传统的方法是使用Java IO的流来复制文件。此方法使用基本的文件输入/输出流,将源文件作为输入流,将目标文件作为输出流进行复制。 public static boolean copyFileU…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部