【golang/redis】redis中大数字自动转换成指数形式的处理

说在前面

  • go version:go1.14.1 windows/amd64
  • redis version:5.0.14

场景

  • 在使用redis的有序集合(sorted set)实现排行榜功能的时候,通常会对成员(member)的分数(score)进行一定的设计;例如最简单的分数榜,可以使用:
    u i n t 64 ( s c o r e ) < < 32 ∣ u i n t 64 ( 0 × F F F F F F F F − u i n t 32 ( c u r _ t i m e _ s t a m p ) ) uint64(score) << 32 | uint64(0\times FFFFFFFF-uint32(cur\_time\_stamp)) uint64(score)<<32uint64(0×FFFFFFFFuint32(cur_time_stamp))
    作为成员的分数(即前32位使用实际的分数,后32位使用最大uint32值减去当前的unix时间戳),这样,在相同分数下,先达成的成员将排在前面。

  • 这样的设计理论上其实是没有问题的;但是有序集合中的score的数据类型其实是double,详见

    Redis sorted sets use a double 64-bit floating point number to represent the score.
    In all the architectures we support, this is represented as an IEEE 754 floating point number, that is able to represent precisely integer numbers between -(2^53) and +(2^53) included. In more practical terms, all the integers between -9007199254740992 and 9007199254740992 are perfectly representable.
    Larger integers, or fractions, are internally represented in exponential form, so it is possible that you get only an approximation of the decimal number, or of the very big integer, that you set as score.

  • score超过一定大小后,就会转为指数形式;例如

    127.0.0.1:6379> zadd test_key 9007199254740991 a
    127.0.0.1:6379> zrange test_key 0 -1 WITHSCORES
    1) "a"
    2) "9007199254740991"
    // 这个时候还是很正常的
    127.0.0.1:6379> zadd test_key 9007199254740993 a
    127.0.0.1:6379> zrange test_key 0 -1 WITHSCORES
    1) "a"
    2) "9007199254740992"
    127.0.0.1:6379> zadd test_key 10007199254740993 a
    127.0.0.1:6379> zrange test_key 0 -1 WITHSCORES
    1) "a"
    2) "10007199254740992"
    // 只是+1,已经出现精度丢失了
    127.0.0.1:6379> zadd test_key 110007199254740993 a
    127.0.0.1:6379> zrange test_key 0 -1 WITHSCORES
    1) "a"
    2) "1.1000719925474099e+17"
    // 使用指数形式表示了
    
  • 在转成指数形式时候,如果还是将从redis中取出来的数据转成uint64,那么就会转换不过去。

  • 这个问题在score相对比较小的时候,不太会出现;但是如果score设计的时候更加细分,比如前32位使用16位的等级+16位的经验,那么就会很容易出现。

解决方式

  • score设计上避免出现问题

    • 例如将后面的时间戳数据换一种形式记录,比如分钟、小时、天,而不使用秒,从而降低bit位的占用,将更多的bit位放在score上
  • 其他

    • 暂时没有想到什么更好的方法;这本质上是uint64float64的问题,当数值大了之后,一定会有精度差异;
    • 即使使用类型安全的redis库,例如go-redis,也会面临这个问题。

你可能感兴趣的:(Go,golang,redis)