文档章节

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

冷冷gg
 冷冷gg
发布于 2017/08/30 09:45
字数 863
阅读 7070
收藏 322
点赞 9
评论 9

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

前几天在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个线程: image
当限制请求信号量为5,并发10个线程:
image

资料

基于reids + lua的实现

张开涛-聊聊高并发系统之限流特技-1

总结

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

© 著作权归作者所有

共有 人打赏支持
冷冷gg
粉丝 248
博文 106
码字总数 46179
作品 1
潍坊
UI设计师
加载中

评论(9)

冷冷gg
冷冷gg

引用来自“DRSoul”的评论

考虑一个问题,当环境中各个节点的时间差比较大的时候,每次交集之前,删除时间戳的zset中过期元素将会导致交集操作时丢失一些正持有信号量的节点信息,这个和超时时间,各节点系统时钟,获取信号量后进行业务处理的所需时间挂钩,特别当各个节点时钟相差过大,导致实际持有信号量节点大于信号量,超时时间过小时同样。
嗯 这种节点时间差比较大的问题 很头疼,这种在应用网关进行限流处理精确度会好一点,
DRSoul
DRSoul
考虑一个问题,当环境中各个节点的时间差比较大的时候,每次交集之前,删除时间戳的zset中过期元素将会导致交集操作时丢失一些正持有信号量的节点信息,这个和超时时间,各节点系统时钟,获取信号量后进行业务处理的所需时间挂钩,特别当各个节点时钟相差过大,导致实际持有信号量节点大于信号量,超时时间过小时同样。
每周精粹
每周精粹
RedisRateLimiter 的逻辑是什么
冷冷gg
冷冷gg

引用来自“瘦-马”的评论

直接 lua+redis 好了;
用 java 多次一举;

回复@瘦-马 : 并不是所有人都和大神你是的 会写lua脚本呀
瘦-马
瘦-马
直接 lua+redis 好了;
用 java 多次一举;
冷冷gg
冷冷gg

引用来自“douglaswei”的评论

redis连接资源不用还给连接池吗?
:smile: 上边还有 优化代码给漏了
douglaswei
douglaswei
redis连接资源不用还给连接池吗?
冷冷gg
冷冷gg

引用来自“1363435084”的评论

看到开涛老师就放心了
:unamused: 啥意思嘛
1363435084
1363435084
看到开涛老师就放心了
《亿级流量网站架构核心技术》目录一览

