本文介绍如何使用 Golang 采用 Redis 的有序集合 zset 实现一个用户排行榜。
用户排行榜按用户的某一种排序值进行排序,比如充值金额,当该排序值相同时,则需按达成该值的时间先后顺序进行排序,比如两个用户充值金额都是 100 元,则先充满 100 元的用户排名在前面。
Redis 的 zset 做这种排行榜是十分合适的,在不考虑时间先后顺序的情况下,只需将用户的排序值作为 zset 的 score, 用户 id 作为 zset 的 value,然后通过 zset 提供的方法进行查询即可。
当需要把时间维度也作为排序因素考虑时,我们可以将时间信息也放入到 score 中一起参与排序。
这里采用的方式是将 zset score 按十进制数拆分,整数部分用于表示用户排序值 val,小数部分表示排行活动结束时间戳(秒)与用户排序值更新时间戳(秒)的差值 deltaTs。
最好是将 score 十进制数字总长度限制为最长 16 位,超过 16 位的数会有浮点数精度导致发生进位,影响结果的正确性。小数部分使用时间戳差值 deltaTs 就是为了缩短整个数字的长度以保存更大的排序值。
小数部分的数字长度由 deltaTs 的数字长度确定,整数部分最大支持长度则为:16-len(deltaTs)。比如排行榜活动时长为 10 天,结束时间减去开始时间,总时间差为 864000 秒,长度为 6,则 deltaTs 宽度为 6,每当有用户更新排序值时,计算活动时间与当前时间的时间戳差值 deltaTs 作为小数部分,其最长为 6 位数,最小 1 位数,不够 6 位数时,则在前面补 0。当然,你需要根据你的实际情况进行修改,比如你需要使用毫秒维度。
这样整数部分为用户真实的排序值,小数部分为用户的时间信息,在排行活动结束时间固定的情况下,越早触发小数值(deltaTs)越大,就可以实现在整数部分的排序值相同时,按其时间进行排序。
原理说明完毕,现在来看看如何实现。