此处仅是搜集资料的一些小结,若有谬误,还望不吝赐教。
背景:考虑到游戏的高频读写行为,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 -- 字面意思),然后由死信消费者来处理。
- 将数据存入队列后,设置 TTL;
- 等待数据过期,被正常队列丢入死信队列;
- 死信队列的消费者逐条消费。
这里还有一个好处:
倘若我们针对每条信息设置 TTL,当正常队列的“过期信息”太多时,新的过期消息并不会立刻丢入死信队列,而是按顺序、按压力来由正常队列稳定丢入死信队列。
死信消费者就无需担忧压力问题,正常队列会帮忙削峰。
(感谢 Rocketer 大佬,若非他点出来,我这种 MQ 萌新真的会漏掉这一点,很有意思)
# Dead Letter Exchanges
(强烈建议玩 MQ 的萌新读 RabbitMQ 的官方技术博文,很多技术问题的探讨都很有深度)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。