基于 Redis 实现分布式应用限流
来源:互联网 发布:淘宝买什么实木家具 编辑:程序博客网 时间:2024/06/04 19:24
原文链接:http://xiaoqiangge.com/aritcle/1513004492550.html
限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务。
前几天在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);}
优化
使用拦截器 + 注解优化代码
@Configurationstatic 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)@Documentedpublic @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个线程:
点评
这种方式可以实现限流功能,但是有一个很严重的问题,窗口中数据过期时间不均匀。所谓时间均匀就是确保每条数据都能遵守过期时间合约,但是上面这个代码不能完全遵守过期时间合约,如下测试,
@Test public void test01(){ JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(20); config.setMaxTotal(40); config.setMinIdle(10); JedisPool jedisPool = new JedisPool(config, "127.0.0.1", 32768, 1000); while(true){ String value = RedisRateLimiter.acquireTokenFromBucket(jedisPool.getResource(),5,10000); log.info(">> {}",value); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
代码中设置的限流条数是5条,限流时是10妙,意思就是说10秒内,最多流量为5条,也就是没一条都得遵守10秒过期的合约。输出的日志如下,
22:34:32.667 [main] INFO com.eju.ess.MyTest - >> b7a6da80-6e8c-489a-8892-d841974c36d622:34:33.680 [main] INFO com.eju.ess.MyTest - >> e85155f4-07a5-4be4-8db5-60043b9ae6af22:34:34.682 [main] INFO com.eju.ess.MyTest - >> 5cf9aa87-9e90-4a3f-8ffa-09217159e2d122:34:35.683 [main] INFO com.eju.ess.MyTest - >> ce9c7106-f291-4e0a-8a19-bd20c492aa2c22:34:36.685 [main] INFO com.eju.ess.MyTest - >> 325add3a-ba66-42a7-a741-320f48e9052522:34:37.687 [main] INFO com.eju.ess.MyTest - >> null22:34:38.689 [main] INFO com.eju.ess.MyTest - >> null22:34:39.690 [main] INFO com.eju.ess.MyTest - >> null22:34:40.691 [main] INFO com.eju.ess.MyTest - >> null22:34:41.693 [main] INFO com.eju.ess.MyTest - >> null22:34:42.694 [main] INFO com.eju.ess.MyTest - >> b456493d-81d5-4a4d-82c9-bb08300cc9c122:34:43.695 [main] INFO com.eju.ess.MyTest - >> f2078bd6-dfcb-4ab1-bc69-fdfbfb17437b22:34:44.696 [main] INFO com.eju.ess.MyTest - >> 88c305fe-a888-4979-a3ca-a90c5129090522:34:45.698 [main] INFO com.eju.ess.MyTest - >> d7d59be2-01a8-4023-9e13-14bb855b761a22:34:46.699 [main] INFO com.eju.ess.MyTest - >> 13010e9a-6cbb-44dc-8c20-7e220104b9b922:34:47.700 [main] INFO com.eju.ess.MyTest - >> null22:34:48.702 [main] INFO com.eju.ess.MyTest - >> null22:34:49.703 [main] INFO com.eju.ess.MyTest - >> null22:34:50.704 [main] INFO com.eju.ess.MyTest - >> null22:34:51.706 [main] INFO com.eju.ess.MyTest - >> null22:34:52.707 [main] INFO com.eju.ess.MyTest - >> 0b2c9cee-e8f6-4cab-b571-6628ebce10a522:34:53.709 [main] INFO com.eju.ess.MyTest - >> f93da7e3-f48f-4321-b573-d3f58677572822:34:54.710 [main] INFO com.eju.ess.MyTest - >> 0e5f9b4a-a8b0-482e-89fb-5efb7b6fd45822:34:55.711 [main] INFO com.eju.ess.MyTest - >> be271748-44bd-4850-9f2a-06b18986f53622:34:56.713 [main] INFO com.eju.ess.MyTest - >> a645c321-35a2-4738-93f4-dbbf5224f00022:34:57.714 [main] INFO com.eju.ess.MyTest - >> null22:34:58.715 [main] INFO com.eju.ess.MyTest - >> null22:34:59.717 [main] INFO com.eju.ess.MyTest - >> null22:35:00.718 [main] INFO com.eju.ess.MyTest - >> null
从上面日志看到,只有b7a6da80-6e8c-489a-8892-d841974c36d6``b456493d-81d5-4a4d-82c9-bb08300cc9c1``0b2c9cee-e8f6-4cab-b571-6628ebce10a5
这三条记录遵守了过期合约,其余的没有遵守,也就是5条之中有一条到期了,整个都会到期。
总结
上面的代码可以对分布式限流实现了部分,但是不够完美。
阅读全文
0 0
- 基于 Redis 实现分布式应用限流
- 基于 Redis 实现分布式应用限流
- 基于redis分布式缓存实现
- 基于redis分布式缓存实现
- 基于redis分布式缓存实现
- 基于redis分布式缓存实现
- 基于Redis实现分布式锁
- 基于Redis实现分布式Session
- 基于Redis实现分布式锁
- 分布式锁实现(基于redis)
- 基于Redis实现分布式锁
- 基于Redis实现分布式锁
- 基于Redis实现分布式锁
- 基于Redis实现分布式锁
- 基于Redis实现分布式锁
- 基于Redis实现分布式锁
- 基于redis分布式缓存实现
- 基于redis分布式缓存实现
- 【每周一本书】之《人之彼岸》:郝景芳带你提前踏入人机交互的未知世界
- NLP汉语自然语言处理原理与实践 1 中文语言的机器处理
- EJB之BMT事务和CMT事务
- java mail.jar和Activation.jar下载地址
- offboard模式的开发及应用
- 基于 Redis 实现分布式应用限流
- 狗屎一样的React(第四节,首页banner图轮播)
- BAT大牛面试之谈解密Android
- 【讨论】问师弟的问题集锦
- float浮动以及清除浮动
- TCP和UDP的最完整的区别
- Hbase表备份——Snapshot
- 隐藏任务栏图标
- Win10+vs2013+opencv建立简单的人脸检测工程