每周提问 : Pika /Redis ZSet排行榜更多细节 /Nodejs的Stop the World

83 阅读5分钟

Pika

公司项目中一直使用Redis做主要的数据存储(RDB + AOF) ,但随着项目的有运行和维护时间变长后,我们发现Redis的存储占用变的巨大, 成本也变得巨大,在找解决方案时候看到了Pika,

什么是Pika呢

pika是一个数据库,存储数据到磁盘上,但Pika的接口和Redis是兼容的,如果我们要把从Redis读写数据修改成从Pika中读写数据将会很简单,这样我们就可以比较平滑的将Redis变成"纯缓存"

我们的解决方案

当然Pika是其中一个解决方案,最终我们并没有使用Pika,还是比较传统的冷热数据分离的方式,将冷数据放到mongodb中,redis放热数据,当玩家在登录时,如果在redis中找不到的话就去mongodb中找,为了不影响后续的逻辑和性能,我们会将mongodb中的数据恢复到redis中来,这样在减少redis存储的基础上,也能保持之前高性能.

Redis ZSet排行榜更多细节

我之前对使用Redis的ZSet实现排行榜有一些细节上模糊的地方,

  1. 相同score的情况下,我想实现先完成的排到前面如何实现?
  2. 我想在score中存储一个时间戳(完成时间排行榜)怎么办呢?
  3. 一个排行榜要显示的信息有很多,比如Nickname / 头像等,这些数据我应该如何存储呢?

下面来解答我这些模糊点:

  1. 我们可以把score变成一个浮点类型的数据, 构建成 score.完成时间倒叙 的结构,其中score.完成时间倒叙是什么意思呢? 我们用一个固定的时间去减完成时间,这样就实现了完成时间短的排到前面的需求
  2. 这个的解决方案和问题1类似,我们用我们的时间去减去一个固定时间,比如这个活动的开始时间,就能得到一个比较小的数了(long类型肯定够用了)
  3. 有两个解决方案,其一: 在redis中使用hash的方式将排行榜中玩家的Nickname,头像等进行单独存储,在获取排行榜的时候使用 hmget的方式批量的去获取数据再整理成完整的排行榜数据返回给客户端,当然这里建议是分页返回给客户端,不然hmget的获取的数据太多的话也会影响到redis的效率,其二: 将zset的member存储一个JSON字符串,这种不用单独存储,但有一个问题,如果我的Nickname改了我还需要删除之前的的member数据,而且从感观上也感觉怪怪的

Nodejs的Stop the World

这个提问的缘由是因为最近我们的项目CPU出现了异常增长的情况,经过了各种排查,觉得可能是因为内存到了临界点,疯狂GC导致CPU异常,我尝试设置--max-old-space-size来解决这个问题,回归主题,

  1. Stop the World是个什么?
  2. Stop the World触发条件什么?
  3. Stop the World表现是什么样子的?
  4. Stop the World如何解决?

下面来解答这些模糊点:

  1. 在 Node.js 中,"Stop the World"(简称 STW)是指 垃圾回收(Garbage Collection, GC)过程中导致的程序执行暂停现象。由于 Node.js 基于 V8 引擎(JavaScript 运行时),而 V8 的垃圾回收机制在某些阶段会暂时阻塞主线程(事件循环),导致程序在这段时间内无法响应任何请求或处理其他任务。关键信息: 暂时阻塞主线程
  2. 什么情况下会出现这种情况 Node.js 使用 V8 引擎的内存管理机制,其垃圾回收通过以下方式触发:
  • 新生代(Young Generation) :存放短期存活对象,使用高效的 Scavenge 算法(复制策略),GC 过程短暂,但会频繁发生。
  • 老生代(Old Generation) :存放长期存活对象,使用 Mark-Sweep(标记清除)和 Mark-Compact(标记整理)算法,需遍历内存中的对象,耗时较长。 在以下情况下会引发明显的 STW: 老生代垃圾回收:Mark-Sweep 或 Mark-Compact 需要遍历和整理内存,此时主线程会被完全阻塞。 内存占用过大:当应用频繁创建/释放对象(尤其大对象)时,GC 触发更频繁,STW 时间可能显著增加。 堆内存接近上限:Node.js 默认堆内存上限约 1.4GB(64 位系统),接近此值时,V8 会频繁尝试释放内存,导致 STW 时间变长。
  1. 表现如下:
  • 延迟敏感型服务(如 API 服务器):若 STW 时间超过客户端超时阈值(如 HTTP Keep-Alive),会导致请求超时或失败。
  • 实时应用(如 Websocket、游戏服务器):可能感知到明显的卡顿。
  • 高并发场景:主线程暂停期间,所有事件(I/O 回调、定时器等)都会被延迟处理。
  1. 如何解决
  • 避免内存泄漏(合理管理闭包、缓存、全局变量)。
  • 减少大对象的频繁创建,尽量复用对象。
  • 使用 Stream 处理大文件,避免一次性加载到内存。
  • 通过 --max-old-space-size 调整老生代内存上限(如 node --max-old-space-size=4096 app.js)。