实现思路
0(最高位不用)
|0000000 00000000 0000000(22bit表示积分)
|0
00000000 00000000 00000000 00000000 00000000(41bit表示时间戳)
yyy-MM-dd 23:59:59对应的时间戳与用户积分更新时间的时间戳的差值,这个值会随着时间的推移而变小,而且不会出现负数的情况,刚好能够达到目的。
实现关键代码
// periodEndTimestamp: 当前周期结束时间的时间戳
// 需确保point不会超过22bit所能表示的数值:2097151
private static long toScore(int point, long periodEndTimestamp) {
long score = 0L;
score = (score | point) << 41;
score = score | (periodEndTimestamp - TimestampUtils.currentTimeMillis());
return score;
}
private static int getPoint(long score) {
return (int) (score >> 41);
}
@Override
public void updateRanking(Integer periodId, Long accountId, Integer addPoint) {
String key = String.format(RankingCacheKeys.REALTIME_POINT_RANKING_KEY, periodId);
Double score = redisTemplate.opsForZSet().score(key, String.valueOf(accountId));
score = (score == null) ? 0d : score;
int curPoint = getPoint(score.longValue());
long newScore = toScore(curPoint + addPoint, getCurPeriodEndDateTimestamp(periodId));
redisTemplate.opsForZSet().add(key, String.valueOf(accountId), newScore);
}
总结
1.将分值score的8字节拆分使用,最高位不用,其余一部分存储实际分值,一部分存储时间戳;
2.先按积分排序,再按时间排序,所以需要高位存储积分,低位存储时间戳,这样才能保证积分越高对应score越大;
3.同分值情况下按时间升序排序,必然让达到当前积分时间最早的score越大;
4.避免积分或者时间戳溢出,如8bit最大可以表示255,如果积分最大可以超过255,那么就需要考虑给积分加到9位…
5.由于每次更新用户积分都需要重新计算score,不能使用ZSet的原子性操作命令,因此可能存在并发数据一致性问题,这点需要考虑。
关于实现排行榜用到ZSet的几个命令
ZREVRANGEBYSCORE key min max offset count
ZSCORE key member
ZCARD key
ZREVRANK key member

文章转载自Java艺术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




