ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

浅谈服务限流

2019-07-13 15:52:17  阅读:199  来源: 互联网

标签:令牌 服务 浅谈 redis 算法 限流 速率 public


服务限流

       本文讨论的限流并非是网关层面的限流,而是业务代码中的逻辑限流。

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

计数器算法

         基于AtomicInteger

public class AtomicIntegerDemo {
    private static AtomicInteger count = new AtomicInteger(0);
    public static void exec() {
        if (count.get() >= 5) {
            System.out.println("系统繁忙  thread name:"+Thread.currentThread().getName());
        } else {
            count.incrementAndGet();
            try {
                //处理核心逻辑
                TimeUnit.SECONDS.sleep(10);
                System.out.println("处理业务  thread name:"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                count.decrementAndGet();
            }
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> exec(),"thread-"+i).start();
        }
    }
}

        弊端:使用 AomicInteger 简单粗暴超过域值就拒绝请求,可能只是瞬时的请求量高,也会拒绝请求。

        基于 Semaphore

public class SemaphoreDemo {
    private static Semaphore semphore = new Semaphore(5);
    public static void exec() {
        if(semphore.getQueueLength()>100){//超过阈值100
            System.out.println("系统繁忙  thread name:"+Thread.currentThread().getName());
        }
        try {
            semphore.acquire();
            // 处理核心逻辑
            TimeUnit.SECONDS.sleep(10);
            System.out.println("处理业务  thread name:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semphore.release();
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> exec(),"thread-"+i).start();
        }
    }
}

        Semaphore信号量来控制并发执行的次数,如果超过域值信号量,则进入阻塞队列中排队等待获取信号量进行执行。如果阻塞队列中排队的请求过多超出系统处理能力,则可以在拒绝请求。

漏桶算法

        漏桶算法理解起来比较简单,可以粗略的认为就是注水漏水过程,往桶(桶指定大小)中以指定固定速率流出水,以随机任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率,不管上面流量多大,下面流出的速度始终保持不变。

 

令牌桶算法

       令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。

在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

Guava RateLimiter 提供了令牌桶算法可用于平滑突发限流策略。 limiter.acquire(1) 表示消费一个令牌。当桶中有足够的令牌时,则直接返回0,否则阻塞。

public class RateLimiterDemo {
    private static RateLimiter limiter = RateLimiter.create(5);
    public static void exec() {
        limiter.acquire(1);
        //  可以设置超时时间,超时返回
        //  boolean b = rateLimiter.tryAcquire(1, TimeUnit.SECONDS);
        try {
            // 处理核心逻辑
            TimeUnit.SECONDS.sleep(10);
            System.out.println("处理业务  thread name:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> exec(),"thread-"+i).start();
        }
    }
}

令牌桶和漏桶对比:

  1. 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;

  2. 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;

  3. 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;

  4. 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;

  5. 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;

  6. 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的

 

       前面讨论的几种算法都属于单机限流的范畴,但是业务需求五花八门,简单的单机限流,根本无法满足他们。

       比如为了限制某个资源被每个用户或者商户的访问次数,5s只能访问2次,或者一天只能调用1000次,这种需求,单机限流是无法实现的,这时就需要通过集群限流进行实现。

       如何实现?为了控制访问次数,肯定需要一个计数器,而且这个计数器只能保存在第三方服务,比如redis。

       大概思路:每次有相关操作的时候,就向redis服务器发送一个incr命令,比如需要限制某个用户访问/index接口的次数,只需要拼接用户id和接口名生成redis的key,每次该用户访问此接口时,只需要对这个key执行incr命令,在这个key带上过期时间,就可以实现指定时间的访问频率。

       Java伪代码实现逻辑

if(key 存在){
   value++;
   if(value>=limit){
      不能访问
      return false
           }
   }else{
       添加key,value为1
       设置key过期时间为expire
   }

       Redisson客户端实现

public class RedissonDemo {
    public static void main(String[] args) {
        Config config = new Config();
        config.useClusterServers().addNodeAddress("redis://ip:port");
        RedissonClient redissonClient = Redisson.create(config);
        //阻塞式
        redissonClient.getRateLimiter("rateLimiter").acquire(100);
        //设置等待时间
        boolean flag = redissonClient.getRateLimiter("rateLimiter").tryAcquire(100,1, TimeUnit.SECONDS);
    }
}

        acquire方法Lua脚本代码 (RedissonRateLimiter.java)

     private <T> RFuture<T> tryAcquireAsync(RedisCommand<T> command, Long value) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                "local rate = redis.call('hget', KEYS[1], 'rate');"
- "local interval = redis.call('hget', KEYS[1], 'interval');"
  ype = redis.call('hget', KEYS[1], 'type');"
  - "assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')"      
  + "local valueName = KEYS[2];"
          + "if type == '1' then "
              + "valueName = KEYS[3];"
          + "end;"
          
          + "local currentValue = redis.call('get', valueName); "
          + "if currentValue ~= false then "
                 + "if tonumber(currentValue) < tonumber(ARGV[1]) then "
                     + "return redis.call('pttl', valueName); "
                 + "else "
                     + "redis.call('decrby', valueName, ARGV[1]); "
                     + "return nil; "
                 + "end; "
          + "else "
                 + "assert(tonumber(rate) >= tonumber(ARGV[1]), 'Requested permits amount could not exceed defined rate'); "
                 + "redis.call('set', valueName, rate, 'px', interval); "
                 + "redis.call('decrby', valueName, ARGV[1]); "
                 + "return nil; "
          + "end;",
            Arrays.<Object>asList(getName(), getValueName(), getClientValueName()), 
            value, commandExecutor.getConnectionManager().getId().toString());
}

 

标签:令牌,服务,浅谈,redis,算法,限流,速率,public
来源: https://www.cnblogs.com/dyg0826/p/11180846.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有