详解Springboot分布式限流实践

yizhihongxing

详解 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中String字符串常量池和intern方法源码分析

    Java中String字符串常量池和intern方法源码分析 什么是字符串常量池 在Java中,字符串是不可变的,也就是说,对一个字符串的任何操作都将返回一个新的字符串对象,而原来的字符串对象不会被修改。 为了提高String对象的创建和删除效率,Java引入了字符串常量池(String Pool),该池用来缓存字符串对象,可以减少新的String对象的创建…

    Java 2023年5月27日
    00
  • 微信小程序实现横屏手写签名

    微信小程序可以通过使用第三方库实现横屏手写签名功能。以下是一些示例来演示如何实现这一功能。 预备知识 在实现横屏手写签名功能前,必须具备以下的预备知识: 了解Canvas绘图的基本概念。 了解怎样创建并运用自定义小程序组件。 了解JavaScript语言,并熟悉使用第三方JavaScript库。 实现步骤 创建一个新的小程序页面,例如名为“Signature…

    Java 2023年5月23日
    00
  • Spring入门到精通之注解开发详解

    《Spring入门到精通之注解开发详解》是一篇介绍Spring框架注解开发的文章。本文将对这篇文章进行详细讲解。 1. 引言 在Spring框架的开发中,注解已经成为了不可避免的话题。使用注解可以帮助开发者简化配置文件,提高代码的可读性和可维护性。 本篇文章将从基础知识讲起,逐渐深入,最终达到精通的程度。 2. 注解基础知识 2.1. 常见的注解 在Spri…

    Java 2023年5月19日
    00
  • IDEA中Maven依赖包下载不了的问题解决方案汇总

    针对“IDEA中Maven依赖包下载不了的问题解决方案汇总”,下面是详细的解决方案攻略: 1.检查Maven配置 在IDEA中,我们首先需要检查Maven的配置是否正确。具体步骤如下: 打开IDEA,点击菜单栏的File->Settings->Build, Execution, Deployment->Build Tools->Mav…

    Java 2023年6月2日
    00
  • 详解spring mvc(注解)上传文件的简单例子

    Spring MVC是一种常用的Web框架,它提供了一种方便的方式来处理HTTP请求和响应。在Spring MVC中,我们可以使用注解来处理文件上传。本文将详细讲解“详解Spring MVC(注解)上传文件的简单例子”的完整攻略,并提供两个示例说明。 步骤一:添加依赖 我们需要在pom.xml文件中添加以下依赖: <dependency> &lt…

    Java 2023年5月18日
    00
  • Linux环境下的Java(JDBC)连接openGauss数据库实践记录

    Linux环境下的Java(JDBC)连接openGauss数据库实践记录 在Linux环境下,我们可以使用Java程序连接openGauss数据库进行数据操作。下面给出连接openGauss数据库的完整攻略。 步骤一:获取openGauss数据库连接驱动 我们需要下载openGauss数据库的JDBC驱动 jar 包,可以从openGauss官网https…

    Java 2023年5月20日
    00
  • 详解Spring Data Jpa 模糊查询的正确用法

    详解Spring Data JPA 模糊查询的正确用法 Spring Data JPA是基于JPA规范的一个简化操作数据库的框架,在使用Spring Data JPA进行数据库操作时,经常会使用模糊查询,下面是模糊查询的正确用法及示例。 特定字段模糊查询 特定字段模糊查询是针对某一个特定的字段进行模糊查询,示例代码如下: @Repository public…

    Java 2023年5月20日
    00
  • Javaweb实战之实现蛋糕订购系统

    Javaweb实战之实现蛋糕订购系统攻略 1. 第一步:环境搭建 在开始实现蛋糕订购系统前,需要搭建好开发环境。首先需要安装JDK和Tomcat,并且配置好环境变量。 其中JDK是Java开发包,Tomcat是一个开放源代码的Web应用服务器,主要用于处理Java Servlet和JavaServer Pages。 2. 第二步:数据库设计 在开始编写代码前…

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