Redis Zset Score精度问题

本文探讨了Redis ZSet中score数据精度的问题,指出score必须为浮点型,通常使用double,但存在精度损失。在Java中,double类型的精度问题可能导致错误的结果。建议在商业计算中使用BigDecimal。实验表明,时间戳作为score不会引发精度问题,但超过17位的数字可能会导致精度丢失。因此,应谨慎处理高精度需求,必要时采取预处理措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在开测项目服务端的开发中,redis的zset是常用的数据结构。因为它元素不重复且每个元素都有一个分数的特点,经常作为有序队列和元素排序来使用,排序的方式自然是通过每个元素的score的大小。

score的数据类型

在计算机中,字符都是可以比较大小的,那么score的数据类型是不是只要是字符型就可以的呢?答案是否定的。如下图,当我们使用非数字类型的时候会提示“(error) value is not a valid float”的错误信息。同时这个错误信息告诉我们,score类型是浮点型的数据。

既然是浮点型的数据都可以,那么我们使用int,long,float,double等数字类型,当然都是可以的。但是浮点型数据自然都是有精度的,且会影响我们的业务开发。

Ps: 在相关的参考资料中有说是double类型的,有说是数字转成字符串类型存到redis中,查询时返回是字符串类型,从结果上观察都是成立的。但是在Jedis驱动中入参和出参使用的都是double数据类型。

java的数据精度与redis的数据精度

先看一个有趣的例子,如下图,使用java代码,声明两个double的变量a和b,值都是27个数字,a的末位数字比b的末位数字大1,其他位置的数字相同,a是大于b的,但是代码输出的结果是a等于b,a和b也变成了18个有效数字的科学计数。

除了上面的问题,在项目中用到double类型数据四则运算时,经常出现精度丢失的问题,总是在一个正确的结果左右偏0.0000**1。

其输出结果如下:

在商业型软件中,这种精度问题,会有很大的影响。在《Effective Java》这本书提供了解决方法,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。但是在Jedis驱动中,使用的是double作为形参的,我们执行像zset中插入3.14的操作,代码如下。

我们通过redis-cli去查看的时候,结果显示的是3.1400000000000001,但是java读取出来的时候显示的是3.14。

通过debug,我们发现,并不是数据插入时导致的精度问题,而是从redis中读取的时候出现的问题,由此我们可以猜测是redis自身的浮点型精度的问题。

我们可以通过redis-cli来验证一下:

果然如前面猜测的一样,redis自身的浮点型精度是有问题的,并且做zincrby的时候也是有精度问题的。

开测中使用的一个例子

笔者在项目中,目前没有遇到因为精度而导致的问题,只是在开测的服务端开发中,会有使用时间戳作为score来进行任务的排序,时间戳由14位数字组成的长整型,向上转型成double,存入redis中会变成成科学计数法,为了避免精度丢失的问题,才进行了相关实验。

经过实验,当zset中的长整型数字位数超过17位的时候才会出现精度丢失的问题,如下图。

此结果与java的double类型保持一致,所以使用时间戳作为分数不会造成精度丢失的问题,同时,我们应该避免使用超过17位的数字作为分数。如果不得不使用,笔者认为一般是数据量特别大、使用uuid,或者是精度要求比较高,这些情况可以通过一些算法做预处理,只要达到预期的效果就行。

小结

本文对redis的zset的score的数据精度问题做了简单的分析和总结,希望能在以后的开发中,能够避免因为score精度而引起业务问题。

哈喽,喜欢这篇文章的话烦请点个赞哦!万分感谢(*^▽^*)PS:有问题可以联系我们哦v ceshiren001
更多技术文章

