Redis zset实现多条件排序思路
#需求某应用用户排行规则:第一排行维度:用户积分;(积分越高,排行越靠前)第二排行维度:用户是否为会员;(积分相同时,会员排前面)第三排行维度:用户最近一次登录时间;(前两个维度值相同时,用户最后一次登录时间越近越靠前)以下是原始数据:用户积分是否会员最近一次登录时间戳A10011612754184997B20001612754184997C20011612754184997D400
#需求
某应用用户排行规则:
第一排行维度:用户积分;(积分越高,排行越靠前)
第二排行维度:用户是否为会员;(积分相同时,会员排前面)
第三排行维度:用户最近一次登录时间;(前两个维度值相同时,用户最后一次登录时间越近越靠前)
以下是原始数据:
用户 积分 是否会员 最近一次登录时间戳
A 100 1 1612754184997
B 200 0 1612754184997
C 200 1 1612754184997
D 400 0 1612754184997
E 200 1 1612754184998
期望的排序结果:
用户 积分 是否会员 最近一次登录时间戳
D 400 0 1612754184997
E 200 1 1612754184998
C 200 1 1612754184997
B 200 0 1612754184997
A 100 1 1612754184997
#使用redis的zset来实现三个维度排序的思路
#score 存储格式
Redis的zset的score是用 double 存储的,
一个 64 位浮点数存储时分为3段:
- 符号位(Sign) : 0代表正,1代表为负;(第一段,占1位)
- 指数位(Exponent):用于存储科学计数法中的指数数据;(第二段,占11位)
- 尾数部分(Mantissa):尾数部分;(第三段,占52位)
double 存储的有效数据是第三段的52位,超出会损失精度,由于标准规定小数点是在有效数字最前面,所以实际可以存储 53 位数字。
#通过“二进制拆分”,将三个维度合并生成score
利用二进制 64 位 long 分段存储各个维度
首先时间戳如果全存储就太长了,可以通过一些计算缩小一些,先忽略毫秒,然后和一个大数计算差值。
long ts = 1609430400000;//2021.1.1 0:0:0
int MAX_SECOND = 1893427200; // 2030.1.1 0:0:0
int sts = (MAX_SECOND - (int)(ts / 1000));// 283996800
这样时间就缩小到了300000000以内。
接下来进行划分,首位标志位不用,剩下63位,然后我划分高33位存分数,1位存标志位,最后29位存时间戳,存储结构是这样的:
如果要不损失精度,第一段可存储位数 = 53 - 1 - 29 = 23;
第一段最大值 = 2^33 = 8589934591,不损失精度 = 2^23 = 8388607;
第三段最大值 = 2^29 = 536870911;
使用时可以适当缩短第三段时间戳的长度,或者不追求时间戳一定精确的话,第一段分数可以超出不损失精度的长度,也只会损失一点时间戳的精度而已。
具体生成score的代码就不列举了。
#直接拆分十进制数
可以用二进制拆分当然也可以直接拆分十进制数,为了方便,还可以用小数划分维度,比如将积分放在整数位,标志位和时间戳放在小数位。
A 100.11612754184
B 200.01612754184
C 200.11612754184
D 400.01612754184
E 200.11612754184
不过这样不丢精度存储的分数比二进制拆分小。
#总结
数据量不大的情况下直接使用 读数据库 就可以了,比较方便。用 Redis 的时候,如果排序维度多,可以使用拆分二进制或十进制的方法存储,二进制的优点是存储的数比较大,而且可以用位运算,十进制的优点是计算简单,可读性比较好。各个维度的长度还可以做成配置项,这样就可以满足不同的业务需求了。
#参考
更多推荐
所有评论(0)