此处仅是搜集资料的一些小结,若有谬误,还望不吝赐教。

背景:考虑到游戏的高频读写行为,Demo 中将 Redis 作为一级存储,定时向 MySQL 进行二级存储。

游戏体验还不错,于是打算制作正式版,就延展出了不少关于效率和永久缓存的遐思,这里简单赘述一二。

关于存储结构:String vs Hash

这个问题最初不存在,我是直接用 String convert to JSON,然后看到了汉家松鼠的技术总结,他们采用的是 Hash,就此产生了 diff 这两者的想法。

简单的思考了一下, 也参考了 Redis strings vs Redis hashes to represent JSON: efficiency? 等提问。

Hash 的特点:

  • K-V 式的读取,500kb 的数据里,直接读到你需要的 value,高效!
  • K 需要额外的存储空间。
  • 嵌套对象不易存储,问题不大,除非是 MongoDB 爱好者。
  • K 的可用性需要额外保证。

String 的特点:

  • 一口气存放所有数据,会比较大,对于 500kb 这种,可以拆分为多个部分来存储。
  • 需要额外转码——譬如 PHP 的 json_decode 还有优化空间,会有性能考虑。
  • 无法定制化的做 TTL。

TTL 由转储动作来自行处理,毕竟不是缓存,而是二级数据库,不考虑过期。
转码性能也需要玩家到一定量级,而且可以配合拆分结构、重写 json decode 来优化。

尽管 Hash 似乎也不错,最终对我并没有特别重大的吸引力,仍保持 String 来处理。

关于 Redis to MySQL

这个问题发现的比较早,如何稳定、可靠的让 Redis 数据定期进入 MySQL,不然玩家会丢档。v2ex 上也提问过,技术大佬们提了很多方案:

  • 时间轮:时间戳作为 key,玩家数据的快照作为 value,定时器每秒处理本时间戳的所有 value,将其实例化入 MySQL;玩家信息更新时,将自身的时间戳 +60s,存入时间轮。
  • 有序列表:每次只需扫描表头,和时间轮类似,更省事。
  • spacekey:Redis 官方功能,当缓存失效时可以触发一个事件,问题在于,这个事件首先默认不支持集群(但可以手动订阅),其次这个事件在客户端 shutdown 时,会丢失在这期间的 message。不是很稳定。
  • MQ 延时队列 & 死信队列:将数据丢进去,延时处理。

死信队列

从未听过,就简单探究了一下。

队列的 TTL 过期处理方式是加入另一个队列——我们将另一个队列,称之为“死信队列”(dead message queue -- 字面意思),然后由死信消费者来处理。

  1. 将数据存入队列后,设置 TTL;
  2. 等待数据过期,被正常队列丢入死信队列;
  3. 死信队列的消费者逐条消费。

这里还有一个好处:

倘若我们针对每条信息设置 TTL,当正常队列的“过期信息”太多时,新的过期消息并不会立刻丢入死信队列,而是按顺序、按压力来由正常队列稳定丢入死信队列。

死信消费者就无需担忧压力问题,正常队列会帮忙削峰。

(感谢 Rocketer 大佬,若非他点出来,我这种 MQ 萌新真的会漏掉这一点,很有意思)

# Dead Letter Exchanges
(强烈建议玩 MQ 的萌新读 RabbitMQ 的官方技术博文,很多技术问题的探讨都很有深度)

UioSun
603 声望33 粉丝

use google find the world. "该用户太懒", dead was yesterday.