深入浅出Redis(十一):Redis四种高级数据结构:Geosptial、Hypeloglog、Bitmap、Bloom Filter布隆过滤器

引言

Redis提供丰富的数据结构来解决各种场景下的问题,前段时间的一篇文章深入浅出Redis(一):对象与数据结构已经深入浅出的说明Redis中的常用基础对象与数据结构

本篇文章将作为那篇文章的补充,深入浅出的解析另外四种数据结构:Geospatial、Hyperloglog、Bitmap以及Bloom Filter布隆过滤器

Geospatial

Geospatial 是一种能够解决地理空间相关场景下的数据结构,它提供的命令能够容易实现两地距离、附近的人等功能

Geospatial 使用GeoHash算法,底层实现使用zset对象 因此也可以使用Zset命令

geoadd 添加

geoadd key 经度 纬度 名称

将指定的地理空间位置(纬度、经度、名称)添加到指定的key中(可添加多个)

有效的经度从-180度到180度

有效的纬度从-85.05112878度到85.05112878度

超出上述经纬度会报错

 127.0.0.1:6379> geoadd china:city 116.23 40.22 beijing 
 (integer) 1
 127.0.0.1:6379> geoadd china:city 121.48 31.40 shanghai 113.88 22.55 shenzhen 104.10 30.65 chendu
 (integer) 3

geopos 获取

geopos key 成员名...

从key里返回所有给定位置元素的位置(经度和纬度)

 127.0.0.1:6379> geopos china:city beijing shenzhen
 1) 1) "116.23000055551528931"
    2) "40.2200010338739844"
 2) 1) "113.87999922037124634"
    2) "22.5500010475923105"

geodist 位置间的距离

geodist key member1 member2 [unit]

返回两个给定位置之间的距离

单位:(默认米):m 表示单位为米,km表示单位为千米,mi 表示单位为英里,ft 表示单位为英尺

 127.0.0.1:6379> geodist china:city beijing shenzhen
 "1977782.5112"
 127.0.0.1:6379> geodist china:city beijing shenzhen km
 "1977.7825"

georadius 以某点为中心,查找周围x半径之内的member(附近的人)

georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [count]

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素

withcoord:获得经纬度坐标

withdist:找到的元素距离中心点的距离

count:限制查到的个数

 #以经纬度 110,30为中心,半径1500km的范围内的成员
 127.0.0.1:6379> georadius china:city 110 30 1500 km 
 1) "chendu"
 2) "shenzhen"
 3) "shanghai"
 4) "beijing"
 #以经纬度 110,30为中心,半径1500km的范围内的成员 列出成员坐标经纬度
 127.0.0.1:6379> georadius china:city 110 30 1500 km withcoord
 1) 1) "chendu"
    2) 1) "104.09999996423721313"
       2) "30.6499990746355806"
 2) 1) "shenzhen"
    2) 1) "113.87999922037124634"
       2) "22.5500010475923105"
 3) 1) "shanghai"
    2) 1) "121.48000091314315796"
       2) "31.40000025319353938"
 4) 1) "beijing"
    2) 1) "116.23000055551528931"
       2) "40.2200010338739844"
  #以经纬度 110,30为中心,半径1500km的范围内的成员 列出成员坐标经纬度和成员到中心直线距离
 127.0.0.1:6379> georadius china:city 110 30 1500 km withcoord withdist
 1) 1) "chendu"
    2) "570.9717"
    3) 1) "104.09999996423721313"
       2) "30.6499990746355806"
 2) 1) "shenzhen"
    2) "914.3335"
    3) 1) "113.87999922037124634"
       2) "22.5500010475923105"
 3) 1) "shanghai"
    2) "1108.3830"
    3) 1) "121.48000091314315796"
       2) "31.40000025319353938"
 4) 1) "beijing"
    2) "1269.3568"
    3) 1) "116.23000055551528931"
       2) "40.2200010338739844"
 #以经纬度 110,30为中心,半径1500km的范围内的成员 列出成员坐标经纬度 限制只查询一个(直线距离最近的)      
 127.0.0.1:6379> georadius china:city 110 30 1500 km withcoord count 1
 1) 1) "chendu"
    2) 1) "104.09999996423721313"
       2) "30.6499990746355806"
  #以经纬度 110,30为中心,半径1500km的范围内的成员 列出成员坐标经纬度 限制只查询俩个(直线距离最近的)  
 127.0.0.1:6379> georadius china:city 110 30 1500 km withcoord count 2
 1) 1) "chendu"
    2) 1) "104.09999996423721313"
       2) "30.6499990746355806"
 2) 1) "shenzhen"
    2) 1) "113.87999922037124634"
       2) "22.5500010475923105"

