| 导语 过载保护对于to c业务的服务来说是非常重要的。试想一下,如果有一个运营活动或者特殊的日子导致流量暴增,用户每秒请求数远远超出了我们的服务所能处理的最大值,假如我们没有做过载保护,很快这个服务所能利用的资源将会被耗尽,无法处理任何请求,如果这个服务所在机器的其他服务没有和这个服务资源隔离,也会影响到其他正常的服务,那么一次严重的事故就发生了。
什么是过载保护
过载
什么是过载? 简单来说,就是当前负载超过了系统的最大处理能力,如:
系统实际每秒能处理的请求量为1000个,但实际每秒的请求量却远大于1000个,可以判定系统过载。
过载保护
对于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 服务在最开始的时候使用这个中间件即可。
这种方法有个前提条件: 必须要知道这个服务同时能处理的最大请求数(maxCount),一般情况下这个服务会有多个api,处理每个api请求所消耗的资源可能不一样,我们很难模拟真实场景得到准确的maxCount。你如果想说我们可以限制每个接口的最大请求数啊,并不一定要限制这个服务的最大请求数,那你就要考虑到一种极端场景,所有的接口都达到最大请求数的时候你的服务能否抗住,这可能需要更高性能或更多的服务器才能满足这种场景。
通过实时查cpu使用率控制请求数
在web服务的场景,CPU是服务最主要的瓶颈,所以我们可以在处理请求之前首先判断下当前CPU负载是否达到了我们设置的CPU最大使用率,然后丢弃或者随机丢弃。 我们需要的是当前服务的进程占用的cpu百分比,github上找到一个 node-usage 。 稍微改造一些,思路如下: 首先node-usage,如果实时获取当前进程cpu利用率,会增加系统的负载,我们可以自己控制采样cpu使用率的评率,得到cpu利用率。
nodejs koa 代码实现:
// 写一个中间件 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都能耗尽的场景下,我们要考虑在上层或系统层做过载保护。
利用nodejs event-loop的机制控制请求数
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实时使用率的方法,更具有通用性。
参考资料:
本文首发在掘金转载请注明原作者,如果你觉得这篇文章对你有帮助或启发,也可以来请我喝咖啡。 利益相关:本篇文章所有涉及到的软件均为笔者日常所用工具,无任何广告费用。