本文主要是介绍欢乐西游通用缓存系统设计—应用Redis,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、需求背景:
欢乐西游是一款手机卡牌游戏,采用分区分服架构,目前已经登录手Q和微信平台。有些全区玩法需要使用到离线玩家数据:1、天梯PVP系统(按竞技积分匹配全区玩家,包括离线玩家),2、大闹天宫(按战斗力匹配全区玩家,包括离线玩家)。
欢乐西采用Tcaplus的KV方式存储玩家数据,数据读写以角色gid作为key进行,因此通过积分或者战斗力的方式匹配玩家将会非常麻烦。以天梯系统为例,为了通过竞技积分查找玩家,需要Cache玩家离线数据并且建立以积分作为数据索引的数据结构,方便通过竞技积分找出符合要求的角色。PVP玩法都是全区级的,因此Cache数据应该在大区级,便于不同的gamesvr访问。
二、缓存设计关键点:
1、 数据持久化。如果进程coredump或者服务器down机怎么办?缓存数据怎么恢复,是否需要通过数据上报机制来重建缓存?
2、 数据量。一个大区缓存数据量级有多少?能否做到缓存在一台机器还是数据分片在不同机器?
3、 性能问题。缓存全区数据,百万级,能否保证业务需要的读写和匹配性能?
4、 数据结构设计。竞技积分匹配(以1000分为例),需要匹配900~1100范围玩家,并且整体匹配满足密度分布,比如[900,920]玩家分布占[900,1100]范围50%的数量,那么从整体来看,匹配到[900,920]的角色要占50%的概率。如何设计数据结构,能够方便管理缓存数据并且能够很好支持匹配规则?
5、 可扩展性。缓存数据数据管理应该具备一定通用和扩展性,例如后续大闹天宫通过战斗力来匹配玩家,扩展新的字段和结构。
三、缓存数据结构设计:
按积分分段管理缓存数据,例如每20分一个积分段,所有符合当前积分段的玩家放在一起。最终呈现的数据结构如下:
当需要匹配[900,1100]角色时,1、计算出匹配范围涉及到的具体积分段 2、计算出每个积分段人数,再根据每个积分段人数比例算出对应积分段的匹配概率 3、通过概率计算匹配最终落在哪个积分段。
上面是一个比较通用的匹配算法,但欢乐西游天梯匹配规则更复杂,他会首先从积分段总共匹配200人(匹配人数符合积分段人数比例分布),再根据这200人等级和自己等级差按概率分布再匹配。具体细节不在赘述,但总体思想如上描述。
四、架构设计方案一:
游戏自己实现MatchSvr,cache玩家离线数据,实现匹配逻辑。使用共享内存方式存储cache数据,保证不停机更新后数据不丢失。
优缺点分析:
1、自己实现一个MatchSvr的缓存系统,会增加开发工作量和复杂度。
2、等级段100+个,每个等级段角色数不固定,用链表来维护增大实现难度。同时,在匹配对手中,需要随机匹配也会增大链表操作的复杂度,查找和删除需要遍历链表,性能低。
3、MatchSvr缓存数据落地磁盘,保证停机后的数据恢复。磁盘落地可以每天晚上开个线程一条一条写磁盘,整个开发量较大。或者使用mmap映射磁盘文件到内存,定时msync同步内存脏数据到磁盘,但这样内存固定,如果以后加字段或新增系统会非常麻烦。
4、扩展性不够好,没有一个通用的缓存系统,新系统需要额外开发。
五、架构设计方案二:
针对方案一,其实可以将匹配逻辑和缓存数据分离,解耦合。缓存数据层是一个通用的存储和数据管理方案,后续新增字段和数据都能非常方便扩展,常驻进程,修改少,稳定,支持数据持久化。匹配逻辑层用于读写缓存数据并且应用各种匹配规则,无状态服务,即使匹配逻辑修改,只需要更新逻辑层。
我们采用开源的Redis作为缓存数据层,提供高效数据管理和持久化功能。Redis是一款开源的、高性能的key-value存储。Redis 和其他很多 key-value 数据库的不同之处在于, Redis 不仅支持简单的字符串键值对,它还提供了一系列数据结构类型值, 比如列表、哈希、集合和有序集, 并在这些数据结构类型上定义了一套强大的 API 。通过对不同类型的值进行操作, Redis 可以很轻易地完成其他只支持字符串键值对的 key-value 数据库很难(或者无法)完成的任务。例如天梯系统一个积分段对应一个集合,在redis层,key就是积分段,value就是集合,可以方便查找删除等操作。
Redis 分别提供了 RDB 和 AOF 两种持久化模式。在 Redis 运行时, RDB 程序将当前内存中的数据库快照保存到磁盘文件中, 在 Redis 重启动时,RDB 程序可以通过载入 RDB 文件来还原数据库的状态。
Redis提供通用的数据缓存服务,服务各个需要的子模块。这个进程类似mysql,只需要启动一次,以后不再启停。CacheProxy使用tapp多线程模式,每个线程同步访问redis。经测试,基本数据接口操作本机2.5W/s,跨机器3500/s。因此,CacheProxy开3~5个线程同步访问不会有任何性能问题。当然,使用cacheproxy能够保证和redis部署在同一台机器上。
优点:
1、 Redis 不仅支持简单的字符串键值对,它的Value还支持列表、哈希、集合和有序集, 并在这些数据结构类型上定义了一套强大的 API 。 在具体应用时,key是一个具体积分段,value就是积分段下所有角色数据组成的集合,Redis底层实现用hashmap方式,集合元素可以动态增加,查找删除都是O(1)时间复杂度。因此可以很轻易解决方案一1、2点。
2、 Redis支持RDB持久化模式,通过配置,可以定期将全量数据写到磁盘文件中。Redis会fork一个子进程,由子进程完成写磁盘的工作。
3、 提供通用的Cache数据服务,拥有较好的扩展性,后续新增系统都可以来使用
4、 节省开发工作,不需要重复实现各种数据结构管理,数据落地等模块。
5、 Redis提供强大的客户端,可以方便查看各种数据和key,便于开发测试和运维,而且还有很多脚本工具,便于后期数据分析。
一些思考:
独立一个CacheProxy进程?
最早的想法是直接通过interface来访问Redis服务,但可能会有两个缺点:
1、 Interface作为world<=>zone, zone<=>zone之间的公共纽带,作用日益加重。Interface直接影响到天梯系统、帮派系统、登录踢人、idip请求、邮件体力通知、公告等。为了保证interface本身作为消息转发的简单和稳定,将redis客户端api访问独立出来会安全许多
2、 Redis客户端访问Redis服务采用多线程同步方式。如果interface和Redis不在同一个机器,将会极大影响同步访问性能(具体参照上面数据) ,性能瓶颈在网络io层。按照目前的服务器部署,world层会使用一台机器,interface和redis分布在一台机器肯定不会影响访问性能,但以后可能会将interface部署到不同机器,保证interface容灾。
同步还是异步访问redis?
异步访问优点:
1、性能非常高。单线程简单读写压测显示,异步20W+/s, 同步2.5W/s
2、异步访问基本不受跨机器影响,瓶颈在redis自身数据处理速度。
但异步相比同步,也有些缺点:
1、异步api本身教复杂,需要用到libevent或者libev库事件机制驱动。
2、异步api会使业务流程变得非常复杂。例如天梯系统更新一个玩家积分,涉及到更新玩家积分数据、从原来积分段删除、添加到新的积分段三个步骤,每个步骤都需要回调处理。而且cacheproxy本身无状态,不希望有数据缓存来支持回调处理。
综上:使用同步接口已经拥有非常高的性能,而且使用cacheproxy能够保证和redis部署在同一台机器,性能完全满足业务需求。同时,同步接口api极为简单,上述更新玩家积分的3个操作,都可以当成本地直接完成,业务流程很清晰。
六、运行流程:
l 上报竞技积分
l 匹配对手
七、压测性能:
Cacheproxy采用多线程同步方式,在v8.2机型压测(8核16G内存),缓存数据1000W,内存占用2G,按上述实际综合业务场景压测数据如下:
Cacheproxy线程数 | 上报积分/s | Cacheproxy单线程cpu占用 | Redis Cpu占用 |
1 | 0.89W | 47% | 42% |
2 | 1.62W | 41% | 58% |
3 | 2.38W | 38% | 76% |
4 | 2.72W | 31% | 84% |
5 | 2.56W | 26% | 81% |
6 | 2.46W | 22% | 72% |
7 | 2.29W | 15% | 66% |
15 | 1.95W | 7% | 58% |
Cacheproxy线程数 | 匹配数/s | Cacheproxy单线程cpu占用 | Redis Cpu占用 |
1 | 3030 | 31% | 62% |
2 | 4370 | 22% | 84% |
3 | 4500 | 16% | 85% |
4 | 5050 | 15% | 86% |
5 | 5100 | 12% | 86% |
6 | 5700 | 11% | 88% |
7 | 5700 | 10% | 88% |
15 | 5000 | 4% | 82% |
综上:cacheproxy线程并发4~5个性能最好。当线程数较少时,同步访问瓶颈在网络IO层。当线程数过多时,由于cpu是8核,其他线程会抢占Redis的cpu资源,导致Redis本身数据处理减慢。
八、运营数据:
欢乐西游是分区架构,redis服务部署在每个大区,cacheproxy并发5个线程,以191太上老君大区为例:
注册用户:50W
Redis内存占用:100M
Redis RDB备份文件:28M
Pvp竞技上报+匹配峰值:44 * 5 =220次/s (tnm2每分钟统计峰值44个/s,瞬间峰值按5倍算)
Cacheproxy和Redis cpu占用在1%左右,基本可以忽略。
以下是一些性能截图:
九、容灾:
根据业务场景需求,pvp缓存数据不是十分关键的数据,哪怕全部丢失也可以自动上报,重新建立缓存数据。
因此Redis采用RDB模式备份数据,每天全量备份一次到本地磁盘,并拷贝一份到其他机器。即使Redis挂了或者整个机器down机,也能通过加载RDB文件非常方便的恢复数据。哪怕恢复的数据是老的,对于玩家也是毫无感知。
如果整个world机器down机,gamesvr层在一定时间收不到心跳包也会将world层置为超时。这时匹配流程会转入匹配机器人,玩家还是能进行竞技玩法,提供一定程度有损服务。直到world机器恢复,匹配流程将自动切换回正常模式。
十、其他:
总体而言,匹配逻辑层和数据层分离,数据层采用redis具有良好的扩展性,数据管理、持久化、高性能都能非常好的满足业务需求。后续的大闹天宫系统,通过战斗力匹配离线玩家,也使用类似的方式,1天时间就完成整个数据上报和匹配流程,极大简化了开发流程。
当然,Redis还有非常丰富的功能,集群管理、AOF备份等机制,我们只是用到其中很小的一部分功能,满足业务需求并具有一定扩展性就可以,关键是能够方便部署,不需要额外的机器资源成本和运维成本。
这篇关于欢乐西游通用缓存系统设计—应用Redis的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!