georadiusbymember (与georadius类似,它是以member为中心的)

georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [count]

这个命令和 georadius命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的

 #以beijing为中心 1500km为半径 查找的成员(会查到自己)
 127.0.0.1:6379> georadiusbymember china:city beijing 1500 km
 1) "shanghai"
 2) "beijing"

geohash

geohash key member...

该命令将返回11个字符的Geohash字符串

 # 深圳与成都
 127.0.0.1:6379> geohash china:city shenzhen chendu
 1) "ws0br3hgk20"
 2) "wm6n2gem1v0"
 ​
 # 东莞与深圳
 127.0.0.1:6379> geohash china:city dongguan shenzhen
 1) "ws0fuqwjpn0"
 2) "ws0br3hgk20"

将二维经纬度转化为一维字符串,如果俩个成员距离越近,字符串就会越相似

通过Zset指令来操作geo(因为geo底层实现原理就是Zset)

 127.0.0.1:6379> zrange china:city 0 -1
 1) "chendu"
 2) "shenzhen"
 3) "dongguan"
 4) "shanghai"
 5) "beijing"
 127.0.0.1:6379> zrem china:city dongguan
 (integer) 1
 127.0.0.1:6379> zrange china:city 0 -1
 1) "chendu"
 2) "shenzhen"
 3) "shanghai"
 4) "beijing"

Hyperloglog

在数据量特别大的情况下,想要统计数量可以选择用哈希表实现的set存储(能够去重),但是哈希表是空间换时间的数据结构,这种情况下会浪费大量空间

hyperloglog使用基数统计算法,用固定且少量的空间,能够实现统计计数,但缺点是有0.81%的错误率

A={1,2,4,5,6,1}

B={1,2,4,5,6}

基数=5(不重复的元素)

命令

  • 存: pfadd key element...将指定元素添加到hyperloglog
  • 读: pfcount key...统计hyperloglog中的基数数量(一个hyperloglog时)或并集数量(多个hyperloglog时)
  • 合并:pfmerge destkey sourcekey...将多个hyperloglog合并成一个hyperloglog并返回并集数量
 127.0.0.1:6379> pfadd mykey a b c d e f g 
 (integer) 1
 127.0.0.1:6379> pfcount mykey
 (integer) 7
 127.0.0.1:6379> pfadd mykey2 a b c d e f g h i j
 (integer) 1
 127.0.0.1:6379> pfcount mykey mykey2
 (integer) 10
 127.0.0.1:6379> pfmerge newkey mykey2  mykey
 OK
 127.0.0.1:6379> pfcount newkey
 (integer) 10

Bitmap

Bitmap使用位数组中的二进制来进行 状态统计 (只有 0 1)

Bitmap能够有效的大数据量下进行只有俩个状态的统计

比如:统计只有俩个状态的用户信息(活跃,不活跃。登录,未登录。打卡,未打卡)

