我们在 AWS VPC 内部有一个 nodeJS 实例,它向在另一个可用区域运行的 mongodb 实例发出请求。
节点实例被特定请求命中很多。该请求用于从 mongodb 实例中提取大量信息。第一次查询后,该条目将被缓存一段时间。
昨天,它返回的数据量发生了一些变化。代码如下:
console.log('before retrieve');
Model.find({}).exec(function() {
console.log('after retrieve');
});
它会命中“检索前”10 次,然后就停顿下来,自己发起 DoS 攻击。我删除了它正在提取的一些数据作为临时修复。
在 mongoDB 方面我有时会看到:
SocketException handling request, closing client connection: 9001 socket exception [SEND_ERROR]
我怎样才能避免这种情况发生?
答案1
您所描述的问题与 mongoose、mongodb 和 node 关系不大,更多的是通常称为“缓存踩踏”或“狗堆”的问题的表现。
顾名思义,当一大堆东西试图同时刷新缓存时,就会发生缓存踩踏。就你的情况而言,这种情况发生在缓存过期时或在将数据首次加载到缓存期间。突然间,大量请求进入并向数据库施加大量读取负载,这 1) 导致缓存刷新缓慢,2) 导致更多请求堆积起来等待缓存。这基本上会导致你看到的行为,即系统崩溃
这个维基百科页面相当清楚地描述了问题以及如何使用单独的进程或锁来解决它。由于节点没有锁或线程,所以这可能不是一个解决方案。此外,虽然单独的进程可以工作,但它要复杂得多。
我过去使用过的一种技术是使用两个过期的缓存键,一个键仅用于指示何时应刷新缓存,而另一个键保存实际数据。
为了说明这一点,假设我有一个foo
要缓存的对象,并且该对象每小时过期一次。我可以创建另一个密钥foo_refresh
,该密钥比该密钥早过期 1 分钟foo
。
当foo_refresh
密钥过期时,一个工作程序/请求会立即替换foo_refresh
密钥并忽略缓存,而是从数据库中提取数据,并foo
在完成后刷新密钥(同时重置过期时间)。使用这样的机制,我们在刷新缓存时获得一种“锁定”,这意味着不会有超过一个工作程序执行昂贵的读取。
假设缓存刷新时间少于 1 分钟,foo
则对象永不过期,而是通过密钥的过期进行刷新foo_refresh
。
希望有帮助!