【Redis基础知识 十一】Redis的高级数据结构

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Redis基础知识 十一】Redis的高级数据结构

除了五种基本的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>

感觉这个的具体应用就是我家附近的美食,我家是固定不变的。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
16天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
21天前
|
存储 消息中间件 NoSQL
Redis 数据结构与对象
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求和数据特点来选择合适的数据结构,并合理地设计数据模型,以充分发挥 Redis 的优势。
54 8
|
21天前
|
存储 NoSQL Java
介绍下Redis 的基础数据结构
本文介绍了Redis的基础数据结构,包括动态字符串(SDS)、链表和字典。SDS是Redis自实现的动态字符串,避免了C语言字符串的不足;链表实现了双向链表,提供了高效的操作;字典则类似于Java的HashMap,采用数组加链表的方式存储数据,并支持渐进式rehash,确保高并发下的性能。
介绍下Redis 的基础数据结构
|
16天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
16天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
70 6
|
4天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
6天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构

热门文章

最新文章