使用

  • setbit key offset value设置key在offset处的bit值(0/1)

  • getbit key offset 获得key在offset处的bit值(0/1)

  • bitcount key 统计key中有多少位1

  • 模拟电影是否被点播情况 key->日期 offset->(电影ID)value->(0为未点播,1为点播)

    • 统计每天某部电影是否被点播 getbit 日期 电影ID
    • 统计每天有多少部电影被点播 bitcount 日期
    • 统计每周/月/年有多少部电影被点播 bitop or 每周日期 记录值后可统计每月,每年 或
    • 统计年度哪部电影没被点播 (为0时没被点播)
     127.0.0.1:6379> setbit 1130 1 1 #11月30日 1号电影 被点播
     (integer) 0
     127.0.0.1:6379> setbit 1130 2 1 #11月30日 2号电影 被点播
     (integer) 0
     127.0.0.1:6379> setbit 1130 3 1 #11月30日 3号电影 被点播
     (integer) 0
     127.0.0.1:6379> setbit 1201 4 1 #12月1日 4号电影 被点播
     (integer) 0
     127.0.0.1:6379> setbit 1201 5 1 #12月1日 5号电影 被点播
     (integer) 0
     127.0.0.1:6379> setbit 1201 1 1 #12月1日 1号电影 被点播
     (integer) 0
     ​
     127.0.0.1:6379> bitcount 1130 #11月30日 3部电影被点播
     (integer) 3
     127.0.0.1:6379> bitcount 1201 #12月1日 3部电影被点播
     (integer) 3
     127.0.0.1:6379> bitop and 1130-1201 1130 1200 #统计11月30日 与 12月1日 都点播了的电影
     (integer) 1
     127.0.0.1:6379> getbit 1130-1201 1 #2日都被点播的电影是1号电影
     (integer) 1
     127.0.0.1:6379> bitcount 1130-1201
     (integer) 1

原理

位数组使用sds来实现,sds是二进制安全的,sds存储时逆序存储位数组,逆序存储在扩容时不用修改老数据

