在開發(fā)高并發(fā)系統(tǒng)時(shí)捂敌,有三把利器用來保護(hù)系統(tǒng):緩存例诀、降級和限流:
緩存:緩存的目的是提升系統(tǒng)訪問速度和增大系統(tǒng)處理容量
降級:降級是當(dāng)服務(wù)出現(xiàn)問題或者影響到核心流程時(shí)型宙,需要暫時(shí)屏蔽掉响牛,待高峰或者問題解決后再打開
限流:限流的目的是通過對并發(fā)訪問/請求進(jìn)行限速瞬捕,或者對一個(gè)時(shí)間窗口內(nèi)的請求進(jìn)行限速來保護(hù)系統(tǒng)歉嗓,一旦達(dá)到限制速率則可以拒絕服務(wù)丰介、排隊(duì)或等待、降級等處理
常見算法:
1鉴分、計(jì)時(shí)器限流
2哮幢、滑動(dòng)窗口
3、漏桶算法
4志珍、令牌桶算法
計(jì)時(shí)器限流
計(jì)時(shí)器是限流算法中比較簡單的一種算法橙垢,比如:限制一個(gè)接口1分鐘內(nèi)不能超過100次請求,可以在一開始設(shè)置一個(gè)計(jì)數(shù)器伦糯,每當(dāng)接收到請求時(shí)柜某,將計(jì)數(shù)器加1嗽元,如果計(jì)數(shù)器的值大于100并且當(dāng)前請求與第一個(gè)請求的執(zhí)行時(shí)間間隔還在1分鐘內(nèi)的,那就說明請求過多莺琳;如果當(dāng)前請求與第一次請求的時(shí)間間隔超過1分鐘还棱,并且請求次數(shù)沒有超限的,那么重置計(jì)數(shù)器
import org.springframework.data.redis.core.RedisTemplate;
/**
* 計(jì)數(shù)器限流
* 限制1分鐘內(nèi)請求100次
* 弊端:
* 1惭等、當(dāng)前窗口100個(gè)請求全部集中到結(jié)束點(diǎn)珍手,下個(gè)窗口100個(gè)請求全部集中到起始點(diǎn),臨界點(diǎn)就會(huì)出現(xiàn)請求暴增的情況辞做,可能瞬間壓垮應(yīng)用
* 2琳要、只適用于單機(jī)應(yīng)用,分布式環(huán)境中無法滿足
*
* @author lile
* @date 2019/7/1618:59
*/
public class CounterLimit {
//時(shí)間窗口內(nèi)的最大請求數(shù)
public final int reqCount = 100;
//時(shí)間窗口內(nèi)的當(dāng)前請求數(shù)
private int currentReqCount;
//時(shí)間窗口的長度 單位:ms
public final int interval = 60 * 1000;
//當(dāng)前時(shí)間
public Long currentTimestamp = System.currentTimeMillis();
public RedisTemplate redisTemplate;
public final String key = "minute_test";
/**
* 計(jì)數(shù)器限流
*
* @return
*/
public boolean grant() {
Long nowTimestamp = System.currentTimeMillis();
if (nowTimestamp < currentTimestamp + interval) {
//在當(dāng)前時(shí)間窗口內(nèi)
currentReqCount++;
//判斷當(dāng)前時(shí)間窗口的請求數(shù)是否小于最大的請求限制數(shù)
return currentReqCount < reqCount;
}
//不在當(dāng)前窗口內(nèi)時(shí)秤茅,重置下個(gè)窗口的首次請求時(shí)間及窗口內(nèi)請求數(shù)
currentReqCount = 1;
currentTimestamp = nowTimestamp;
return true;
}
/**
* 滑動(dòng)窗口的限流
* 保證時(shí)間窗口中的請求數(shù)不超過最大限制數(shù)即可稚补,能平穩(wěn)的控制請求的速率,請求過快會(huì)被拒絕
* 窗口時(shí)間范圍定為1分鐘框喳,每次接收到請求后重新劃分時(shí)間窗口课幕,當(dāng)前時(shí)間往前的1分鐘范圍為新的時(shí)間窗口
*
* @return
*/
public boolean DuoJiQi() {
Long nowTimestamp = System.currentTimeMillis();
if (redisTemplate.opsForZSet().size(key) > 0) {
//移除不在當(dāng)前時(shí)間窗口的數(shù)據(jù)
redisTemplate.opsForZSet().removeRangeByScore(key, 0, nowTimestamp - interval);
}
redisTemplate.opsForZSet().add(key, "當(dāng)前請求的唯一ID", nowTimestamp);
if (redisTemplate.opsForZSet().size(key) > reqCount) {
//請求超限,禁止提交請求
return false;
}
return true;
}
}