<think>嗯,用户之前问了一个关于Java集合的问题,现在又问RedisZSET数据结构。我需要先回顾一下之前的对话,看看是否有联系。之前的回答解释了为什么修改Integer列表元素不生效,涉及到了不可变对象和集合操作的问题。现在用户转向Redis的数据结构,可能是在学习不同的存储结构,或者在做项目时需要考虑数据存储方式。 首先,用户可能对Redis的基础有一定了解,但想深入理解ZSET。需要先介绍ZSET是什么,它的特点,比如有序、唯一成员、分数排序等。然后要解释底层实现,比如跳跃表和哈希表的结合,这样用户能明白为什么ZSET高效。 接下来,用户可能需要知道ZSET的常用命令,比如ZADD、ZRANGE、ZREVRANGE等,这些是操作ZSET的基本命令。给出示例能帮助用户更好地理解如何使用。另外,应用场景也很重要,用户可能想知道在实际开发中哪里会用到ZSET,比如排行榜、延时队列、范围查询等,这部分需要结合实际例子说明。 还要注意用户可能的进阶问题,比如为什么选择跳跃表和哈希表,或者ZSET的时间复杂度,这样可以在解释时提前涵盖这些点。此外,用户可能对ZSET和其他数据结构的区别感兴趣,比如和SET的区别,或者和列表的区别,可以简要提及。 最后,确保语言通俗易懂,避免过多技术术语,但又要准确。可能需要检查是否有错误,比如ZSET是否真的使用两种数据结构,或者命令的正确写法。另外,用户之前的问题涉及到修改集合元素,这里可以提一下ZSET的更新操作是否直接,比如更新分数是否容易,这也是实际应用中的常见需求。 总结下来,回答的结构应该是先定义ZSET,然后底层结构,常用命令,应用场景,最后注意事项。这样用户能逐步深入了解,并且能应用到实际项目中。同时,要确保信息准确,示例正确,帮助用户形成清晰的理解。</think>Redis 的 **ZSET**(有序集合,Sorted Set)是一种结合了 **Set**(成员唯一)和 **Sorted**(按分数排序)特性的数据结构。它通过为每个成员分配一个 `score`(分数值)来实现排序,是 Redis 中非常实用且高效的数据结构之一。 --- ### **核心特性** 1. **唯一成员,可重复分数** - 每个成员(`member`)唯一,但多个成员可以拥有相同的 `score`。 - 例如:`ZADD rank 90 "Alice" 90 "Bob"`,允许两人分数相同。 2. **按分数排序** - 默认按 `score` **升序**排列,支持范围查询(如获取排名前 N 的成员)。 - 可以通过 `ZREVRANGE` 实现降序排序。 3. **高性能操作** - 插入、删除、查询的时间复杂度为 **O(log N)**,基于底层**跳跃表(Skip List)**实现。 --- ### **底层实现** ZSET 内部使用两种结构组合实现: 1. **跳跃表(Skip List)** - 支持快速的范围查询(如 `ZRANGE`)和排序。 2. **哈希表(Hash Table)** - 以 `member` 为键,`score` 为值,实现 **O(1)** 复杂度的单个成员查询(如 `ZSCORE`)。 这种组合兼顾了**范围操作的高效性**和**单成员查询的快速性**。 --- ### **常用命令** | 命令 | 作用 | 示例 | |------|------|------| | `ZADD` | 添加成员 | `ZADD key score member [score2 member2 ...]` | | `ZRANGE` | 按升序返回成员 | `ZRANGE key start stop [WITHSCORES]` | | `ZREVRANGE` | 按降序返回成员 | `ZREVRANGE key start stop [WITHSCORES]` | | `ZRANK` | 获取成员升序排名 | `ZRANK key member` | | `ZSCORE` | 获取成员分数 | `ZSCORE key member` | | `ZREM` | 删除成员 | `ZREM key member` | | `ZCOUNT` | 统计分数区间内的成员数 | `ZCOUNT key min_score max_score` | --- ### **典型应用场景** 1. **排行榜** - 例如游戏积分排名,通过 `ZREVRANGE` 直接获取 Top N 玩家。 2. **延时队列** - 将任务到期时间作为 `score`,用 `ZRANGEBYSCORE` 轮询到期任务。 3. **范围查询** - 如按时间范围筛选数据(将时间戳作为 `score`)。 4. **权重调度** - 用 `score` 表示优先级,按权重顺序处理任务。 --- ### **示例:实现排行榜** ```bash # 添加玩家分数 ZADD rank 95 "Alice" 88 "Bob" 100 "Charlie" # 获取前2名(降序) ZREVRANGE rank 0 1 WITHSCORES # 输出:1) "Charlie" 2) "100" 3) "Alice" 4) "95" # 更新 Alice 的分数(直接覆盖) ZADD rank 99 "Alice" # 查询 Alice 的排名(升序排名从0开始) ZRANK rank "Alice" # 返回1(Bob=88, Alice=99, Charlie=100) ``` --- ### **注意事项** 1. **分数范围** - `score` 是双精度浮点数,但 Redis 实际存储为 64 位整数(可能导致精度丢失)。 2. **性能** - 超大 ZSET(如百万级成员)的批量操作可能较慢,需合理分片。 3. **内存优化** - 若仅需排序,可用 `ZSET`;若需要更复杂操作,可能需结合其他数据结构(如 Hash)。 ZSET 凭借其排序能力和高效操作,成为 Redis 中解决**有序且唯一**场景的利器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值