(不了解sds的同学可以先看这篇文章深入浅出Redis(一):对象与数据结构

setbit :先计算len是否需要扩容,再计算偏移量在哪个字节上,接着计算偏移量在哪个位上,修改那个位的值并返回旧的值

getbit :计算偏移量在哪个字节上,接着计算偏移量在哪个位上,再获取

bittop :and、or、xor都是新建sds 每个字节做位操作结果放入新建sds中 ;not 则是直接取反

bitcount : 数据量小于128位时使用查表,大于128位时使用每次循环4次 swar,每次swar可以计算32位;swar是将32位分成2bit一组与01 (计算低位)再右移一位继续与运算(计算高位),再依次分为4位、8位、16位一组依次操作(jdk的integer.bitcount也是swar算法)

Bloom Filter

布隆过滤器能够使用少量的空间来判断某个元素是否存在于集合中,但存在一定的误判率(不在集合中保存元素)

布隆过滤器适合在大数据场景下,允许一定误判的快速判断元素是否存在集合中

Bloom Filter用于判断元素是否重复在集合中,不保存元素数据,节省空间,有一定误差

原理

Bloom Filter由位数组和多个hash函数组成

image-20221209214507196.png

添加:将Key经过多个hash函数得到的索引,在位数组对应索引上设置为1

判断是否在集合中:将Key经过多个hash函数得到的索引,查看位数组对应索引上值是否为1,为1则可能存在(该索引上设置为1还有可能是添加其他Key设置的),如果值为0,那么该Key一定不存在集合中

布隆过滤器的误判率与空间大小有关,空间越小就越容易导致误判

使用

安装布隆过滤器插件

 #下载
 wget https://github.com/RedisLabsModules/rebloom/archive/v1.1.1.tar.gz
 #解压
 tar -zxvf v1.1.1.tar.gz
 cd rebloom-1.1.1
 #编译
 make

使用 bf.add添加 bf.exists判断是否存在集合中

 127.0.0.1:6379> bf.add bloomfilter cl
 (integer) 1
 127.0.0.1:6379> bf.add bloomfilter tcl
 (integer) 1
 127.0.0.1:6379> bf.exists bloomfilter cl
 (integer) 1
 127.0.0.1:6379> bf.exists bloomfilter cl1
 (integer) 0
 127.0.0.1:6379> bf.exists bloomfilter tcl
 (integer) 1

总结

本篇文章深入浅出的解析Redis中四种高级数据结构的使用、适用场景以及原理

Geospatial 使用Geohash算法以及zset对象实现,适用于计算地理空间的场景

Hypeloglog 使用少量固定空间以及基数统计算法,适用于大数据情况下能接收微小出错的统计场景

Bitmap 使用sds实现的位数组,sds逆序存储位数组扩容时不用修改旧数据,适用于大数据情况下只有两个状态的统计场景

Bloom Filter 使用位数组与多个哈希函数实现,适用于在大数据情况下且能接收微小出错的判断元素是否存在集合的场景

最后(一键三连求求拉~)

本篇文章笔记以及案例被收入 gitee-StudyJavagithub-StudyJava 感兴趣的同学可以stat下持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 发布!

### Redis布隆过滤器Bitmap 的使用场景 #### 布隆过滤器的特点及其在 Redis 中的应用 布隆过滤器Bloom Filter)是一种高效的空间节约型概率数据结构,主要用于快速判断某个元素是否属于一个集合。这种特性使得其非常适合应用于需要频繁查询大量数据集成员关系的场合[^4]。 对于 Redis 来说,虽然本身并不直接提供原生支持的布隆过滤器模块,但是可以通过第三方扩展或者利用现有的命令组合来模拟实现这一功能。例如,在某些特定版本或通过安装额外插件的方式可以启用布隆过滤器的支持;而在其他情况下,则可能依赖于像 `SETBIT` 和 `GETBIT` 这样的底层指令配合哈希函数构建自定义解决方案[^2]。 #### Bitmap 特性概述 另一方面,Bitmap 是指由一系列二进制位组成的序列,其中每一位都可以单独设置为0或1。Redis 提供了一组专门针对 bitmap 操作的功能,允许用户方便地执行按位运算以及统计操作。由于每个 bit 只占用一位存储空间,因此当面对海量布尔状态记录需求时显得尤为经济有效[^1]。 #### 实现方式对比 - **布隆过滤器** - 利用多个独立散列函数将输入映射到固定长度的比特向量上; - 对应位置设为 true (即置1),以此表示该元素已被加入过; - 查询时只需检查对应索引处的状态即可得出结论; - 存在误报的可能性——即认为不存在但实际上存在的情况,但绝不会漏检真正存在过的项目。 - **Bitmap** - 主要是用来保存大量的布尔值信息; - 支持高效的批量更新和读取; - 不涉及复杂的算法逻辑,更侧重于简单直观的数据表达形式。 #### 使用场景分析 - **布隆过滤器适用范围** - 缓存穿透防护:防止恶意请求绕过缓存层直击数据库造成压力过大; - 黑名单管理:快速筛选出已知不良行为者而不必每次都访问持久化存储; - URL 去重:辅助网络爬虫识别并忽略重复抓取的内容资源。 - **Bitmap 应用领域** - 用户签到打卡系统:每天标记一次完成相应动作的时间戳; - 统计在线人数变化趋势:每分钟采样当前活跃连接数的变化曲线; - 大规模权限控制机制:精细化设定不同角色所能触及的操作权限列表[^3]。 综上所述,尽管两者都涉及到位级操作的概念,但在实际应用中的侧重点各有千秋。选择合适的技术方案取决于具体业务需求和技术栈现状等因素考量。 ```python import redis from hashlib import md5, sha1 def add_to_bloom_filter(r, key, item): """Add an element to the bloom filter.""" hashes = get_hashes(item) for h in hashes: r.setbit(key, h % 8 * 1024, 1) def check_in_bloom_filter(r, key, item): """Check if an element is possibly in the set represented by a bloom filter.""" hashes = get_hashes(item) return all(r.getbit(key, h % 8 * 1024) == 1 for h in hashes) def get_hashes(value): """Generate multiple hash values from one input value using different algorithms.""" algos = [md5, sha1] results = [] for algo in algos: hasher = algo() hasher.update(str.encode(value)) result = int(hasher.hexdigest(), base=16) results.append(result) return results r = redis.Redis(host='localhost', port=6379, db=0) add_to_bloom_filter(r, 'bloomfilter_key', 'example_item') print(check_in_bloom_filter(r, 'bloomfilter_key', 'example_item')) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值