基于Redis实现分布式应用限流的方法

限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务。

前几天在DD的公众号,看了一篇关于使用 瓜娃 实现单应用限流的方案 --》原文,参考《redis in action》 实现了一个jedis版本的,都属于业务层次限制。 实际场景中常用的限流策略:

Nginx接入层限流

按照一定的规则如帐号、IP、系统调用逻辑等在Nginx层面做限流

业务应用系统限流

通过业务代码控制流量这个流量可以被称为信号量,可以理解成是一种锁,它可以限制一项资源最多能同时被多少进程访问。

代码实现

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.ZParams;
import java.util.List;
import java.util.UUID;

/**
 * @email wangiegie@gmail.com
 * @data 2017-08
 */
public class RedisRateLimiter {
  private static final String BUCKET = "BUCKET";
  private static final String BUCKET_COUNT = "BUCKET_COUNT";
  private static final String BUCKET_MONITOR = "BUCKET_MONITOR";

  static String acquireTokenFromBucket(
      Jedis jedis, int limit, long timeout) {
    String identifier = UUID.randomUUID().toString();
    long now = System.currentTimeMillis();
    Transaction transaction = jedis.multi();

    //删除信号量
    transaction.zremrangeByScore(BUCKET_MONITOR.getBytes(), "-inf".getBytes(), String.valueOf(now - timeout).getBytes());
    ZParams params = new ZParams();
    params.weightsByDouble(1.0,0.0);
    transaction.zinterstore(BUCKET, params, BUCKET, BUCKET_MONITOR);

    //计数器自增
    transaction.incr(BUCKET_COUNT);
    List<Object> results = transaction.exec();
    long counter = (Long) results.get(results.size() - 1);

    transaction = jedis.multi();
    transaction.zadd(BUCKET_MONITOR, now, identifier);
    transaction.zadd(BUCKET, counter, identifier);
    transaction.zrank(BUCKET, identifier);
    results = transaction.exec();
    //获取排名,判断请求是否取得了信号量
    long rank = (Long) results.get(results.size() - 1);
    if (rank < limit) {
      return identifier;
    } else {//没有获取到信号量,清理之前放入redis 中垃圾数据
      transaction = jedis.multi();
      transaction.zrem(BUCKET_MONITOR, identifier);
      transaction.zrem(BUCKET, identifier);
      transaction.exec();
    }
    return null;
  }
}

调用

测试接口调用

@GetMapping("/")
public void index(HttpServletResponse response) throws IOException {
  Jedis jedis = jedisPool.getResource();
  String token = RedisRateLimiter.acquireTokenFromBucket(jedis, LIMIT, TIMEOUT);
  if (token == null) {
    response.sendError(500);
  }else{
    //TODO 你的业务逻辑
  }
  jedisPool.returnResource(jedis);
}

优化

使用拦截器 + 注解优化代码

拦截器

@Configuration
static class WebMvcConfigurer extends WebMvcConfigurerAdapter {
  private Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class);
  @Autowired
  private JedisPool jedisPool;

  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new HandlerInterceptorAdapter() {
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                   Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
        if (rateLimiter != null){
          int limit = rateLimiter.limit();
          int timeout = rateLimiter.timeout();
          Jedis jedis = jedisPool.getResource();
          String token = RedisRateLimiter.acquireTokenFromBucket(jedis, limit, timeout);
          if (token == null) {
            response.sendError(500);
            return false;
          }
          logger.debug("token -> {}",token);
          jedis.close();
        }
        return true;
      }
    }).addPathPatterns("/*");
  }
}

定义注解

