每周异常:第 4期,如何避免客户端攻击造成的大量异常报警 (20130807)
hotoo opened this issue · 2 comments
在 JavaScript 异常监控过程中,发现有很多由于客户端连续攻击造成的大量异常,导致监控系统报警。
(图中大量不连续的波动,都是攻击造成的)
这种少量用户(UV)发起攻击造成的大量异常(PV)波动,不是我们实时监控需要关注的部分。
我们实时监控最重要的是要发现由于系统发布过程中,我们的发布的代码有问题导致大量用户受影响而出现的波动(波形可能跟上图的每一个异常波形相似)。
理论上导致异常数据波动的场景包括:
- 少量用户发起大量攻击。
- 大量用户同时涌入或离开。
- 发布问题代码影响到大量用户。
少量用户发起大量攻击造成数据波动,是我们可以不必实时关注实时报警的部分。
大量用户同时涌入或离开造成的异常数量(PV)波动,也是我们无需实时关注的部分,另外我们也可以通过异常率规避波动。
场景 | 异常 PV | 异常 UV | 异常率(PV) | 异常率(UV) |
---|---|---|---|---|
攻击 | 波动 | 无波动 | 波动 | 无波动 |
涌入 | 波动 | 波动 | 无波动 | 无波动 |
发布 | 波动 | 波动 | 波动 | 波动 |
注:
- 异常率(PV) = 异常PV / 所在页面PV
- 异常率(UV) = 异常UV / 所在页面UV
从上面的对照表可以看出,理论上我们只需要关注异常率(UV) 是否正常波动就可以了。
理想很性感,现实很骨感。
我们的数据仓库、数据分析系统对于实时 PV 有很强的处理能力,但是对于 UV 就感觉力有不逮。
起初想到在实时日志处理逻辑中使用缓存(伪代码):
class LogParse(){
var user_cache = {};
var MINUTES_FORMAT = "YYYYMMDDHHmm";
var lastTime = moment().format(MINUTES_FORMAT);
function parse(log){
// PV 统计
var now = moment().format(MINUTES_FORMAT);
if(now !== lastTime){
user_cache = {};
lastTime = now;
}
if(user_cache.hasOwnProperty(user_id)){return;}
// UV 统计
}
}
- 但是实时数据分析系统是分布式的,由多台服务器组成的集群同时处理日志,所以里面的内部状态只有本机有效
- 另外更致命的是,实时数据分析系统是无状态的,下次执行会 new 新的 LogParser 实例,同一台机器的状态也保证不了。
于是我们又想到一种从客户端来标识用户的方法:
- 客户端单位时间内同一个页面抛出的同一个异常,第一次打上 uv 标记。
- 数据处理时遇到 uv 标记的日志,同时统计算做一个异常 uv。
由于客户端时间和服务端有时间差,每个客户端时间和服务端时间差也不一致,导致这种方案会有稍大误差,但也基本能解决问题。
但是要在客户端打标记,则需要在客户端(cookie 或其他本地存储)记录每个异常(url, file, line, message 相同被当作同一个异常)最后抛出的时间,每次抛出异常都需要读写本地存储,稍嫌臃肿。
通过深入分析实际的攻击案例,发现攻击引发大量异常的场景中,客户端页面是不刷新或重新访问的,都是直接在当前访问的页面执行上批量脚本。
因此我们可以使用局部变量,将当前访问的页面的每个异常最后抛出时间记录在内存中,重新访问页面则重新开始记录。
这种方案对于防范攻击造成的大量异常非常有效;对于大量用户涌入引起的异常也有一定效用,毕竟正常用户较少会频繁触发异常。
总之,通过这个方案,我们可以非常有效的实时监控异常波动,较少误报。
p.s. 这个想法是早上洗澡的时候想到的,还没开始实践。但是我信心满满写下这篇,数据会来证明我是对的。
这个想法是早上洗澡的时候想到的,还没开始实践。但是我信心满满写下这篇,数据会来证明我是对的。
顶这句话。