新教E区

 楼主| 发表于 2019-1-4 11:43:35 | 显示全部楼层 |阅读模式
Eureka的限流算法类RateLimiter是基于令牌桶算法来实现的,下面看一看令牌桶算法的原理:
原理.jpg

需要JAVA Spring Cloud大型企业分布式微服务云构建的B2B2C电子商务平台源码 一零三八七七四六二六

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。如图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

源码解读:

  1. package com.lovnx.web;



  2. import java.util.concurrent.TimeUnit;

  3. import java.util.concurrent.atomic.AtomicInteger;

  4. import java.util.concurrent.atomic.AtomicLong;



  5. /**

  6. * @Class RateLimiter 实现基于令牌桶算法,有两个参数:

  7. *

  8. * @para  burstSize - 允许作为突发事件进入系统的最大请求数

  9. * @para  averageRate - 预期的每秒请求数(新版本也支持使用分钟为单位)

  10. *

  11. * @author lovnx

  12. */

  13. public class RateLimiter {

  14.     //限流时间单位

  15.     private final long rateToMsConversion;

  16.     //当前可供消费的令牌数量

  17.     private final AtomicInteger consumedTokens = new AtomicInteger();

  18.     //上一次填充令牌的时间戳

  19.     private final AtomicLong lastRefillTime = new AtomicLong(0);



  20.     //限流时间单位可设置为TimeUnit.SECONDS,已废弃

  21.     @Deprecated

  22.     public RateLimiter() {

  23.         this(TimeUnit.SECONDS);

  24.     }



  25.     //限流时间单位可设置为TimeUnit.SECONDS或TimeUnit.MINUTES

  26.     public RateLimiter(TimeUnit averageRateUnit) {

  27.         switch (averageRateUnit) {

  28.             case SECONDS:

  29.                 rateToMsConversion = 1000;

  30.                 break;

  31.             case MINUTES:

  32.                 rateToMsConversion = 60 * 1000;

  33.                 break;

  34.             default:

  35.                 throw new IllegalArgumentException("TimeUnit of " + averageRateUnit + " is not supported");

  36.         }

  37.     }



  38.     //这个方法默认传的当前系统时间戳

  39.     public boolean acquire(int burstSize, long averageRate) {

  40.         return acquire(burstSize, averageRate, System.currentTimeMillis());

  41.     }



  42.     public boolean acquire(int burstSize, long averageRate, long currentTimeMillis) {

  43.         //这里为了避免傻白甜将burstSize和averageRate设为负值而抛出异常

  44.         if (burstSize <= 0 || averageRate <= 0) {

  45.             return true;

  46.         }

  47.         //填充令牌

  48.         refillToken(burstSize, averageRate, currentTimeMillis);

  49.         //消费令牌成功与否

  50.         return consumeToken(burstSize);

  51.     }



  52.     private void refillToken(int burstSize, long averageRate, long currentTimeMillis) {

  53.         //得到上一次填充令牌的时间戳

  54.         long refillTime = lastRefillTime.get();

  55.         //时间间隔timeDelta = 传进来的时间戳currentTimeMillis - 上一次填充令牌的时间戳refillTime

  56.         long timeDelta = currentTimeMillis - refillTime;

  57.         //计算出新的令牌数量newTokens = 时间间隔 * 平均速率 / 限流时间单位

  58.         long newTokens = timeDelta * averageRate / rateToMsConversion;

  59.         //如果新的令牌数量大于0个

  60.         if (newTokens > 0) {

  61.             

  62.             //就等于上一次填充令牌的时间戳 + 新的令牌数量 * 限流时间单位 / 平均速率

  63.             long newRefillTime = refillTime == 0

  64.                     ? currentTimeMillis

  65.                     : refillTime + newTokens * rateToMsConversion / averageRate;

  66.             //如果lastRefillTime内存偏移量值==上一次填充令牌的时间戳refillTime,则将lastRefillTime内存值设置为新的填充令牌时间戳newRefillTime

  67.             //成功时进入条件体放令牌

  68.             if (lastRefillTime.compareAndSet(refillTime, newRefillTime)) {

  69.                 //放令牌(核心代码)

  70.                 while (true) {

  71.                     //得到当前已消费的令牌数量currentLevel

  72.                     int currentLevel = consumedTokens.get();

  73.                     //获取校正令牌数量adjustedLevel,从当前已消费的令牌数量currentLevel和允许最大请求数burstSize间取小者,以防允许最大请求数burstSize变小

  74.                     //这一步和下一步叫做“流量削峰”

  75.                     int adjustedLevel = Math.min(currentLevel, burstSize);

  76.                     //获取新的令牌数量newLevel,0 与 (校正值 - 计算值)之间取大者

  77.                     int newLevel = (int) Math.max(0, adjustedLevel - newTokens);

  78.                     //如果当前已消费的令牌内存偏移量等于consumedTokens等于currentLevel,则将已消费的令牌量consumedTokens设置为新的令牌数量newLevel

  79.                     //终止放令牌,在已消费偏移量不等于currentLevel时循环计算,直到它们相等

  80.                     if (consumedTokens.compareAndSet(currentLevel, newLevel)) {

  81.                         return;

  82.                     }

  83.                 }

  84.             }

  85.         }

  86.     }



  87.     //消费令牌,传入突发量

  88.     private boolean consumeToken(int burstSize) {

  89.         //取令牌

  90.         while (true) {

  91.             //得到当前已消费的令牌数量currentLevel

  92.             int currentLevel = consumedTokens.get();

  93.             //如果已消费令牌量大于等于突发量,则不能消费令牌

  94.             if (currentLevel >= burstSize) {

  95.                 return false;

  96.             }

  97.             //消费令牌,已消费令牌量+1

  98.             if (consumedTokens.compareAndSet(currentLevel, currentLevel + 1)) {

  99.                 return true;

  100.             }

  101.         }

  102.     }



  103.     //重置令牌桶

  104.     public void reset() {

  105.         consumedTokens.set(0);

  106.         lastRefillTime.set(0);

  107.     }

  108. }
复制代码

java B2B2C springmvc mybatis电子商务平台源码

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|小黑屋|手机版|Archiver|新教e区 ( 鲁ICP备16007390号-1 )

GMT+8, 2019-2-19 09:29 , Processed in 0.062500 second(s), 18 queries , Gzip On.

新教E区

© 2006-2014 新教E区

快速回复 返回顶部 返回列表