redis之特殊数据类型
bitmap
bitmap
即位图,是一种基于位运算的数据结构,用连续的二进制位描述某些业务,例如用户的签到状态等。redis
中的string是二进制安全的,故而使用string来描述这个位图。
相比其他数据结构,位图所占用的存储空间非常小,在上述的业务场景,使用位图更加高效。
Redis提供了SETBIT
、GETBIT
、BITCOUNT
、BITOP
四个命令,用于处理二进制数组
SETBIT
1 | SETBIT key offset value |
SETBIT
用于将二进制数组在偏移量位置处的二进制值设置为value,当key
不存在时,会自动生成一个新的字符串值。字符串会扩展以确保可以在offset位置上设置value值,当字符串扩展时,空白位置以0填充 offset>=0, 小于512M
注意对于较大的OFFSET
值,命令执行过程中的内存分配可能阻塞redis服务器。
1 | 123.122.10.231:6379> setbit bitmap 10086 1 |
setbit的执行过程
- 计算保存offset偏移量指定的二进制位至少需要多少个字节:
len=[offset/8] + 1
向下取整。 - 检查这个位图对应的
redisObject.ptr
指向的sdshar
的len字段
是否小于步骤1中计算的len值,如果小于,则将sds的长度扩展为len字节,并将所有扩展空间的二进制位设置为0。 - 计算offset偏移量指定的二进制位保存在哪一个字节:
byte=offset/8
。 - 计算offset偏移量指定的二进制位在byte个字节中的第几个二进制位:
bit=offser%8 + 1
。 - 根据计算得出的byte和bit定位到二进制数组中offset的位置,将旧值存储在oldvalue中,并将其值修改为value指定的值。
- 最后,向客户端返回oldvalue的值。
上述计算过程均可以在常数时间内完成,故而其时间复杂度为O(1)
GETBIT
1 | GETBIT key offset |
获取二进制数组key指定偏移量offset处的值。当offset比字符串值的长度大或者key不存在时,返回0。
1 | 123.122.10.231:6379> getbit bitmap 10086 |
getbit的执行过程
和setbit
类似,核心就是计算byte
和bit
。命令执行过程如下:
- 计算byte
- 计算bit
- 根据byte 和 bit定位到二进制数组中偏移量位置,读取该位置上的值
上述过程的时间复杂度也为O(1)
BITCOUNT
此命令经常被用于统计用户上线次数等业务:模式:使用 bitmap 实现用户上线次数统计
1 | BITCOUNT key [start] [end] |
计算给定字符串key中,被设置为1的比特位数量;也可以只计算start到stop指定的字节范围的二进制数组。
1 | 123.122.10.231:6379> getbit bitmap 10086 |
bitcount实现原理
在数学上,统计一个二进制数组中,非0二进制位的数量,被称作计算汉明重量
,该算法通过一系列位移和位运算操作,可以在常数时间内计算多个字节的汉明重量,并且不需要使用任何额外的内存。
参见variable-precision swar
算法
redis使用查表法+swar实现:
redis中采用的实现源码如下:
1 | /* Count number of bits set in the binary array pointed by 's' and long |
BITOP
1 | BITOP operation destkey key [key …] |
对一个或多个二进制数组key进行元操作,并将结果保存到二进制数组destkey中。其中operation
可以是and
、or
、not
、xor
中的任意一种,对应与或非、异或操作。
hyperloglog
hyperloglog
并非redis
特有,它是一种基于统计的算法,用以计算大数据场景下统计基数的算法:
目前用于基数统计的算法包括:
- linear counting(LC): 早期的基数估算算法,时间复杂度是O(N)。
- LogLog counting(LLC): 相比LC更节省内存,空间复杂度为O(log(log(N)))。
- hyperloglog counting(HLLC): 是基于LLC的优化,在相同的空间复杂度下,比LLC的误差要小。
其优点在于,在输入的元素数量或者所占空间非常大时,计算基数的空间开销非常少。每个hyperloglog
键只需要花费12KB的内存,就可以计算2^64
个不同元素的基数。
另外,它只会根据输入元素计算基数,不会存储元素本身,所以不能返回各个元素。
常用命令
1 | // 将任意数量的元素添加到指定的hyperloglog中 |
PFADD
命令的返回取决于执行命令后,hyperloglog
估计的基数发生变化时,则返回1,否则返回0。命令不会一次性分配12K内存。[具体的扩展原则见源码]
如果该key值不存在,则先创建,再执行此命令。PFCOUNT
返回的基数并不是精确值,而是带有一个0.81%
标准错误的近似值。PFMERGE
,将多个hyperloglog
合并为一个,合并后的htperloglog
的基数接近于所有输入的hyperloglog
可见集合的并集。hyperloglog
的源码实现主要参考 P. Flajolet, Éric Fusy, O. Gandouet, and F. Meunier. Hyperloglog: The analysis of a near-optimal cardinality estimation algorithm
实际操作如下:
1 | 123.122.10.231:6379> PFADD database "Redis" "MongoDB" "MySQL" |
使用场景
- 由于不需要保存数据内容+计算基数特别快,所以非常适合计算日活、月活数据
geo
redis 3.2版本开始提供了geo(地理信息定位)
功能,支持存储地理位置信息。
常用命令
1 | // 将给定的空间元素加到指定的键中 [longitude latitude member], 这些元素会以有序集合的形式被存储在键中 |
GEOADD
命令以标准(x,y)的形式接收输入参数,经度在前,纬度在后。另外,它能够记录的坐标是有限的,非常接近两级的区域无法被索引,精确的坐标限制为:
- 有效的经度介于 -180 度至 180 度之间。
- 有效的纬度介于 -85.05112878 度至 85.05112878 度之间。
超出上述范围则会返回错误。
GEODIST
命令中默认的单位为m,可选参数有:
- m,即米。
- km,即千米。
- mi, 即英里。
- ft,即英尺。
geohash
: 对于精度维度而言,geohash
会将其编码为N位的二进制数,实际上就是通过N次分区实现。
对于某一位置元素,分别对其经度和维度进行编码,得到的两个二进制数,偶数位放置经度值,奇数为放置维度,最后使用base32进行编码即得到最后的输出。
这里要注意的是,geohash算法使用的是peano空间填充曲线
,这种曲线会产生突变,会导致编码虽然相似,但其距离可能相差很大的问题。在实际应用中,需要先筛选相似geohash
编码相似的点,然后计算实际距离。
1 | 123.122.228.34:6379> zrange citys 0 -1 withscores |
geo
中使用某种编码将经纬度设置为 对应 memeber 的 score。
故而,GEOADD citys 116.41667 39.91667 "beijing"
命令等价于:ZADD citys 4069885649163649 beijing
。
实际操作:
1 | 123.122.10.231:6379> GEOADD citys 116.41667 39.91667 "beijing" 121.43333 34.50000 "shanghai" 113.23333 23.16667 "guangzhou" 114.06667 22.61667 "shenzhen" 113.51667 22.30000 "zhuhai" |
适用场景
适用于和位置相关的业务,例如附近的人、共享单车等。
引用
1. 详解redis bitmap
2. redis hyperloglog实现
3. redis geo命令参考
4. GeoHash核心原理解析