以王者荣耀例子来解释方案,并非是王者荣耀真实方案
业务需求分析
在王者荣耀中,我们会打排位,而且我们想展示我们的段位会有很多个排行榜,比如段位排行榜。而段位排行榜是我们经常关注的,我们会看我们的段位在区服的排名情况,在好友列表中的排名情况。
根据以上需求,我们可以分析出排行榜需要提供两个功能:
- 查看段位排行榜
- 查看自己的排名
设计方案
在做设计方案之前,有个细节得注意:段位在数据库和后端计算都是分数形式,比如青铜三,三颗星:13
那怎么设计这个方案呢?
有同学就会说,直接将数据保存在MySQL中,然后在玩家登录进行计算,得出各个所有排行榜。这种方案是可以,但是登录时需要计算,如果单个区服数据过多,就会导致玩家登录的RT太长了。
所以,对于现在来说比较好的设计方案是使用Redis的有序集合
方案细节
我们将段位保存在MySQL的个人排位信息表中,在Redis中会存储多个排行榜,比如全区段位榜,好友段位榜等等;当我们打完一局排位后我们在进行结算时同时也会更新多个段位排行榜(在DDD中一般用领域事件去更新排行榜)。
数据结构
Redis有序集合
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。
分数设计
对于分数设计,我们其实可以直接将段位的分数设置为score即可,但是需要考虑一种情况,就是两个玩家分数相同时,我们一般是将分数+时间戳进行组合,成为一个新的分数。
分数封装和解析代码如下:
//封装成分数
func packScore(score int64) float64 {
now := time.Now().Unix()
return float64(score*TimeLength +now)
//如果排行榜支持负数
//if score >= 0 {
// return float64(score*TimeLength + now)
//} else {
// return float64(score*TimeLength + now - TimeLength)
//}
}
//将分数解析
func parseScore(score float64) int64 {
return int64(score / TimeLength)
}