除了五种基本的Redis结构【Redis从入门到放弃系列 四】数据结构应用场景,我们这里总共聊三种:Bitmaps,HyperLogLog以及Geo三种数据模型。
Bitmaps数据模型
计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如【是】这样一个中文字符占两个字节, 实际在计算机存储时将其用二进制表示,也就是16位来表示,所以说我们如果只是想标明状态的话其实直接用二进制位的01也可以,0代表否,1代表是,那么可以大大节约内存空间,需要注意以下两点:
- Bitmaps本身不是一种数据结构, 实际上它就是字符串 , 但是它可以对字符串的位进行操作。这也是我为啥把这小节的标题设置为数据模型的原因。
- Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
Bitmaps的优点在于节省内存空间,缺点就是需要找到位移位置,确定位移值,需要计算。以空间换时间。
举个例子,如果我要统计最近7天的用户访问量有多少,就可以存储一个bitmaps作为统计,每个二进制位用来标识一个用户的userid,访问了就设置为1,没有访问就设置为0。
设置bitmaps位的值: setbit key offset value 获取bitmaps位的值: getbit key offset
以下就是对各个用户是否访问站点的一个统计。分别对2020-10-30以及2020-10-31登录的用户进行统计
127.0.0.1:6379> setbit 20201030 0 1 (integer) 0 127.0.0.1:6379> setbit 20201030 3 1 (integer) 0 127.0.0.1:6379> setbit 20201030 6 1 (integer) 0 127.0.0.1:6379> setbit 20201031 3 1 (integer) 0 127.0.0.1:6379> setbit 20201031 4 1 (integer) 0 127.0.0.1:6379> setbit 20201031 6 1 (integer) 0 127.0.0.1:6379> getbit 20201030 0 (integer) 1 127.0.0.1:6379> getbit 20201030 1 (integer) 0 127.0.0.1:6379> 127.0.0.1:6379> setbit tml 100000000 1 (integer) 0 127.0.0.1:6379> setbit tml 100000000000000000 1 (error) ERR bit offset is not an integer or out of range 127.0.0.1:6379>
需要注意如下几点使用:
- 很多应用的用户id以一个指定数字(例如10000) 开头, 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字。
- 在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞
当然我们现在设置好之后是需要统计结果的,这个时候就需要bitmaps的其它操作了。
bitcount统计
如果我们想要查询每天内有多少用户访问了网站,可以进行操作,如果是每月或者每年,只需要把所有的天按照时间范围做或操作就行。
获取Bitmaps指定范围值为1的个数 bitcount key [start][end]
可以获取指定范围访问1的个数
127.0.0.1:6379> setbit 20201030 0 1 (integer) 0 127.0.0.1:6379> setbit 20201030 3 1 (integer) 0 127.0.0.1:6379> setbit 20201030 6 1 (integer) 0 127.0.0.1:6379> setbit 20201031 3 1 (integer) 0 127.0.0.1:6379> setbit 20201031 4 1 (integer) 0 127.0.0.1:6379> setbit 20201031 6 1 (integer) 0 127.0.0.1:6379> bitcount 20201030 (integer) 3 127.0.0.1:6379> bitcount 20201030 0 2 (integer) 3 127.0.0.1:6379> setbit 20201030 100000 1 (integer) 0 127.0.0.1:6379> bitcount 20201030 (integer) 4 127.0.0.1:6379> bitcount 20201030 0 1 (integer) 3 127.0.0.1:6379>
这里需要注意:就是bitcount命令,在使用start,end的时候一定要注意,setbit和getbit命令操作的是bit,但是bitcount用的是byte来计算位数,两者差了8倍,因此这点很容易采坑,例如我查询0-2字节之间的,可以查到bit位为0,3,6的三个全部的,但是查100000 就查不到。
复合运算
那我如果想查询昨天【30】和今天【31】两天总的用户数有多少可以这么操作,结果会被保存在destkey里
bitop operation destkey key [key ...] //operation 可以为 and、or、not、异或
例如统计这两天的
127.0.0.1:6379> bitop or 30-31 20201030 20201031 (integer) 12501 127.0.0.1:6379> bitcount 30-31 (integer) 5 127.0.0.1:6379>
可以看的出合并的bit位为:0、3、4、6、100000 这几个比特位。合并计算的返回结果不需要关心。
HyperLogLog数据结构
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。以数据集 {1, 3, 4, 6, 4, 6, 8}为例, 那么这个数据集的基数集为 {1, 3, 4 ,6, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
可以这么理解HyperLogLog:理解为bitmaps进行or操作后进行bitcount统计。还是bitmaps的场景,统计登录用户,这里就很简单:
127.0.0.1:6379> pfadd 20201030 0 (integer) 1 127.0.0.1:6379> pfadd 20201030 3 (integer) 1 127.0.0.1:6379> pfadd 20201030 3 (integer) 0 127.0.0.1:6379> pfadd 20201030 6 (integer) 1 127.0.0.1:6379> pfadd 20201031 3 (integer) 1 127.0.0.1:6379> pfadd 20201031 4 (integer) 1 127.0.0.1:6379> pfadd 20201031 6 (integer) 1 127.0.0.1:6379> pfcount 20201030 (integer) 3 127.0.0.1:6379> pfcount 20201031 (integer) 3 127.0.0.1:6379> pfmerge 30-31 20201030 20201031 OK 127.0.0.1:6379> pfcount 30-31 (integer) 4 127.0.0.1:6379>
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存【使用上限】,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比;和bitmap不同的是,HyperLogLog只是记录基数【4】,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。需要注意HyperLogLog 的误差是0.81%
GEO数据结构
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,主要使用经纬度计算。
主要使用的操作如下几种,需要注意的是geo只计算水平位置,不附加高度这些信息。
- geoadd:添加地理位置的坐标【geoadd key longitude latitude member [longitude latitude member …]】
- geopos:获取地理位置的坐标。【geopos key member [member …]】
- geodist:计算两个位置之间的距离。【geodist key member1 member2 [m|km|ft|mi]】,其中参数m :米,默认单位、km :千米;mi :英里;ft :英尺。
- georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
- georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
- geohash:返回一个或多个位置对象的 geohash 值。
接下来实际操作下。
计算两点之间距离
我想看看百度地图二环的长算的准不准,百度地图显示复兴门地铁站到西直门地铁站距离为6.6km
我们获取下复兴门地铁站经纬度
首先我们录入两个地铁站的经纬度:
127.0.0.1:6379> geoadd fuxin-jianguo 116.3571631 39.90723718 fuxing 116.4352424 39.90860684 jianguo (integer) 2 127.0.0.1:6379> geopos fuxin-jianguo fuxing jianguo 1) 1) "116.35716408491134644" 2) "39.90723672058083338" 2) 1) "116.43524318933486938" 2) "39.90860800472803049" 127.0.0.1:6379>
录入完成后我们来进行计算:
127.0.0.1:6379> geodist fuxin-jianguo fuxing jianguo km "6.6634" 127.0.0.1:6379>
结果为6.6634km,与百度提供的6.6km还是比较接近,所以说比较准确。
根据坐标求范围内数据【中心不固定】
georadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素其实比较好理解,就是当我们自己是个点在不停移动的时候用这个比较合适,例如查询我周边的外卖,而我是有可能移动的,所以坐标是不停的在变的。
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
- m :米,默认单位。km :千米。mi :英里。ft :英尺。
- WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
- WITHCOORD: 将位置元素的经度和维度也一并返回。
- WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
- COUNT 限定返回的记录数。
- ASC: 查找结果根据距离从近到远排序。
- DESC: 查找结果根据从远到近排序。
那我来查一下指定坐标点周边具体范围内的坐标点:
127.0.0.1:6379> GEORADIUS fuxin-jianguo 116 39 1000 km withdist 1) 1) "fuxing" 2) "105.4674" 2) 1) "jianguo" 2) "107.7519" 127.0.0.1:6379>
感觉这个的具体应用就是我附近的美食,我是不停移动的。
根据点求范围内数据【中心固定】
georadiusbymember 和 georadius 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。
georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
参数与georadius 类似。
127.0.0.1:6379> GEORADIUSBYMEMBER fuxin-jianguo fuxing 7 km 1) "fuxing" 2) "jianguo" 127.0.0.1:6379> GEORADIUSBYMEMBER fuxin-jianguo fuxing 5 km 1) "fuxing" 127.0.0.1:6379>
感觉这个的具体应用就是我家附近的美食,我家是固定不变的。