专栏首页一枝花算不算浪漫限流的简单使用及学习

限流的简单使用及学习

前言

最近系统刚做了一次大的重构,以及下游子服务都做了升级改造。

整个系统间的调用都是采用spring cloud这一套去实现的。我所负责的为业务服务端,专门为web端和pc端提供接口调用。在服务刚上线的一段时间,出现了一次雪崩的事件,整个调用链路如下:

调用链路很简单,因为文本匹配服务 需要分词,匹配,已经从ES获取匹配后的术语语料等数据,所以会有请求挤压,一段时间类服务就崩溃了。为了紧急处理这种情况,所以需要再业务方加上限流机制(后续优化下游的匹配算法)。刚好也针对于这种情况,自己来学习下几种限流的方式。

限流算法分类

参见的限流算法有:令牌桶,漏桶,计数器。

计数器限流算法

计数器是最简单也是最粗暴的一种限流算法,同时也是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。

  1. 使用Redis的限流做法:
/**
 * 限流方法,通过redis进行方法级别的限流措施。
 */
@Service
@Transactional
@Slf4j
public class MethodThrottleService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 通过指定key值获取是否是合法请求,如果在规定缓存时间内仍然存在该key值,说明该请求不合法
     *
     * @param key        请求key值
     * @param expireTime 过期时间
     * @param timeUnit   过期时间单位
     * @return 是否过期 true || false
     */
    public Boolean validateKeyRequest(String key, int expireTime, TimeUnit timeUnit) {
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String result = ops.get(key);
        if (StringUtils.isNotBlank(result)) {
            return false;
        }

        ops.set(key, key, expireTime, timeUnit);
        return true;
    }

    /**
     * 通过指定用户和方法名判断请求是否合法请求,如果在规定缓存时间内仍然存在该key值,说明该请求不合法
     *
     * @param methodName 方法名
     * @param perCount   规定时间请求的次数
     * @param iolId      用户名
     * @return 是否过期 true || false
     */
    public Boolean validateUserRequest(String methodName, int perCount, String iolId, int expireTime, TimeUnit timeUnit) {
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String cacheKey = getCacheKey(iolId, methodName);
        Long requestCount = ops.increment(cacheKey, 1);
        log.info("requestCount = {}", requestCount);
        redisTemplate.expire(cacheKey,expireTime, timeUnit );
        if (requestCount >= perCount) {
            log.info("MethodThrottle exceed weight limit! iolId = {}, methodName = {}, requestCount = {}", iolId, methodName, requestCount);
            return false;
        }

        return true;
    }

    /**
     * 获取缓存的key值
     * @param targetName 目标名称
     * @param methodName 方法名称
     * @return 缓存key
      */
    private String getCacheKey(String targetName, String methodName) {
        StringBuilder sb = new StringBuilder("");
        sb.append("limitRate.").append(targetName).append(".").append(methodName);
        return sb.toString();
    }
}

使用redis限流,可以针对于用户+方法名进行精准限流。同时可以根据请求key值进行限流,目的是限定规定时间类同样参数的请求次数。 但是redis 限流会有很大的性能瓶颈,频繁的写入,读取,过期会对redis性能损耗比较大。不建议此种方法。 另外计数器还可以使用AtomicIntegerSemaphore,具体就不在这列出代码了,具体可以参考:Java限流策略-简书

令牌桶算法

令牌桶算法是一个存放固定容量的令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:(参考开涛:亿级流量网站架构核心技术 中第4章部分内容) 如下:

  • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;
  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝; -当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上; -如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

备注(10r/s: 一秒钟10令牌放入桶中) 对于令牌桶限流,我们可以使用Guava开源得到RateLimiter 来做,具体可以参考如下代码:

//每秒只发出10个令牌
RateLimiter rateLimiter = RateLimiter.create(10);
/**
 * 尝试获取令牌
 *
 * @return 获取令牌是否成功 true || false
 */
public boolean tryAcquire() {
    return rateLimiter.tryAcquire();
}

漏桶算法

漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;
  • 如果桶是空的,则不需流出水滴;
  • 可以以任意速率流入水滴到漏桶;
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

令牌桶和漏桶对比:

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;
  • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;
  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;
  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;
  • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java中常见数据结构Map之HashMap

    一枝花算不算浪漫
  • Java中常见数据结构Map之HashMap

    彻底搞懂HashMap,HashTableConcurrentHashMap关联:

    一枝花算不算浪漫
  • 【Java拾遗】JDK源码之集合篇

    System.arraycopy(srcElements, srcIndex, targetElement, tarIndex, srcLength); Arr...

    一枝花算不算浪漫
  • 高并发系统支撑---限流算法

    有些场景并不能用缓存和降级来解决,比如写服务、频繁的复杂查询,因此需有一种手段来限制这些场景的并发/请求量,即限流。

    一条老狗
  • Power Query中如何找到第一个中文字符并进行分割提取?

    以上是一份视频文件名称,通过PQ对文件夹内的名称进行导入后得到的。由3部分组成:集数+文件名+文件格式。我们需要分别把其拆分成3个列,我们看下如何操作。

    逍遥之
  • Eclipse安卓使用Activity模板快速新建Activity

    使用android studio的安卓开发者可能发现它和eclipse+ADT大同小异,个人用完android studio之后发现android studio...

    极客人
  • 在没有源代码的情况下对Linux二进制代码进行模糊测试

    AFL配合DynamoRIO,没错,就是drAFL。在drAFL的帮助下,我们就可以在没有源代码的情况下对LInux二进制代码进行模糊测试了。

    FB客服
  • Java开发环境系列:消息队列RabbitMQ的安装与使用(windows)

    http://www.erlang.org/download/otp_win64_17.3.exe

    架构师小跟班
  • Centos7部署RabbitMQ 3.7.6

    rabbitmq是采用Erlang(一种通用的面向并发的编程语言)编写的符合AMQP(Advanced Message Queuing Protocol)规范的...

    阿dai学长
  • 学界 | 伯克利 DeepMimic:虚拟特技演员的基本修养

    「运动控制问题已经成为强化学习的基准,而深度强化学习的方法可以很高效的处理控制和运动等问题。然而,使用深度强化学习训练的目标对象也经常会出现不自然动作、异常抖动...

    AI科技评论

扫码关注云+社区

领取腾讯云代金券