在日常的开发中,我们经常会使用有序集合对有排序需求的数据进行存储。但是在某些情况下,我们可能需要根据多个条件对集合中的数据进行排序。因此我们需要对存储在集合中的成员分数进行特殊处理,才能满足需求。
例如,现在需要设计一个用户收礼值排行榜系统,用户根据收礼值的大小降序排序,在收礼值相同的情况下,则按照最后的更新时间升序排序。
我们都知道,在执行 zrevrange 命令时,如果成员分数相同的情况下,默认会根据字典顺序降序排序,如下图所示:
这并不符合我们的需求(uid1需要排在uid2前面),我们需要有一个简单的算法来保证更新后,收礼值相同的用户相对位置保持不变。
假设在每次更新用户收礼值的时候,都带上一个随时间衰减的偏移量,就可以保证uid1排在uid2前面,如下图所示:
Hooray!!!我们完成目标了!!!🥳🥳🥳
接下来,让我们来看看如何计算这个“随时间衰减”的偏移量。
在给定的一个时间区间内,随着时间的流逝,结束时间(右区间)与当前时间的差值会越来越小,如下图所示:
根据上述的分析结果,我们就可以写出如下的伪代码:
const startTime = 0;
const endTime = new Date('2038-01-01 00:00:00').getTime()
// 随着时间的推移,偏移量会越来越小
function calcOffset() {
const now = new Date().getTime();
return (endTime - now) / (endTime - startTime);
}
// 增加成员的分数
function incrby(key, member, increment) {
return redis.zincrby(key, increment, member)
.then(async (newScore) => {
const offset = calcOffset();
// 根据当前偏移量与上一次偏移量的差值,就可以计算出应该衰减的值
await redis.zincrby(key, offset - (newScore - Math.floor(newScore)), member);
return Math.floor(newScore);
});
}
这样,我们就能保证在值相同的情况下,按照最后的更新时间升序排序。
⚠️注意
- 在获取成员分数时,需要移除偏移量,即对分数进行取整数
- 此方法只适用于成员分数为整数的情况
- 在高并发的条件下,计算出的偏移量并不百分百准确