/**
 * @email wangiegie@gmail.com
 * @data 2017-08
 * 限流注解
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {
  int limit() default 5;
  int timeout() default 1000;
}

使用

@RateLimiter(limit = 2, timeout = 5000)
@GetMapping("/test")
public void test() {
}

并发测试

工具:apache-jmeter-3.2

说明: 没有获取到信号量的接口返回500,status是红色,获取到信号量的接口返回200,status是绿色。

当限制请求信号量为2,并发5个线程:

当限制请求信号量为5,并发10个线程:

资料

基于reids + lua的实现

总结

  1. 对于信号量的操作,使用事务操作。
  2. 不要使用时间戳作为信号量的排序分数,因为在分布式环境中,各个节点的时间差的原因,会出现不公平信号量的现象。
  3. 可以使用把这块代码抽成@rateLimiter注解,然后再方法上使用就会很方便啦
  4. 不同接口的流控,可以参考源码的里面RedisRateLimiterPlus,无非是每个接口生成一个监控参数
  5. 源码:boding1-pig-cloud-jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Javaweb应用使用限流处理大量的并发请求详解

    在web应用中,同一时间有大量的客户端请求同时发送到服务器,例如抢购.秒杀等.这个时候如何避免将大量的请求同时发送到业务系统. 第一种方法:在容器中配置最大请求数,如果大于改请求数,则客户端阻塞.该方法有效的阻止了大量的请求同时访问业务系统,但对用户不友好. 第二种方法:使用过滤器,保证一定数量的请求能够正常访问系统,多余的请求先跳转到排队页面,由排队页面定时发起请求.过滤器实现如下: public class ServiceFilter implements Filter { private

  • 基于Redis实现分布式应用限流的方法

    限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务. 前几天在DD的公众号,看了一篇关于使用 瓜娃 实现单应用限流的方案 -->原文,参考<redis in action> 实现了一个jedis版本的,都属于业务层次限制. 实际场景中常用的限流策略: Nginx接入层限流 按照一定的规则如帐号.IP.系统调用逻辑等在Nginx层面做限流 业务应用系统限流 通过业务代码控制流量这个流量可以被称为信号量,可以理解成是一种锁,它

  • 基于redis+lua进行限流的方法

    1,首先我们redis有很多限流的算法(比如:令牌桶,计数器,时间窗口)等,但是都有一定的缺点,令牌桶在单项目中相对来说比较稳定,但是在分布式集群里面缺显的不那么友好,这时候,在分布式里面进行限流的话,我们则可以使用redis+lua脚本进行限流,能抗住亿级并发 2,下面说说lua+redis进行限流的做法开发环境:idea+redis+lua第一:打开idea的插件市场,然后搜索lua,点击右边的安装,然后安装好了,重启即可 第二:写一个自定义限流注解 package com.sport.sp

  • 基于 Redis 实现接口限流的方式

    目录 基于 Redis 实现接口限流 1. 准备工作 2. 限流注解 3. 定制 RedisTemplate 4. 开发 Lua 脚本 5. 注解解析 6. 接口测试 7. 全局异常处理 总结 基于 Redis 实现接口限流 Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了- 今天想和小伙伴们聊聊用 Redis 处理接口限流,这也是最近的 TienChin 项目涉及到这个知识点了,我就拎出来和大家聊聊这个话题,后面视频也会讲. 1. 准备工作 首先我

  • 利用Redis实现访问次数限流的方法详解

    假设我们要做一个业务需求,这个需求就是限制用户的访问频次.比如1分钟内只能访问20次,10分钟内只能访问200次.因为是用户维度的场景,性能肯定是要首先考虑,那么适合这个场景的非Redis莫属. 最简单的实现,莫过于只是用incr进行计数操作,于是有了下面的代码: long count = redisTemplate.opsForValue().increment("user:1:60"); if (count > maxLimitCount) { throw new Limit

  • SpingBoot中使用Redis对接口进行限流的实现

    目录 实现的思路 使用 Hash 存储接口的限流配置 使用普通kv,存储api的请求次数 使用SpringBoot实现 RedisKeys ObjectRedisTemplate RedisConfigration RequestLimitConfig RequestLimitInterceptor Controller WebMvcConfigration 最后一些问题 怎么灵活的配置 针对指定的用户限流 Restful 接口的问题 一个基于Redis实现的接口限流方案,先说要实现的功能 可以

  • SpringBoot中使用Redis对接口进行限流的实现

    目录 实现的思路 使用 Hash 存储接口的限流配置 使用普通kv,存储api的请求次数 使用SpringBoot实现 RedisKeys ObjectRedisTemplate RedisConfigration RequestLimitConfig RequestLimitInterceptor Controller WebMvcConfigration 最后一些问题 怎么灵活的配置 针对指定的用户限流 Restful 接口的问题 一个基于Redis实现的接口限流方案,先说要实现的功能 可以

  • springboot+redis 实现分布式限流令牌桶的示例代码

    1.前言 网上找了很多redis分布式限流方案,要不就是太大,需要引入第三方jar,而且还无法正常运行,要不就是定时任务定时往key中放入数据,使用的时候调用,严重影响性能,所以着手自定义实现redis令牌桶. 只用到了spring-boot-starter-data-redis包,并且就几行代码. 2.环境准备 a.idea新建springboot项目,引入spring-data-redis包 b.编写令牌桶实现方法RedisLimitExcutor c.测试功能,创建全局拦截器,测试功能 3

  • 使用SpringBoot + Redis 实现接口限流的方式

    目录 配置 限流注解 定制 RedisTemplate Lua 脚本 注解解析 接口测试 全局异常处理 Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了 配置 首先我们创建一个 Spring Boot 工程,引入 Web 和 Redis 依赖,同时考虑到接口限流一般是通过注解来标记,而注解是通过 AOP 来解析的,所以我们还需要加上 AOP 的依赖,最终的依赖如下: <dependency> <groupId>org.springfra

  • SpringBoot+Redis+Lua分布式限流的实现

    Redis支持LUA脚本的主要优势 LUA脚本的融合将使Redis数据库产生更多的使用场景,迸发更多新的优势: 高效性:减少网络开销及时延,多次redis服务器网络请求的操作,使用LUA脚本可以用一个请求完成 数据可靠性:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入. 复用性:LUA脚本执行后会永久存储在Redis服务器端,其他客户端可以直接复用 可嵌入性:可嵌入JAVA,C#等多种编程语言,支持不同操作系统跨平台交互 简单强大:小巧轻便,资源占用率低,支持过程化和对象化的编程

  • Nginx使用limit_req_zone对同一IP访问进行限流的方法

    nginx可以使用ngx_http_limit_req_module模块的limit_req_zone指令进行限流访问,防止用户恶意攻击刷爆服务器.ngx_http_limit_req_module模块是nginx默认安装的,所以直接配置即可. 首先,在nginx.conf文件中的http模块下配置 limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; 说明:区域名称为one(自定义),占用空间大小为10m,平均处理的请求频率不能超

随机推荐