举报   在2011年年底的时候笔者就曾规划写一本Spring的书,但是因为是Spring入门类型的书,框架的内容更新太快,觉得还是写博客好一些,因此就把写完的书稿放到了博客(jinnianshilongnia...

jinjiang2009 ⋅ 2017/03/14 ⋅ 0

小柒2012/spring-boot-seckill

分布式秒杀系统 开发环境 JDK1.7、Maven、Mysql、Eclipse、SpringBoot1.5.10、zookeeper3.4.6、kafka_2.11、redis-2.8.4、curator-2.10.0 友情提示 由于工作原因,项目正在完善中(仅供参考)...

小柒2012 ⋅ 05/19 ⋅ 0

百度、阿里、腾讯、京东、大型互联网分布式架构必备技能

分布式架构 迎接高并发大数据的挑战,从深度到广度完善知识体系,成为下一个互联网高薪人才。 理论结合实战,透彻理解分布式架构及其解决方案。 面向人群 1、工作1-5年需要突破瓶颈; 2、传统...

Java高级架构 ⋅ 2017/12/21 ⋅ 0

Spring Cloud限流详解(内含源码)

原文:http://www.itmuch.com/spring-cloud-sum/spring-cloud-ratelimit/ 转载声明:本博客由周立创作,采用CC BY 3.0 CN许可协议。可自由转载、引用,但需署名作者且注明文章出处。如转载至...

周立_itmuch ⋅ 2017/12/01 ⋅ 0

基于Redis的限流系统的设计【转】

基于Redis的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计;在实现方面,算法使用的是令牌桶算法来,访问 Redis使用lua脚本。 1、概念 限流是对系统的出入流量进行控制,防止...

小红牛 ⋅ 2017/11/22 ⋅ 0

高并发系统之限流特技

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的...

vshcxl ⋅ 2016/11/22 ⋅ 2

亿级流量电商详情页系统的大型高并发与高可用缓存架构实战

对于高并发的场景来说,比如电商类,o2o,门户,等等互联网类的项目,缓存技术是Java项目中最常见的一种应用技术。然而,行业里很多朋友对缓存技术的了解与掌握,仅仅停留在掌握redis/memca...

登录404 ⋅ 2017/06/05 ⋅ 0

SpringBoot开发案例从0到1构建分布式秒杀系统

前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路。俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了...

小柒2012 ⋅ 05/16 ⋅ 0

基于分布式环境下限流系统的设计

前提 业务背景 就拿前些天的双十一的 “抢券活动” 来说,一般是设置整点开始抢的,你想想,淘宝的用户群体非常大,可以达到亿级别,而服务接口每秒能处理的量是有限的,那么这个时候问题就会...

t4i2b10X4c22nF6A ⋅ 2017/11/18 ⋅ 0

微服务下的网关与容错

自从微服务概念以来,众多的软件架构在践行着这一优秀的设计理念。各自的系统在这一指导思想下收获了优雅的可维护性,但一方面也给接口调用提出了新的要求。比如众多的API调用急需一个统一的...

新栋BOOK ⋅ 2017/10/31 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

使用 vue-cli 搭建项目

vue-cli 是一个官方发布 vue.js 项目脚手架,使用 vue-cli 可以快速创建 vue 项目,GitHub地址是:https://github.com/vuejs/vue-cli 一、 安装 node.js 首先需要安装node环境,可以直接到中...

初学者的优化 ⋅ 11分钟前 ⋅ 0

设计模式 之 享元模式

设计模式 之 享元模式 定义 使用共享技术来有效地支持大量细粒度对象的复用 关键点:防止类多次创建,造成内存溢出; 使用享元模式来将内部状态与外部状态进行分离,在循环创建对象的环境下,...

GMarshal ⋅ 26分钟前 ⋅ 0

SpringBoot集成Druid的最简单的小示例

参考网页 https://blog.csdn.net/king_is_everyone/article/details/53098350 建立maven工程 Pom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM......

karma123 ⋅ 今天 ⋅ 0

Java虚拟机基本结构的简单记忆

Java堆:一般是放置实例化的对象的地方,堆分新生代和老年代空间,不断未被回收的对象越老,被放入老年代空间。分配最大堆空间:-Xmx 分配初始堆空间:-Xms,分配新生代空间:-Xmn,新生代的大小一...

算法之名 ⋅ 今天 ⋅ 0

OSChina 周日乱弹 —— 这么好的姑娘都不要了啊

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @TigaPile :分享曾惜的单曲《讲真的》 《讲真的》- 曾惜 手机党少年们想听歌,请使劲儿戳(这里) @首席搬砖工程师 :怎样约女孩子出来吃饭,...

小小编辑 ⋅ 今天 ⋅ 8

Jenkins实践3 之脚本

#!/bin/sh# export PROJ_PATH=项目路径# export TOMCAT_PATH=tomcat路径killTomcat(){pid=`ps -ef | grep tomcat | grep java|awk '{print $2}'`echo "tom...

晨猫 ⋅ 今天 ⋅ 0

Spring Bean的生命周期

前言 Spring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。 首先看下生命周期图: 再谈生命周期之前有一点需要先明确: Spring 只帮我们管理单例模...

素雷 ⋅ 今天 ⋅ 0

zblog2.3版本的asp系统是否可以超越卢松松博客的流量[图]

最近访问zblog官网,发现zlbog-asp2.3版本已经进入测试阶段了,虽然正式版还没有发布,想必也不久了。那么作为aps纵横江湖十多年的今天,blog2.2版本应该已经成熟了,为什么还要发布这个2.3...

原创小博客 ⋅ 今天 ⋅ 0

聊聊spring cloud的HystrixCircuitBreakerConfiguration

序 本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration HystrixCircuitBreakerConfiguration spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar!/org/springframework/......

go4it ⋅ 今天 ⋅ 0

二分查找

二分查找,也称折半查找、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于...

人觉非常君 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部