redis zset score精度问题

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

一、score的数据类型

   在计算机中,字符都是可以比较大小的,那么score的数据类型是不是只要是字符型就可以的呢?答案是否定的。如下图,当我们使用非数字类型的时候会提示“(error)ERR 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精度而引起业务问题。

Redis 中,集合(Set)、哈希表(Hash)、有序集合(ZSet)都有过期时间设置的功能。这里我们重点介绍如何为 Redis 的有序集合(ZSet)中的每个元素(值)设置过期时间。 ### 设置 ZSet 元素过期时间 在 Redis 中,ZSet 是一个可以为集合中的每个成员分配一个分数(score)的有序集合。这个分数决定了成员在集合中的顺序。为了给 ZSet 的每个元素设置过期时间,你可以使用 `EXPIRE` 或者 `PEXPIRE` 命令针对特定元素进行设置。 #### 使用 EXPIRE `EXPIRE key seconds [nsecs]` 命令用于设置键的过期时间。这里的 `key` 是指你想要设置过期时间的 ZSet 的名称,`seconds` 表示过期时间为多少秒,而 `[nsecs]` 可选部分表示过期时间精确到毫秒的时间戳。 例如,如果你有一个名为 `myzset` 的 ZSet,并希望将所有元素的过期时间设置为 3600 秒(即一小时),则可以遍历 ZSet 所有的元素并分别使用 `EXPIRE` 命令设置过期时间: ```bash for element in $(redis-cli zrange myzset 0 -1 withscores); do value=$(echo $element | cut -d ' ' -f1) score=$(echo $element | cut -d ' ' -f2) redis-cli expire myzset:$value $((score + 3600)) done ``` 在这个例子中,我们首先获取 `myzset` 中所有的元素及其对应的分数组成的字符串列表,然后通过管道将每一对元素与其分数分割开,并使用 `EXPIRE` 命令设置对应元素的过期时间。 #### 使用 PEXPIRE `PEXPIRE key milliseconds [nanoseconds]` 类似于 `EXPIRE`,但是以毫秒为单位指定过期时间。这对于高精度的需求更为实用。 ### 示例脚本 下面是一个简化的 Bash 脚本来实现为所有元素设置过期时间的例子: ```bash #!/bin/bash # 获取 ZSet 中的所有元素及其分数 elements=$(redis-cli zrange myzset 0 -1 withscores) # 分割出每个元素及其分数 IFS=$'\n' for line in $elements; do value=$(echo "$line" | cut -d ' ' -f1) score=$(echo "$line" | cut -d ' ' -f2) # 将元素名转换为正确的 Redis 键(假设分数作为键的一部分) redis-cli pexpire myzset:$value $((score * 1000)) # 注意这里使用毫秒转为纳秒 done echo "所有元素的过期时间已设置。" ``` ### 注意事项 - 确保在执行批量设置过期时间之前先备份数据,以防万一出现问题导致数据丢失。 - 对于非常大的集合,考虑使用适当的并发机制或分批处理来避免单点瓶颈。 - 谨慎管理过期时间,避免误删有效数据。 --- ### 相关问题: 1. 如何在批量操作中高效地设置 Redis ZSet 中大量元素的过期时间? 2. Redis 过期策略是如何工作的,对性能有何影响? 3. 在高并发环境下,应该如何优化 Redis ZSet 中元素的过期时间设置过程?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值