游戏项目中使用redis的Sorted-Sets 做排行榜,对于并列排行处理的机制为:因为redis存储的是double类型,为64位所以对于分数相同的情况,采用按照时间来排序。为此,现在的设计为:double  64位  , 拆分为:  高32位 , 低32位其中高32位存储真正的分数,低32位存储当时与某一个时刻的时间差(秒),这样就很好解决了并列排名,不过支持的最大分为32位,即一个int问题:
因为redis内部使用double类型,关于double的本身就有精度问题,在<<计算机操作系统>>中,double 64位, 其中63位是符号位,62-52位位阶码,51-0 位表示尾数。其中可以确定的说,在double中,只有低52位(尾数位部分)才是精确的所以,在我的redis排行榜设计中,其中低32位为时间戳,高32位为真正的分数,这样只有高32位中的低20位才是有效数位,因此,这样最多就能最大为支持100w的数据(因为高32位的高12位不能用,精度不准确)求助:
有什么更好的设计方案吗?实现分数相同,按照时间来排序?ps:为什么时间戳使用32位,因为 当前时间与2014-01-01 或  2080-01-01 的秒数差 正好是一个int值,也符合项目的周期

解决方案 »

  1.   

    redis的排序分值=1000*分数+时间差*0.1可否?
      

  2.   

    double精度问题在整型不发生运算时没有。
    你没有进行运算,只是使用了数据存储整形。不涉及精度问题。
      

  3.   


    但是我往redis中存入Long的最大值-1,即将一个long在client转型为double, 然后存到redis可是我从redis重新读取的话,读到的还是Long的最大值,而不是Long的最大值-1,我存入Long的最大值-10, 我得到的还是long得最大值
      

  4.   


    分值=1000*分数+时间差*0.1,   这个算出来不是小数吗?而且小数的话就存在精度问题
    1. 我不清楚你说的精度问题是什么,据我所知只有在小数位数比较多的时候,才会出现精度问题,你只要控制好数字的长度,应该是没有这类问题的,退一步说,不会在排序过程中造成排序的错误。2. 如果你实在要纠结小数的问题,分值=100000*分数+时间差 这样可否?我的意思是, 高位32位中,前10位有值的话,存到redis中,和从redis中取出来就有问题所以只有20位,即小于100w的话,我存入和读取出来就没有误差
      

  5.   

    jedis.zadd("zset", -1, "hello");
    System.out.println(jedis.zscore("zset", "hello"));
    jedis.zadd("zset", -10, "hello");
    System.out.println(jedis.zscore("zset", "hello"));
    输出:
    -1.0
    -10.0
    没有问题,你看下你的转换吧。
    但是我往redis中存入Long的最大值-1,即将一个long在client转型为double, 然后存到redis可是我从redis重新读取的话,读到的还是Long的最大值,而不是Long的最大值-1,我存入Long的最大值-10, 我得到的还是long得最大值
      

  6.   


    但是我往redis中存入Long的最大值-1,即将一个long在client转型为double, 然后存到redis可是我从redis重新读取的话,读到的还是Long的最大值,而不是Long的最大值-1,我存入Long的最大值-10, 我得到的还是long得最大值
    long sub = Long.MAX_VALUE - 2;
    System.out.println("long , sub 2 : " + sub);

    rankNode.zadd("just_test_long", sub, "t");
    System.out.println("long , max : " + Long.MAX_VALUE);

    Set<Tuple> zrevrangeWithScores = rankNode.zrevrangeWithScores("just_test_long", 0, -1);
    if (zrevrangeWithScores != null && zrevrangeWithScores.size() > 0) {
    for (Tuple tuple : zrevrangeWithScores) {
    System.out.println(tuple.getScore());
    System.out.println((long)tuple.getScore());
    }输出
    long , sub 2 : 9223372036854775805
    long , max : 9223372036854775807
    9.223372036854776E18
    9223372036854775807
      

  7.   

    但是我往redis中存入Long的最大值-1,即将一个long在client转型为double, 然后存到redis可是我从redis重新读取的话,读到的还是Long的最大值,而不是Long的最大值-1,我存入Long的最大值-10, 我得到的还是long得最大值
    long sub = Long.MAX_VALUE - 2;
    System.out.println("long , sub 2 : " + sub);

    rankNode.zadd("just_test_long", sub, "t");
    System.out.println("long , max : " + Long.MAX_VALUE);

    Set<Tuple> zrevrangeWithScores = rankNode.zrevrangeWithScores("just_test_long", 0, -1);
    if (zrevrangeWithScores != null && zrevrangeWithScores.size() > 0) {
    for (Tuple tuple : zrevrangeWithScores) {
    System.out.println(tuple.getScore());
    System.out.println((long)tuple.getScore());
    }输出
    long , sub 2 : 9223372036854775805
    long , max : 9223372036854775807
    9.223372036854776E18
    9223372036854775807

    Long表示的范围大于Double能表示的整形范围,传了maxLong -2进去,超了double的范围、
      

  8.   


    但是我往redis中存入Long的最大值-1,即将一个long在client转型为double, 然后存到redis可是我从redis重新读取的话,读到的还是Long的最大值,而不是Long的最大值-1,我存入Long的最大值-10, 我得到的还是long得最大值
    long sub = Long.MAX_VALUE - 2;
    System.out.println("long , sub 2 : " + sub);

    rankNode.zadd("just_test_long", sub, "t");
    System.out.println("long , max : " + Long.MAX_VALUE);

    Set<Tuple> zrevrangeWithScores = rankNode.zrevrangeWithScores("just_test_long", 0, -1);
    if (zrevrangeWithScores != null && zrevrangeWithScores.size() > 0) {
    for (Tuple tuple : zrevrangeWithScores) {
    System.out.println(tuple.getScore());
    System.out.println((long)tuple.getScore());
    }输出
    long , sub 2 : 9223372036854775805
    long , max : 9223372036854775807
    9.223372036854776E18
    9223372036854775807

    Long表示的范围大于Double能表示的整形范围,传了maxLong -2进去,超了double的范围、是的,其实不是超出表示范围,而是double的exp阶码位已经不再是0,所以这样重新算double就会有问题这就是为什么double的精度位有0-52位,超过100w的值转为double就会有误差而我的目标,我的希望的解决方案,求指导啊
      

  9.   


    但是我往redis中存入Long的最大值-1,即将一个long在client转型为double, 然后存到redis可是我从redis重新读取的话,读到的还是Long的最大值,而不是Long的最大值-1,我存入Long的最大值-10, 我得到的还是long得最大值
    long sub = Long.MAX_VALUE - 2;
    System.out.println("long , sub 2 : " + sub);

    rankNode.zadd("just_test_long", sub, "t");
    System.out.println("long , max : " + Long.MAX_VALUE);

    Set<Tuple> zrevrangeWithScores = rankNode.zrevrangeWithScores("just_test_long", 0, -1);
    if (zrevrangeWithScores != null && zrevrangeWithScores.size() > 0) {
    for (Tuple tuple : zrevrangeWithScores) {
    System.out.println(tuple.getScore());
    System.out.println((long)tuple.getScore());
    }输出
    long , sub 2 : 9223372036854775805
    long , max : 9223372036854775807
    9.223372036854776E18
    9223372036854775807

    Long表示的范围大于Double能表示的整形范围,传了maxLong -2进去,超了double的范围、是的,其实不是超出表示范围,而是double的exp阶码位已经不再是0,所以这样重新算double就会有问题这就是为什么double的精度位有0-52位,超过100w的值转为double就会有误差而我的目标,我的希望的解决方案,求指导啊
    如果用double超出部分,必然导致排序失效,所以超出部分是不能用的。
    为了突破上限的限制,分组是个解决办法。第一个zset前100W,第二个zset表示后100万,等等。
      

  10.   


    但是我往redis中存入Long的最大值-1,即将一个long在client转型为double, 然后存到redis可是我从redis重新读取的话,读到的还是Long的最大值,而不是Long的最大值-1,我存入Long的最大值-10, 我得到的还是long得最大值
    long sub = Long.MAX_VALUE - 2;
    System.out.println("long , sub 2 : " + sub);

    rankNode.zadd("just_test_long", sub, "t");
    System.out.println("long , max : " + Long.MAX_VALUE);

    Set<Tuple> zrevrangeWithScores = rankNode.zrevrangeWithScores("just_test_long", 0, -1);
    if (zrevrangeWithScores != null && zrevrangeWithScores.size() > 0) {
    for (Tuple tuple : zrevrangeWithScores) {
    System.out.println(tuple.getScore());
    System.out.println((long)tuple.getScore());
    }输出
    long , sub 2 : 9223372036854775805
    long , max : 9223372036854775807
    9.223372036854776E18
    9223372036854775807

    Long表示的范围大于Double能表示的整形范围,传了maxLong -2进去,超了double的范围、是的,其实不是超出表示范围,而是double的exp阶码位已经不再是0,所以这样重新算double就会有问题这就是为什么double的精度位有0-52位,超过100w的值转为double就会有误差而我的目标,我的希望的解决方案,求指导啊
    如果用double超出部分,必然导致排序失效,所以超出部分是不能用的。
    为了突破上限的限制,分组是个解决办法。第一个zset前100W,第二个zset表示后100万,等等。谢谢,你的方案的确可以接受,但是觉得有点麻烦因为一个sort set最多支持100w,但是如果有分数超多10亿的话,就需要1000key,觉得这样还是有点麻烦的。倘若我要获取排行榜前1000名(假设分数最大为21亿,int最大值),就有可能需要遍历2000key来组成这个排行榜
      

  11.   


    谢谢,你的方案的确可以接受,但是觉得有点麻烦因为一个sort set最多支持100w,但是如果有分数超多10亿的话,就需要1000key,觉得这样还是有点麻烦的。倘若我要获取排行榜前1000名(假设分数最大为21亿,int最大值),就有可能需要遍历2000key来组成这个排行榜
    插入数据时已经排列好了,不需要遍历。
    首先第一个zset是排列好的。
    现在插入一个值,如果 zset没满,直接放入。
    如果zset满了,那么与set最后一个值end比较,如果大于end,将end加入下一个zset,把新值插入。
    多个zset情况,end插入重复上述过程。这样,第一个zset就是前100w,第二个就是后100w,..
    取前1000,只需取第一个zset的前1000.
      

  12.   


    谢谢,你的方案的确可以接受,但是觉得有点麻烦因为一个sort set最多支持100w,但是如果有分数超多10亿的话,就需要1000key,觉得这样还是有点麻烦的。倘若我要获取排行榜前1000名(假设分数最大为21亿,int最大值),就有可能需要遍历2000key来组成这个排行榜
    插入数据时已经排列好了,不需要遍历。
    首先第一个zset是排列好的。
    现在插入一个值,如果 zset没满,直接放入。
    如果zset满了,那么与set最后一个值end比较,如果大于end,将end加入下一个zset,把新值插入。
    多个zset情况,end插入重复上述过程。这样,第一个zset就是前100w,第二个就是后100w,..
    取前1000,只需取第一个zset的前1000.

    很感谢你一直在关注这个问题但是我觉得你好像没有明白我的问题,应该是我没表达的很清楚我的意思是这样的:
    因为double和long的转换存在精度问题,上面的回帖中已经看到。所以当double 64位中的高12位不是0的话,把这个数转为long整型会有问题
    而我说的100w是表示,我用double的高32位,而且高12位也不用(因为存在精度问题),所以就剩下了20位,所以表示的最大的Score为100w举个例子:
    第一个zset:  key为 key1,    可以存入,1, 100, 1000,  10000这些都没有问题,而且也会排好序,但是不能存入超过100w的,不然的话,高12位不是0,就有精度问题.
    所以第二个zset的存储score的范围为; 0  -- 100w第二个zset:  key为 key2,   (相当于大于100w的值,)不过也只是可以存储1, 100 ,10000等等,不能超过100w,只不过我取出来啊之后,看到key的标识为key2,就表示第2个100w区间,取出来之后会加上100w,
    所以第二个zset的存储score的范围为; 100w  -- 200w以此类推,有第3个key     存储score的范围为; 200w  -- 300w
    以此类推,有第4个key     存储score的范围为; 300w  -- 400w
    等等等等所以当我要将一个score为10亿的存进去的话,就需要存储到key1000这个里面。
    这样就需要我来维护,现在已经使用了哪些key
    倘若我要便利的话,就需要所有的key全部遍历
    不知道这样的解释你是否能明白不过还是很感谢你一直的关注
      

  13.   

    分数超过100w,分两个zset,id : score, id : score:time。
    排名等于:score第一次出现的排名,加上相同分数time的排名。
      

  14.   

    如果对实时并发排序感兴趣,请关注这个项目(java):https://github.com/xuerong/hqrank,欢迎参与开发,pass:支持多字段排行