| 導語 過載保護對於to c業務的服務來講是很是重要的。試想一下,若是有一個運營活動或者特殊的日子致使流量暴增,用戶每秒請求數遠遠超出了咱們的服務所能處理的最大值,假如咱們沒有作過載保護,很快這個服務所能利用的資源將會被耗盡,沒法處理任何請求,若是這個服務所在機器的其餘服務沒有和這個服務資源隔離,也會影響到其餘正常的服務,那麼一次嚴重的事故就發生了。node
什麼是過載? 簡單來講,就是當前負載超過了系統的最大處理能力,如:git
系統實際每秒能處理的請求量爲1000個,但實際每秒的請求量卻遠大於1000個,能夠斷定系統過載。github
對於web應用,就是對請求量的控制問題,控制請求量不要超過系統的最大處理能力。web
直接的作法就是控制當前服務同時處理的請求數,這個很好控制,請求來一個就計數+1, 處理完請求回包就減1,若是當前在處理的請求總數超過了設置的最大值,那麼就直接回包503錯誤。 nodejs koa 代碼實現:算法
// 寫一箇中間件 overloadProtection.js
const maxCount = 1000;
let totalCount = 0;
moudle.exports = async(ctx, next) => {
totalCount += 1;
// TODO 這裏能夠加一些監控上報
if (totalCount > maxCount) { // 直接丟棄
ctx.status = 503;
ctx.body = 'Service heavy load!!!';
return;
}
await next();
totalCount -= 1;
}
複製代碼
而後koa 服務在最開始的時候使用這個中間件便可。api
這種方法有個前提條件: 必需要知道這個服務同時能處理的最大請求數(maxCount),通常狀況下這個服務會有多個api,處理每一個api請求所消耗的資源可能不同,咱們很難模擬真實場景獲得準確的maxCount。你若是想說咱們能夠限制每一個接口的最大請求數啊,並不必定要限制這個服務的最大請求數,那你就要考慮到一種極端場景,全部的接口都達到最大請求數的時候你的服務可否抗住,這可能須要更高性能或更多的服務器才能知足這種場景。bash
在web服務的場景,CPU是服務最主要的瓶頸,因此咱們能夠在處理請求以前首先判斷下當前CPU負載是否達到了咱們設置的CPU最大使用率,而後丟棄或者隨機丟棄。 咱們須要的是當前服務的進程佔用的cpu百分比,github上找到一個 node-usage 。 稍微改造一些,思路以下: 首先node-usage,若是實時獲取當前進程cpu利用率,會增長系統的負載,咱們能夠本身控制採樣cpu使用率的評率,獲得cpu利用率。服務器
nodejs koa 代碼實現:dom
// 寫一箇中間件 overloadProtection.js
const usage = require('usage');
const pid = process.pid; // 當前node服務的進程pid
let sampleCpuInterval = 1000; // 採樣cpu使用率的頻率
let cpuUsedLimit = 85; // cpu使用率上限
let getCupUsed = function getCupUsed() {
setInterval(() => {
usage.lookup(pid, {keepHistory: true}, (err, result) => {
if (result) {
global.cpuUsed = result.cpu || 0;
}
})
}, sampleCpuInterval);
};
getCupUsed();
module.exports = async(ctx, next) => {
// TODO 這裏能夠加一些監控上報
if (cpuUsedLimit && global.cpuUsed > cpuUsedLimit) {
// 這裏是隨機丟棄的算法,能夠思考下爲何這麼作
let rejectRate = Math.pow((global.cpuUsed - cpuUsedLimit) / (100 - cpuUsedLimit), 2);
if (Math.random() > rejectRate) {
ctx.status = 503;
ctx.body = 'Service heavy load!!!';
return;
}
}
await next();
};
複製代碼
壓測很順利, 可是若是請求量特別特別大的時候,即便node服務沒有作任何邏輯處理,只是簡單回包cpu都能耗盡的場景下,咱們要考慮在上層或系統層作過載保護。koa
github上找到一個overload-protection,使用很是容易。可是我本身壓測後發下有很大問題。我我的理解做者意圖以下: 處理請求以前,先設置一個setInterval, 經過計算setInterval裏的回調函數執行延時來預估當前cpu負載。能夠預見的是延遲越高,cpu負載越高。延遲在某個值時多是咱們指望限制的cpu最大使用率。 咱們看下源碼
var xtend = require('xtend')
var EE = require('events').EventEmitter
var defaults = {
limit: 42, // 默認延遲是42,
sampleInterval: 5
}
function loopbench (opts) {
opts = xtend(defaults, opts)
var timer = setInterval(checkLoopDelay, opts.sampleInterval)
timer.unref() // 參考 https://zhuanlan.zhihu.com/p/38091559
var result = new EE(); // EventEmitter實例
result.delay = 0
result.sampleInterval = opts.sampleInterval
result.limit = opts.limit
result.stop = clearInterval.bind(null, timer)
var last = now()
return result
function checkLoopDelay () {
var toCheck = now()
var overLimit = result.overLimit
result.delay = toCheck - last - result.sampleInterval // 實際執行的延時,系統負載越高(越忙)延時越大
last = toCheck
result.overLimit = result.delay > result.limit
if (overLimit && !result.overLimit) {
result.emit('unload'); // 由過載變成正常,外面監聽這個事件而後告訴服務變成正常狀態了,正常處理請求
} else if (!overLimit && result.overLimit) {
result.emit('load'); // 由正常變爲過載,外面監聽這個事件而後告訴服務變成過載狀態了,要丟棄請求
}
}
function now () {
var ts = process.hrtime()
return (ts[0] * 1e3) + (ts[1] / 1e6)
}
}
複製代碼
可是我持續壓測了幾分鐘,cpu全層使用率是98%-100%,並無觸發到它的過載機制,我試着打印了下 result.delay 發現特別小:
而且還有比較小的負數,我想拌一個黑人問號臉表情,整個壓測過程當中只零星出現了幾個503(觸發過載)。不知是我壓測的姿式不對仍是其餘因素,這種方法宣告失敗。可是我以爲做者的是思想是能夠借鑑的,能夠用於其餘的應用場景。
上面描述了過載保護的三種實踐方法,前兩種均可以應用於特定的場景下,第三種方法實踐失敗。我的認爲第二種經過判斷cpu實時使用率的方法,更具備通用性。
本文首發在掘金轉載請註明原做者,若是你以爲這篇文章對你有幫助或啓發,也能夠來請我喝咖啡。 利益相關:本篇文章全部涉及到的軟件均爲筆者平常所用工具,無任何廣告費用。