常用的限流算法学习
常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的頻率向桶中放入令牌,例如一秒鐘10枚令牌,實(shí)際業(yè)務(wù)在每次響應(yīng)請(qǐng)求之前都從桶中獲取令牌,只有取到令牌的請(qǐng)求才會(huì)被成功響應(yīng),獲取的方式有兩種:阻塞等待令牌或者取不到立即返回失敗。
限流算法:令牌桶算法、漏斗桶算法、基于redis的滑動(dòng)窗口計(jì)數(shù)法
?
令牌桶算法
我們用的是guava的RateLimiter,用在處理請(qǐng)求時(shí)候,從桶中申請(qǐng)令牌,申請(qǐng)到了就成功響應(yīng),申請(qǐng)不到時(shí)直接返回失敗;
代碼示例:
package common.guava;import com.google.common.util.concurrent.RateLimiter; import org.junit.Test;public class RateLimitTest {@Testpublic void use1(){RateLimiter rateLimiter = RateLimiter.create(5.0);for (int i = 0 ; i < 20; i++){//嘗試獲取令牌if (rateLimiter.tryAcquire()){System.out.println("獲取令牌成功");//模擬業(yè)務(wù)執(zhí)行 // try { // Thread.sleep(500); // } catch (InterruptedException e) { // e.printStackTrace(); // }}else {System.out.println("獲取令牌失敗");}}}@Testpublic void test2(){RateLimiter rateLimiter = RateLimiter.create(5);long start = System.currentTimeMillis()/1000;for (int i = 0 ; i < 10; i++){System.out.println("----start----");//阻塞式放行rateLimiter.acquire();System.out.println("放行");System.out.println("-----end-----");}long end = System.currentTimeMillis() / 1000;System.out.println(String.format("耗時(shí):%d s", (end - start)));}}漏斗桶算法
漏桶算法思路很簡(jiǎn)單,水(請(qǐng)求)先進(jìn)入到漏桶里,漏桶以一定的速度出水,當(dāng)水流入速度過(guò)大會(huì)直接溢出,可以看出漏桶算法能強(qiáng)行限制數(shù)據(jù)的傳輸速率。
參考:https://www.cnblogs.com/xuwc/p/9123078.html
?
package flowLimit;import com.google.common.util.concurrent.RateLimiter; import org.junit.Test;import java.util.concurrent.TimeUnit;/*** @author: weijie* @Date: 2020/9/23 19:15* @url:https://blog.csdn.net/cailianren1/article/details/85283044*/ public class LeakyBucketLimitTest {/*** @param qps 平均qps 控制接口的響應(yīng)速率,響應(yīng)速率越快處理請(qǐng)求越多* @param countOfReq 桶的大小,接受請(qǐng)求的最大值* @return*/public RateLimiter createLeakyBucket(int qps, int countOfReq){return RateLimiter.create(qps,countOfReq, TimeUnit.MILLISECONDS);}@Testpublic void run(){RateLimiter leakyBucket = createLeakyBucket(100, 1000);long start = System.currentTimeMillis()/1000;int countRequest = 200;for (int i = 0; i < countRequest; i++){ // System.out.println("請(qǐng)求過(guò)來(lái)");leakyBucket.acquire(); // System.out.println("業(yè)務(wù)處理");}long spend = System.currentTimeMillis()/1000 - start;System.out.println("處理的請(qǐng)求數(shù)量:" + countRequest +"," +""+"耗時(shí):" + spend + "s " +",qps:" + leakyBucket.getRate()+",實(shí)際qps:"+Math.ceil(countRequest/(spend)));} }窗口計(jì)數(shù)法
優(yōu)點(diǎn):和令牌桶相比,這種算法不需要去等待令牌生成的時(shí)間,在新的時(shí)間窗口,可以立即處理大量的請(qǐng)求。
缺點(diǎn):在一個(gè)窗口臨界點(diǎn)的前后時(shí)間,比如時(shí)間窗口是1分鐘,在59秒和1分01秒同時(shí)突發(fā)大量請(qǐng)求,極端情況下可能會(huì)帶來(lái) 2 倍的流量,系統(tǒng)可能承受不了這么大的突發(fā)性流量
java實(shí)現(xiàn)的固定窗口計(jì)數(shù)法:
package common.flowLimit;import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /*** @author: weijie* @Date: 2020/9/23 18:02* @Description:* @url: https://blog.csdn.net/king0406/article/details/103129530?*/ public class WindowLimiter {Logger log = LoggerFactory.getLogger(WindowLimiter.class);//本地緩存,以時(shí)間戳為key,以原子類(lèi)計(jì)數(shù)器為valueprivate LoadingCache<Long, AtomicLong> counter =CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build(new CacheLoader<Long, AtomicLong>() {@Overridepublic AtomicLong load(Long seconds) throws Exception {return new AtomicLong(0);}});private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);//設(shè)置限流閾值為15private long limit = 15;/*** 固定時(shí)間窗口* 每隔5s,計(jì)算時(shí)間窗口內(nèi)的請(qǐng)求數(shù)量,判斷是否超出限流閾值*/@Testpublic void run(){while (true){fixWindow();}}private void fixWindow() {scheduledExecutorService.scheduleWithFixedDelay(() -> {try {// time windows 5 slong time = System.currentTimeMillis() / 5000;//模擬每秒發(fā)送隨機(jī)數(shù)量的請(qǐng)求int reqs = (int) (Math.random() * 5) + 1;long num = counter.get(time).addAndGet(reqs);log.info("time=" + time + ",num=" + num);if (num > limit) {log.info("限流了,num=" + num);}} catch (Exception e) {log.error("fixWindow error", e);} finally {}}, 0, 1000, TimeUnit.MILLISECONDS);} }基于redis分布式固定窗口計(jì)數(shù)法:
package flowLimit;import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool;import java.util.Random;/*** @author: weijie* @Date: 2020/9/23 18:11* @url: https://blog.csdn.net/king0406/article/details/103130327*/ public class WindowLimiterByRedisTest {Logger logger = LoggerFactory.getLogger(WindowLimiterByRedisTest.class);JedisPool jedisPool;@Beforepublic void init(){String host = "39.96.204.209";int port = 6379;jedisPool = new JedisPool(host, port);}@Testpublic void run(){/*每次請(qǐng)求進(jìn)來(lái),查詢一下當(dāng)前的計(jì)數(shù)值,如果超出請(qǐng)求數(shù)閾值,則拒絕請(qǐng)求,返回系統(tǒng)繁忙提示*/Jedis redis = jedisPool.getResource();redis.auth("123456");long limit = 10;while (true){Random random = new Random();//模擬三個(gè)不同的請(qǐng)求String request = "flow:" + random.nextInt(3);long count = 0;try {count = limitFlow(redis, request);//超過(guò)限流if (count > limit){logger.error("當(dāng)前訪問(wèn)過(guò)于頻道,請(qǐng)稍后再試");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}else {logger.info("請(qǐng)求放行,執(zhí)行業(yè)務(wù)處理");}}catch (Exception e){e.printStackTrace();}}}private long limitFlow(Jedis jedis, String key) {//Setnx(SET if Not eXists) 命令在指定的 key 不存在時(shí),為 key 設(shè)置指定的值。設(shè)置成功返回1,設(shè)置失敗返回0Long lng = jedis.setnx(key, "1");if (lng == 1) {//設(shè)置時(shí)間窗口,redis-key時(shí)效為10秒jedis.expire(key, 10);return 1L;} else {//Redis Incrby 命令將 key 中儲(chǔ)存的數(shù)字加上指定的增量值。相當(dāng)于放在redis中的計(jì)數(shù)器,每次請(qǐng)求到來(lái)計(jì)數(shù)器自增1System.out.println("key: " + key);String va = jedis.get(key);System.out.println("value: " + va);long val = jedis.incr(key);System.out.println("result: " + val);return val;}} }?
總結(jié)
- 上一篇: Win7虚拟无线AP以及Android手
- 下一篇: HtmlUnit优秀文章