今年雙 11 全民購物狂歡節進入第十一個年頭,1 分 36 秒,交易額衝到 100 億 !比 2018 年快了近 30 秒,比 2017 年快了近 1 分半!這個速度再次刷新天貓雙 11 成交總額破 100 億的紀錄。web
服務等級協議
咱們常說的 N 個 9,就是對 SLA 的一個描述。SLA 全稱是 Service Level Agreement,翻譯爲服務水平協議,也稱服務等級協議,它代表了公有云提供服務的等級以及質量。算法
例如阿里雲對外承諾的就是一個服務週期內集羣服務可用性不低於 99.99%,若是低於這個標準,雲服務公司就須要賠償客戶的損失。數據庫
作到 4 個 9 夠好了嗎緩存
對互聯網公司來講,SLA 就是網站或者 API 服務可用性的一個保證。服務器
9 越多表明整年服務可用時間越長服務更可靠,4 個 9 的服務可用性,聽起來已經很高了,但對於實際的業務場景,這個值可能並不夠。微信
咱們來作一個簡單的計算,假設一個核心鏈路依賴 20 個服務,強依賴同時沒有配置任何降級,而且這 20 個服務的可用性達到 4 個 9,也就是 99.99%。架構
微服務的雪崩效應併發
限流降級怎麼作
緩存以及隊列等手段,增長系統的容量。限流和降級則是關心在到達系統瓶頸時系統的響應,更看重穩定性。app
限流和降級負載均衡
超時降級
失敗次數降級
故障降級
熔斷隔離
若是不對服務資源作隔離,一旦一個服務出現了問題,整個系統的穩定性都會受到影響!服務隔離的目的就是避免服務之間相互影響。
何處隔離:一次服務調用,涉及到的是服務提供方和調用方,咱們所指的資源,也是兩方的服務器等資源,服務隔離一般能夠從提供方和調用方兩個方面入手。
隔離什麼:廣義的服務隔離,不只包括服務器資源,還包括數據庫分庫,緩存,索引等,這裏咱們只關注服務層面的隔離。
降級和熔斷的區別
熔斷,通常是中止服務:典型的就是股市的熔斷,若是大盤不受控制,直接休市,不提供服務,是保護大盤的一種方式。
降級,一般是有備用方案:從北京到濟南,下雨致使航班延誤,我能夠乘坐高鐵,若是高鐵票買不到,也能夠乘坐汽車或者開車過去。
二者的區別:降級通常是主動的,有預見性的,熔斷一般是被動的,服務 A 降級之後,通常會有服務 B 來代替,而熔斷一般是針對核心鏈路的處理。
經常使用限流算法設計
計數器法
這時候判斷,若是計數器的值小於限流值,而且與上一次請求的時間間隔還在一分鐘內,容許請求經過,不然拒絕請求,若是超出了時間間隔,要將計數器清零。
public class CounterLimiter {
//初始時間
private static long startTime = System.currentTimeMillis();
//初始計數值
private static final AtomicInteger ZERO = new AtomicInteger(0);
//時間窗口限制
private static final long interval = 10000;
//限制經過請求
private static int limit = 100;
//請求計數
private AtomicInteger requestCount = ZERO;
//獲取限流
public boolean tryAcquire() {
long now = System.currentTimeMillis();
//在時間窗口內
if (now < startTime + interval) {
//判斷是否超過最大請求
if (requestCount.get() < limit) {
requestCount.incrementAndGet();
return true;
}
return false;
} else {
//超時重置
startTime = now;
requestCount = ZERO;
return true;
}
}
}
漏桶算法
漏桶算法的示意圖以下:
這裏簡單實現一下,也可使用 Guava 的 SmoothWarmingUp 類,能夠更好的控制漏桶算法:
public class LeakyLimiter {
//桶的容量
private int capacity;
//漏水速度
private int ratePerMillSecond;
//水量
private double water;
//上次漏水時間
private long lastLeakTime;
public LeakyLimiter(int capacity, int ratePerMillSecond) {
this.capacity = capacity;
this.ratePerMillSecond = ratePerMillSecond;
this.water = 0;
}
//獲取限流
public boolean tryAcquire() {
//執行漏水,更新剩餘水量
refresh();
//嘗試加水,水滿則拒絕
if (water + 1 > capacity) {
return false;
}
water = water + 1;
return true;
}
private void refresh() {
//當前時間
long currentTime = System.currentTimeMillis();
if (currentTime > lastLeakTime) {
//距上次漏水的時間間隔
long millisSinceLastLeak = currentTime - lastLeakTime;
long leaks = millisSinceLastLeak * ratePerMillSecond;
//容許漏水
if (leaks > 0) {
//已經漏光
if (water <= leaks) {
water = 0;
} else {
water = water - leaks;
}
this.lastLeakTime = currentTime;
}
}
}
}
令牌桶算法
若是令牌不被消耗,或者被消耗的速度小於產生的速度,令牌就會不斷地增多,直到把桶填滿。後面再產生的令牌就會從桶中溢出。
最後桶中能夠保存的最大令牌數永遠不會超過桶的大小,每當一個請求過來時,就會嘗試從桶裏移除一個令牌,若是沒有令牌的話,請求沒法經過。
public class TokenBucketLimiter {
private long capacity;
private long windowTimeInSeconds;
long lastRefillTimeStamp;
long refillCountPerSecond;
long availableTokens;
public TokenBucketLimiter(long capacity, long windowTimeInSeconds) {
this.capacity = capacity;
this.windowTimeInSeconds = windowTimeInSeconds;
lastRefillTimeStamp = System.currentTimeMillis();
refillCountPerSecond = capacity / windowTimeInSeconds;
availableTokens = 0;
}
public long getAvailableTokens() {
return this.availableTokens;
}
public boolean tryAcquire() {
//更新令牌桶
refill();
if (availableTokens > 0) {
--availableTokens;
return true;
} else {
return false;
}
}
private void refill() {
long now = System.currentTimeMillis();
if (now > lastRefillTimeStamp) {
long elapsedTime = now - lastRefillTimeStamp;
int tokensToBeAdded = (int) ((elapsedTime / 1000) * refillCountPerSecond);
if (tokensToBeAdded > 0) {
availableTokens = Math.min(capacity, availableTokens + tokensToBeAdded);
lastRefillTimeStamp = now;
}
}
}
}
漏桶和令牌桶的比較
使用 RateLimiter 實現限流
RateLimter 提供的 API 能夠直接應用,其中 acquire 會阻塞,相似 JUC 的信號量 Semphore,tryAcquire 方法則是非阻塞的:
public class RateLimiterTest {
public static void main(String[] args) throws InterruptedException {
//容許10個,permitsPerSecond
RateLimiter limiter = RateLimiter.create(10);
for(int i=1;i<20;i++){
if (limiter.tryAcquire(1)){
System.out.println("第"+i+"次請求成功");
}else{
System.out.println("第"+i+"次請求拒絕");
}
}
}
}
總結
本文分享自微信公衆號 - JAVA乾貨分享(JAVA347411)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。