saijs/wiki

每周异常:第 4期,如何避免客户端攻击造成的大量异常报警 (20130807)

hotoo opened this issue · 2 comments

在 JavaScript 异常监控过程中,发现有很多由于客户端连续攻击造成的大量异常,导致监控系统报警。

2013-08-07 9 58 37

(图中大量不连续的波动,都是攻击造成的)

这种少量用户(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 实例,同一台机器的状态也保证不了。

于是我们又想到一种从客户端来标识用户的方法:

  1. 客户端单位时间内同一个页面抛出的同一个异常,第一次打上 uv 标记。
  2. 数据处理时遇到 uv 标记的日志,同时统计算做一个异常 uv。

由于客户端时间和服务端有时间差,每个客户端时间和服务端时间差也不一致,导致这种方案会有稍大误差,但也基本能解决问题。

但是要在客户端打标记,则需要在客户端(cookie 或其他本地存储)记录每个异常(url, file, line, message 相同被当作同一个异常)最后抛出的时间,每次抛出异常都需要读写本地存储,稍嫌臃肿。

通过深入分析实际的攻击案例,发现攻击引发大量异常的场景中,客户端页面是不刷新或重新访问的,都是直接在当前访问的页面执行上批量脚本。

因此我们可以使用局部变量,将当前访问的页面的每个异常最后抛出时间记录在内存中,重新访问页面则重新开始记录。

这种方案对于防范攻击造成的大量异常非常有效;对于大量用户涌入引起的异常也有一定效用,毕竟正常用户较少会频繁触发异常。

总之,通过这个方案,我们可以非常有效的实时监控异常波动,较少误报。

p.s. 这个想法是早上洗澡的时候想到的,还没开始实践。但是我信心满满写下这篇,数据会来证明我是对的。

这个想法是早上洗澡的时候想到的,还没开始实践。但是我信心满满写下这篇,数据会来证明我是对的。

顶这句话。

是时候结帐了。正好发布一天,大家看数据:

2013-08-20 3 55 10

图中的灰色竖线是发布点,橙色是 JavaScript 异常,绿色是静态资源异常。

妈妈再也不用担心我的手机会收到误报的报警短信了。