超详细Redis入门教程——Redis命令(下)

news2024/9/22 11:27:10

前言

在这里插入图片描述

本文小新为大家带来 超详细Redis入门教程——Redis命令 相关知识,具体内容包括简单动态字符串 SDS集合的底层实现原理BitMap 操作命令HyperLogLog 操作命令Geospatial 操作命令发布/订阅命令Redis 事务等进行详尽介绍~

不积跬步,无以至千里;不积小流,无以成江海。每天进步一点点,在成为强者的路上,小新与大家共同成长!

📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)

↩️本文上接:超详细Redis入门教程——Redis命令(上)


目录

Redis命令(下)

  • 前言
  • 目录
  • 一、简单动态字符串 SDS
    • 1️⃣SDS 简介
    • 2️⃣SDS 结构
    • 3️⃣SDS 的优势
    • 4️⃣常用的 SDS 操作函数
  • 二、集合的底层实现原理
    • 1️⃣两种实现的选择
    • 2️⃣zipList
    • 3️⃣listPack
    • 4️⃣skipList
    • 5️⃣quickList
    • 6️⃣key 与 value 中元素的数量
  • 三、BitMap 操作命令
    • 1️⃣BitMap 简介
    • 2️⃣setbit
    • 3️⃣getbit
    • 4️⃣bitcount
    • 5️⃣bitpos
    • 6️⃣bitop
    • 7️⃣应用场景
  • 四、HyperLogLog 操作命令
    • 1️⃣HyperLogLog 简介
    • 2️⃣pfadd
    • 3️⃣pfcount
    • 4️⃣pfmerge
    • 5️⃣应用场景
  • 五、Geospatial 操作命令
    • 1️⃣Geospatial 简介
    • 2️⃣geoadd
    • 3️⃣geopos
    • 4️⃣geodist
    • 5️⃣geohash
    • 6️⃣georadius
    • 7️⃣georadiusbymember
    • 8️⃣应用场景
  • 六、发布/订阅命令
    • 1️⃣消息系统
    • 2️⃣subscribe
    • 3️⃣psubscribe
    • 4️⃣publish
    • 5️⃣unsubscribe
    • 6️⃣punsubscribe
    • 7️⃣pubsub
  • 七、Redis 事务
    • 1️⃣Redis 事务特性
    • 2️⃣Redis 事务实现
    • 3️⃣Redis 事务异常处理
    • 4️⃣Redis 事务隔离机制
  • 后记

在这里插入图片描述

一、简单动态字符串 SDS

1️⃣SDS 简介

无论是 Redis 的 Key 还是 Value,其基础数据类型都是字符串。例如,Hash 型 Value 的 field 与 value 的类型、List 型、Set 型、ZSet 型 Value 的元素的类型等都是字符串。虽然 Redis 是使用标准 C 语言开发的,但并没有直接使用 C 语言中传统的字符串表示,而是自定义了一种字符串。这种字符串本身的结构比较简单,但功能却非常强大,称为简单动态字符串,Simple Dynamic String,简称 SDS。

注意,Redis 中的所有字符串并不都是 SDS,也会出现 C 字符串。C 字符串只会出现在字符串“字面常量”中,并且该字符串不可能发生变更。

redisLog(REDIS_WARNNING, “sdfsfsafsafds”); 

2️⃣SDS 结构

SDS 不同于 C 字符串。C 字符串本身是一个以双引号括起来,以空字符’\0’结尾的字符序列。但 SDS 是一个结构体,定义在 Redis 安装目录下的 src/sds.h 中:

struct sdshdr { 
// 字节数组,用于保存字符串 
char buf[]; 
// buf[]中已使用字节数量,称为 SDS 的长度 int len; 
// buf[]中尚未使用的字节数量 int free; 
} 

例如执行 SET country “China”命令时,键 country 与值”China”都是 SDS 类型的,只不过一个是 SDS 的变量,一个是 SDS 的字面常量。”China”在内存中的结构如下:

在这里插入图片描述

通过以上结构可以看出,SDS 的 buf 值实际是一个 C 字符串,包含空字符\0共占 6 个字节。但 SDS 的 len 是不包含空字符\0的。

在这里插入图片描述

该结构与前面不同的是,这里有 3 字节未使用空间。

3️⃣SDS 的优势

C 字符串使用 Len+1 长度的字符数组来表示实际长度为 Len 的字符串,字符数组最后以空字符’\0’结尾,表示字符串结束。这种结构简单,但不能满足 Redis 对字符串功能性、安全性及高效性等的要求。

🍀(1)防止”字符串长度获取”性能瓶颈

对于 C 字符串,若要获取其长度,则必须要通过遍历整个字符串才可获取到的。对于超长字符串的遍历,会成为系统的性能瓶颈。

但,由于 SDS 结构体中直接就存放着字符串的长度数据,所以对于获取字符串长度需要消耗的系统性能,与字符串本身长度是无关的,不会成为 Redis 的性能瓶颈。

🍀(2)保障二进制安全

C 字符串中只能包含符合某种编码格式的字符,例如 ASCII、UTF-8 等,并且除了字符串末尾外,其它位置是不能包含空字符’\0’的,否则该字符串就会被程序误解为提前结束。而在图片、音频、视频、压缩文件、office 文件等二进制数据中以空字符’\0’作为分隔符的情况是很常见的。故而在 C 字符串中是不能保存像图片、音频、视频、压缩文件、office 文件等二进制数据的。

但 SDS 不是以空字符’\0’作为字符串结束标志的,其是通过 len 属性来判断字符串是否结束的。所以,对于程序处理 SDS 中的字符串数据,无需对数据做任何限制、过滤、假设,只需读取即可。数据写入的是什么,读到的就是什么。

🍀(3)减少内存再分配次数

SDS 采用了空间预分配策略与惰性空间释放策略来避免内存再分配问题。

空间预分配策略是指,每次 SDS 进行空间扩展时,程序不但为其分配所需的空间,还会为其分配额外的未使用空间,以减少内存再分配次数。而额外分配的未使用空间大小取决于空间扩展后 SDS 的 len 属性值。

  • 如果 len 属性值小于 1M,那么分配的未使用空间 free 的大小与 len 属性值相同。
  • 如果 len 属性值大于等于 1M ,那么分配的未使用空间 free 的大小固定是 1M。

SDS 对于空间释放采用的是惰性空间释放策略。该策略是指,SDS 字符串长度如果缩短,那么多出的未使用空间将暂时不释放,而是增加到 free 中。以使后期扩展 SDS 时减少内存再分配次数。

如果要释放 SDS 的未使用空间,则可通过 sdsRemoveFreeSpace()函数来释放。

🍀(4)兼容 C 函数

Redis 中提供了很多的 SDS 的 API,以方便用户对 Redis 进行二次开发。为了能够兼容 C 函数,SDS 的底层数组 buf[]中的字符串仍以空字符\0结尾。

现在要比较的双方,一个是 SDS,一个是 C 字符串,此时可以通过 C 语言函数 strcmp(sds_str->buf,c_str)

4️⃣常用的 SDS 操作函数

下表列出了一些常用的 SDS 操作函数及其功能描述。

函数功能描述
sdsnew()使用指定的 C 字符串创建一个 SDS
sdsempty()创建一个不包含任何字符串数据的 SDS
sdsdup()创建一个指定 SDS 的副本
sdsfree()释放指定的 SDS
sdsclear()清空指定 SDS 的字符串内容
sdslen()获取指定 SDS 的已使用空间 len 值
sdsavail()获取指定 SDS 的未使用空间 free 值
sdsMakeRoomFor()使指定的 SDS 的 free 空间增加指定的大小
sdsRemoveFreeSpace()释放指定 SDS 的 free 空间
sdscat()将指定的 C 字符串拼接到指定 SDS 字符串末尾
sdscatsds()将指定的 SDS 的字符串拼接到另一个指定 SDS 字符串末尾
sdscpy()将指定的 C 字符串复制到指定的 SDS 中,覆盖原 SDS 字符串内容
sdsgrouzero()扩展 SDS 字符串到指定长度。这个扩展是使用空字符’\0’填充
sdsrange()截取指定 SDS 中指定范围内的字符串
sdstrim()在指定 SDS 中删除所有指定 C 字符串中出现的所有字符
sdsemp()对比两个给定的 SDS 字符串是否相同
sdstolow()将指定 SDS 字符串中的所有字母变为小写
sdstoupper()将指定 SDS 字符串中的所有字母变为大写

二、集合的底层实现原理

Redis 中对于 Set 类型的底层实现,直接采用了 hashTable。但对于 Hash、ZSet、List 集合的底层实现进行了特殊的设计,使其保证了 Redis 的高性能。

1️⃣两种实现的选择

对于Hash与ZSet集合,其底层的实现实际有两种:压缩列表zipList,与跳跃列表skipList。这两种实现对于用户来说是透明的,但用户写入不同的数据,系统会自动使用不同的实现。

只有同时满足以配置文件 redis.conf 中相关集合元素数量阈值与元素大小阈值两个条件,使用的就是压缩列表 zipList,只要有一个条件不满足使用的就是跳跃列表 skipList。例如,对于ZSet 集合中这两个条件如下:

  • 集合元素个数小于 redis.conf 中 zset-max-ziplist-entries 属性的值,其默认值为 128
  • 每个集合元素大小都小于 redis.conf 中 zset-max-ziplist-value 属性的值,其默认值为 64 字节

2️⃣zipList

在这里插入图片描述

🍀(1)什么是 zipList

zipList,通常称为压缩列表,是一个经过特殊编码的用于存储字符串或整数的双向链表。其底层数据结构由三部分构成:head、entries 与 end。这三部分在内存上是连续存放的。

🍀(2)head

head 又由三部分构成:

  • zlbytes:占 4 个字节,用于存放 zipList 列表整体数据结构所占的字节数,包括 zlbytes 本身的长度。
  • zltail:占 4 个字节,用于存放 zipList 中最后一个 entry在整个数据结构中的偏移量(字节)。该数据的存在可以快速定位列表的尾 entry 位置,以方便操作。
  • zllen:占 2 字节,用于存放列表包含的 entry 个数。由于其只有 16 位,所以 zipList 最多可以含有的 entry个数为 216-1 = 65535 个。

🍀(3)entries

entries 是真正的列表,由很多的列表元素 entry 构成。由于不同的元素类型、数值的不同,从而导致每个 entry 的长度不同。

每个 entry 由三部分构成:

  • prevlength:该部分用于记录上一个 entry 的长度,以实现逆序遍历。默认长度为 1 字节,只要上一个 entry的长度<254 字节,prevlength 就占 1 字节,否则其会自动扩展为 3 字节长度。
  • encoding:该部分用于标志后面的 data 的具体类型。如果 data 为整数类型,encoding 固定长度为 1 字节。如果data 为字符串类型,则 encoding 长度可能会是 1 字节、2 字节或 5 字节。data 字符串不同的长度,对应着不同的encoding 长度。
  • data:真正存储的数据。数据类型只能是整数类型或字符串类型。不同的数据占用的字节长度不同。

🍀(4)end

end 只包含一部分,称为 zlend。占 1 个字节,值固定为 255,即二进制位为全 1,表示一个 zipList 列表的结束。

3️⃣listPack

对于 ziplist,实现复杂,为了逆序遍历,每个 entry 中包含前一个 entry 的长度,这样会导致在 ziplist 中间修改或者插入 entry 时需要进行级联更新。在高并发的写操作场景下会极度降低 Redis 的性能。为了实现更紧凑、更快的解析,更简单的实现,重写实现了 ziplist,并命名为 listPack。

在 Redis 7.0 中,已经将 zipList 全部替换为了 listPack,但为了兼容性,在配置中也保留了 zipList 的相关属性。

在这里插入图片描述

🍀(1)什么是 listPack

listPack 也是一个经过特殊编码的用于存储字符串或整数的双向链表。其底层数据结构也由三部分构成:head、entries 与 end,且这三部分在内存上也是连续存放的。

listPack与zipList的重大区别在head与每个entry的结构上,表示列表结束的end与zipList 的 zlend 是相同的,占一个字节,且 8 位全为 1。

🍀(2)head

head 由两部分构成:

  • totalBytes:占 4 个字节,用于存放 listPack 列表整体数据结构所占的字节数,包括 totalBytes 本身的长度。
  • elemNum:占 2 字节,用于存放列表包含的 entry 个数。其意义与 zipList 中 zllen 的相同。

与 zipList 的 head 相比,没有了记录最后一个 entry 偏移量的 zltail。

🍀(3)entries

entries 也是 listPack 中真正的列表,由很多的列表元素 entry 构成。由于不同的元素类型、数值的不同,从而导致每个 entry 的长度不同。但与 zipList 的 entry 结构相比,listPack 的 entry 结构发生了较大变化。

其中最大的变化就是没有了记录前一个 entry 长度的 prevlength,而增加了记录当前entry 长度的 element-total-len。而这个改变仍然可以实现逆序遍历,但却避免了由于在列表中间修改或插入 entry 时引发的级联更新。

每个 entry 仍由三部分构成:

  • encoding:该部分用于标志后面的 data 的具体类型。如果 data 为整数类型,encoding 长度可能会是 1、2、3、4、5 或 9 字节。不同的字节长度,其标识位不同。如果 data 为字符串类型,则 encoding 长度可能会是 1、2或 5 字节。data 字符串不同的长度,对应着不同的 encoding 长度。
  • data:真正存储的数据。数据类型只能是整数类型或字符串类型。不同的数据占用的字节长度不同。
  • element-total-len:该部分用于记录当前 entry的长度,用于实现逆序遍历。由于其特殊的记录方式,使其本身占有的字节数据可能会是 1、2、3、4 或 5 字节。

4️⃣skipList

🍀(1)什么是 skipList

skipList,跳跃列表,简称跳表,是一种随机化的数据结构,基于并联的链表,实现简单,查找效率较高。简单来说跳表也是链表的一种,只不过它在链表的基础上增加了跳跃功能。

也正是这个跳跃功能,使得在查找元素时,能够提供较高的效率。

🍀(2)skipList 原理

假设有一个带头尾结点的有序链表。

在这里插入图片描述

在该链表中,如果要查找某个数据,需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点,或者找到最后尾结点,后两种都属于没有找到的情况。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。

为了提升查找效率,在偶数结点上增加一个指针,让其指向下一个偶数结点。

在这里插入图片描述

这样所有偶数结点就连成了一个新的链表(简称高层链表),当然,高层链表包含的节点个数只是原来链表的一半。此时再想查找某个数据时,先沿着高层链表进行查找。当遇到第一个比待查数据大的节点时,立即从该大节点的前一个节点回到原链表中进行查找。例如,若想插入一个数据 20,则先在(8,19,31,42)的链表中查找,找到第一个比 20 大的节点 31,然后再在高层链表中找到 31 节点的前一个节点 19,然后再在原链表中获取到其下一个节点值为 23。比 20 大,则将 20 插入到 19 节点与 23 节点之间。若插入的是 25,比节点23 大,则插入到 23 节点与 31 节点之间。

该方式明显可以减少比较次数,提高查找效率。如果链表元素较多,为了进一步提升查找效率,可以将原链表构建为三层链表,或再高层级链表。
在这里插入图片描述

层级越高,查找效率就会越高。

🍀(3)存在的问题

这种对链表分层级的方式从原理上看确实提升了查找效率,但在实际操作时就出现了问题:由于固定序号的元素拥有固定层级,所以列表元素出现增加或删除的情况下,会导致列表整体元素层级大调整,但这样势必会大大降低系统性能。

例如,对于划分两级的链表,可以规定奇数结点为高层级链表,偶数结点为低层级链表。对于划分三级的链表,可以按照节点序号与 3 取模结果进行划分。但如果插入了新的节点,或删除的原来的某些节点,那么定会按照原来的层级划分规则进行重新层级划分,那么势必会大大降低系统性能。

🍀(4)算法优化

为了避免前面的问题,skipList 采用了随机分配层级方式。即在确定了总层级后,每添加一个新的元素时会自动为其随机分配一个层级。这种随机性就解决了节点序号与层级间的固定关系问题。

在这里插入图片描述

上图演示了列表在生成过程中为每个元素随机分配层级的过程。从这个 skiplist 的创建和插入过程可以看出,每一个节点的层级数都是随机分配的,而且新插入一个节点不会影响到其它节点的层数。只需要修改插入节点前后的指针,而不需对很多节点都进行调整。这就降低了插入操作的复杂度。

skipList 指的就是除了最下面第 1 层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针跳过了一些节点,并且越高层级的链表跳过的节点越多。在查找数据的时先在高层级链表中进行查找,然后逐层降低,最终可能会降到第 1 层链表来精确地确定数据位置。在这个过程中由于跳过了一些节点,从而加快了查找速度。

5️⃣quickList

在这里插入图片描述

🍀(1)什么是 quickList

quickList,快速列表,quickList 本身是一个双向无循环链表,它的每一个节点都是一个 zipList。从Redis3.2版本开始,对于List的底层实现,使用quickList替代了zipList 和 linkedList。

zipList 与 linkedList 都存在有明显不足,而 quickList 则对它们进行了改进:吸取了 zipList 和 linkedList 的优点,避开了它们的不足。

quickList 本质上是 zipList 和 linkedList 的混合体。其将 linkedList 按段切分,每一段使用 zipList 来紧凑存储若干真正的数据元素,多个 zipList 之间使用双向指针串接起来。当然,对于每个 zipList 中最多可存放多大容量的数据元素,在配置文件中通过 list-max-ziplist-size 属性可以指定。

🍀(2)检索操作

为了更深入的理解 quickList 的工作原理,通过对检索、插入、删除等操作的实现分析来加深理解。

对于 List 元素的检索,都是以其索引 index 为依据的。quickList 由一个个的 zipList 构成,每个zipList 的 zllen 中记录的就是当前 zipList 中包含的 entry 的个数,即包含的真正数据元素的个数。根据要检索元素的 index,从 quickList 的头节点开始,逐个对 zipList 的 zllen 做 sum 求和,直到找到第一个求和后 sum 大于 index 的 zipList,那么要检索的这个元素就在这个 zipList 中。

🍀(3)插入操作

由于 zipList 是有大小限制的,所以在 quickList 中插入一个元素在逻辑上相对就比较复杂一些。假设要插入的元素的大小为 insertBytes,而查找到的插入位置所在的 zipList 当前的大小为 zlBytes,那么具体可分为下面几种情况:

  • 情况一:当 insertBytes + zlBytes <= list-max-ziplist-size 时,直接插入到 zipList中相应位置即可
  • 情况二:当 insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于该 zipList的首部位置,此时需要查看该 zipList 的前一个 zipList 的大小 prev_zlBytes。
    • 若 insertBytes + prev_zlBytes<= list-max-ziplist-size 时,直接将元素插入到前一个zipList 的尾部位置即可
    • 若 insertBytes + prev_zlBytes> list-max-ziplist-size 时,直接将元素自己构建为一个新的zipList,并连入 quickList 中
  • 情况三:当 insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于该 zipList的尾部位置,此时需要查看该 zipList 的后一个 zipList 的大小 next_zlBytes。
    • 若 insertBytes + next_zlBytes<= list-max-ziplist-size 时,直接将元素插入到后一个zipList 的头部位置即可
    • 若 insertBytes + next_zlBytes> list-max-ziplist-size 时,直接将元素自己构建为一个新的zipList,并连入 quickList 中
  • 情况四:当 insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于该 zipList的中间位置,则将当前 zipList 分割为两个 zipList 连接入 quickList 中,然后将元素插入到分割后的前面zipList 的尾部位置

🍀(4)删除操作

对于删除操作,只需要注意一点,在相应的 zipList 中删除元素后,该 zipList 中是否还有元素。如果没有其它元素了,则将该 zipList 删除,将其前后两个 zipList 相连接。

6️⃣key 与 value 中元素的数量

前面讲述的 Redis 的各种特殊数据结构的设计,不仅极大提升了 Redis 的性能,并且还使得 Redis 可以支持的 key 的数量、集合 value 中可以支持的元素数量可以非常庞大。

  • Redis 最多可以处理 232个 key(约 42 亿),并且在实践中经过测试,每个 Redis 实例至少可以处理 2.5 亿个key。
  • 每个Hash、List、Set、ZSet 集合都可以包含 232 个元素。

三、BitMap 操作命令

1️⃣BitMap 简介

BitMap 是 Redis 2.2.0 版本中引入的一种新的数据类型。该数据类型本质上就是一个仅包含 0 和 1 的二进制字符串。而其所有相关命令都是对这个字符串二进制位的操作。用于描述该字符串的属性有三个:key、offset、bitValue。

  • key:BitMap 是 Redis 的 key-value 中的一种 Value 的数据类型,所以该 Value 一定有其对应的
    key。
  • offset:每个 BitMap 数据都是一个字符串,字符串中的每个字符都有其对应的索引,该索引从 0 开始计数。该索引就称为每个字符在该BitMap 中的偏移量 offset。这个 offset 的值的范围是[0,232-1],即该 offset 的最大值为 4G-1,即4294967295,42 亿多。
  • bitValue:每个 BitMap 数据中都是一个仅包含 0 和 1 的二进制字符串,每个 offset 位上的字符就称为该位的值bitValue。bitValue 的值非 0 即 1。

2️⃣setbit

  • 格式:SETBIT key offset value
  • 功能:为给定 key 的 BitMap 数据的 offset 位置设置值为 value。其返回值为修改前该 offset 位置的bitValue
  • 说明:对于原 BitMap 字符串中不存在的 offset 进行赋值,字符串会自动伸展以确保它可以将 value 保存在指定的offset 上。当字符串值进行伸展时,空白位置以 0 填充。当然,设置的 value 只能是 0 或 1。不过需要注意的是,对使用较大offset 的 SETBIT 操作来说,内存分配过程可能造成 Redis 服务器被阻塞。

3️⃣getbit

格式:GETBIT key offset 功能:对 key 所储存的 BitMap 字符串值,获取指定 offset 偏移量上的位值 bitValue。说明:当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。

4️⃣bitcount

  • 格式:BITCOUNT key [start] [end]
  • 功能:统计给定字符串中被设置为 1 的 bit 位的数量。一般情况下,统计的范围是给定的整个BitMap 字符串。但也可以通过指定额外的start 或 end 参数,实现仅对指定字节范围内字符串进行统计,包括 start 和 end 在内。注意,这里的 start 与end 的单位是字节,不是 bit,并且从 0 开始计数。
  • 说明:start 和 end 参数都可以使用负数值: -1 表示最后一个字节, -2 表示倒数第二个字节,以此类推。另外,对于不存在的key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。

5️⃣bitpos

  • 格式:BITPOS key bit [start] [end]
  • 功能:返回 key 指定的 BitMap 中第一个值为指定值 bit(非 0 即 1) 的二进制位的位置。 pos,即position,位置。在默认情况下, 命令将检测整个 BitMap,但用户也可以通过可选的 start 参数和end参数指定要检测的范围。
  • 说明:start 与 end 的意义与 bitcount 命令中的相同。

6️⃣bitop

  • 格式:BITOP operation destkey key *key …+
  • 功能:对一个或多个 BitMap 字符串 key 进行二进制位操作,并将结果保存到 destkey 上。
  • operation 可以是AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
    • BITOP AND destkey key [key …] :对一个或多个 BitMap 执行按位与操作,并将结果保存到destkey 。
    • BITOP OR destkey key [key …] :对一个或多个 BitMap 执行按位或操作,并将结果保存destkey。
    • BITOP XOR destkey key [key …] :对一个或多个 BitMap 执行按位异或操作,并将结果保存到destkey 。
    • BITOP NOT destkey key :对给定 BitMap 执行按位非操作,并将结果保存到 destkey 。
  • 说明:
    • 除了 NOT 操作之外,其他操作都可以接受一个或多个 BitMap 作为输入。
    • 除了 NOT 操作外,其他对一个 BitMap 的操作其实就是一个复制。
    • 如果参与运算的多个 BitMap 长度不同,较短的 BitMap 会以 0 作为补充位与较长 BitMap 运算,且运算结果长度与较长BitMap 的相同。

7️⃣应用场景

由于 offset 的取值范围很大,所以其一般应用于大数据量的二值性统计。例如平台活跃用户统计(二值:访问或未访问)、支持率统计(二值:支持或不支持)、员工考勤统计(二值:上班或未上班)、图像二值化(二值:黑或白)等。

不过,对于数据量较小的二值性统计并不适合 BitMap,可能使用 Set 更为合适。当然,具体多少数据量适合使用 Set,超过多少数据量适合使用 BitMap,这需要根据具体场景进行具体分析。

例如,一个平台要统计日活跃用户数量。

如果使用 Set 来统计,只需上线一个用户,就将其用户 ID 写入 Set 集合即可,最后只需统计出 Set 集合中的元素个数即可完成统计。即 Set 集合占用内存的大小与上线用户数量成正比。假设用户 ID 为 m 位 bit 位,当前活跃用户数量为 n,则该 Set 集合的大小最少应该是 m*n 字节。

如果使用 BitMap 来统计,则需要先定义出一个 BitMap,其占有的 bit 位至少为注册用户数量。只需上线一个用户,就立即使其中一个 bit 位置 1,最后只需统计出 BitMap 中 1 的个数即可完成统计。即 BitMap 占用内存的大小与注册用户数量成正比,与上线用户数量无关。假设平台具有注册用户数量为 N,则 BitMap 的长度至少为 N 个 bit 位,即 N/8 字节。 何时使用 BitMap 更合适?令 mn 字节 = N/8 字节,即 n = N/8/m = N/(8m) 时,使用 Set 集合与使用 BitMap 所占内存大小相同。以淘宝为例,其用户 ID 长度为 11 位(m),其注册用户数量为 8 亿(N),当活跃用户数量为 8 亿/(811) = 0.09 亿 = 9106 = 900 万,使用 Set 与 BitMap 占用的内存是相等的。但淘宝的日均活跃用户数量为 8 千万,所以淘宝使用 BitMap 更合适。

四、HyperLogLog 操作命令

1️⃣HyperLogLog 简介

HyperLogLog 是 Redis 2.8.9 版本中引入的一种新的数据类型,其意义是 hyperlog log,超级日志记录。该数据类型可以简单理解为一个 set 集合,集合元素为字符串。但实际上HyperLogLog 是一种基数计数概率算法,通过该算法可以利用极小的内存完成独立总数的统计。其所有相关命令都是对这个“set 集合”的操作。

在这里插入图片描述

HyperLogLog 算法是由法国人 Philippe Flajolet 博士研究出来的,Redis 的作者 Antirez 为了纪念 Philippe Flajolet 博士对组合数学和基数计算算法分析的研究,在设计 HyperLogLog 命令的时候使用了 Philippe Flajolet 姓名的英文首字母 PF 作为前缀。遗憾的是 Philippe Flajolet 博士于 2011 年 3 月 22 日因病在巴黎辞世。

HyperLogLog 算法是一个纯数学算法,我们这里不做探究。

2️⃣pfadd

  • 格式:PFADD key element *element …+
  • 功能:将任意数量的元素添加到指定的 HyperLogLog 集合里面。如果内部存储被修改了返回 1,否则返回 0。

3️⃣pfcount

  • 格式:PFCOUNT key *key …+
  • 功能:该命令作用于单个 key 时,返回给定 key 的 HyperLogLog 集合的近似基数;该命令作用于多个 key时,返回所有给定 key 的 HyperLogLog 集合的并集的近似基数;如果 key 不存在,则返回 0。

4️⃣pfmerge

  • 格式:PFMERGE destkey sourcekey *sourcekey …+
  • 功能:将多个 HyperLogLog 集合合并为一个 HyperLogLog 集合,并存储到 destkey 中,合并后的HyperLogLog 的基数接近于所有 sourcekey 的 HyperLogLog 集合的并集。

5️⃣应用场景

HyperLogLog 可对数据量超级庞大的日志数据做不精确的去重计数统计。当然,这个不精确的度在 Redis 官方给出的误差是 0.81%。这个误差对于大多数超大数据量场景是被允许的。对于平台上每个页面每天的 UV 数据,非常适合使用 HyperLogLog 进行记录。

五、Geospatial 操作命令

1️⃣Geospatial 简介

Geospatial,地理空间。

Redis 在 3.2 版本中引入了 Geospatial 这种新的数据类型。该类型本质上仍是一种集合,只不过集合元素比较特殊,是一种由三部分构成的数据结构,这种数据结构称为空间元素:

  • 经度:longitude。有效经度为[-180,180]。正的表示东经,负的表示西经。
  • 纬度:latitude。有效纬度为[-85.05112878, 85.05112878]。正的表示北纬,负的表示南纬。
  • 位置名称:为该经纬度所标注的位置所命名的名称,也称为该 Geospatial 集合的空间元素名称。

通过该类型可以设置、查询某地理位置的经纬度,查询某范围内的空间元素,计算两空间元素间的距离等。

2️⃣geoadd

  • 格式:GEOADD key longitude latitude member *longitude latitude member …+
  • 功能:将一到多个空间元素添加到指定的空间集合中。
  • 说明:当用户尝试输入一个超出范围的经度或者纬度时,该命令会返回一个错误。

3️⃣geopos

  • 格式:GEOPOS key member *member …+
  • 功能:从指定的地理空间中返回指定元素的位置,即经纬度。
  • 说明:因为 该命令接受可变数量元素作为输入,所以即使用户只给定了一个元素,命令也会返回数组。

4️⃣geodist

  • 格式:GEODIST key member1 member2 [unit]
  • 功能:返回两个给定位置之间的距离。其中 unit 必须是以下单位中的一种:
  • m :米,默认
  • km :千米
  • mi :英里
  • ft:英尺
  • 说明:如果两个位置之间的其中一个不存在, 那么命令返回空值。另外,在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成0.5% 的误差。

5️⃣geohash

  • 格式:GEOHASH key member *member …+
  • 功能:返回一个或多个位置元素的 Geohash 值。
  • 说明:GeoHash 是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串。该值主要用于底层应用或者调试,实际中的作用并不大。

6️⃣georadius

  • 格式:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]

  • 功能:以给定的经纬度为中心,返回指定地理空间中包含的所有位置元素中,与中心距离不超过给定半径的元素。返回时还可携带额外的信息:

  • WITHDIST :在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。

  • WITHCOORD :将位置元素的经维度也一并返回。

  • WITHHASH:将位置元素的 Geohash 也一并返回,不过这个 hash 以整数形式表示

    命令默认返回未排序的位置元素。通过以下两个参数,用户可以指定被返回位置元素的排序方式:

  • ASC :根据中心的位置,按照从近到远的方式返回位置元素。

  • DESC :根据中心的位置,按照从远到近的方式返回位置元素。

  • 说明:在默认情况下, 该命令会返回所有匹配的位置元素。虽然用户可以使 用 COUNT 选项去获取前 N个匹配元素,但因为命令在内部可能会需要对所有被匹配的元素进行处理,所以在对一个非常大的区域进行搜索时,即使使用 COUNT选项去获取少量元素,该命令的执行速度也可能会非常慢。

7️⃣georadiusbymember

  • 格式: GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]
  • 功能:这个命令和 GEORADIUS 命令一样,都可以找出位于指定范围内的元素,但该命令的中心点是由位置元素形式给定的,而不是像GEORADIUS 那样,使用输入的经纬度来指定中心点。
  • 说明:返回结果中也是包含中心点位置元素的

8️⃣应用场景

Geospatial 的意义是地理位置,所以其主要应用地理位置相关的计算。例如,微信发现中的“附近”功能,添加朋友中“雷达加朋友”功能;QQ 动态中的“附近”功能;钉钉中的“签到” 功能等。

六、发布/订阅命令

1️⃣消息系统

在这里插入图片描述

发布/订阅,即 pub/sub,是一种消息通信模式:发布者也称为消息生产者,生产和发送消息到存储系统;订阅者也称为消息消费者,从存储系统接收和消费消息。这个存储系统可以是文件系统 FS、消息中间件 MQ、数据管理系统 DBMS,也可以是 Redis。整个消息发布者、订阅者与存储系统称为消息系统。

消息系统中的订阅者订阅了某类消息后,只要存储系统中存在该类消息,其就可不断的接收并消费这些消息。当存储系统中没有该消息后,订阅者的接收、消费阻塞。而当发布者将消息写入到存储系统后,会立即唤醒订阅者。当存储系统放满时,不同的发布者具有不同的处理方式:有的会阻塞发布者的发布,等待可用的存储空间;有的则会将多余的消息丢失。

当然,不同的消息系统消息的发布/订阅方式也是不同的。例如 RocketMQ、Kafka 等消息中间件构成的消息系统中,发布/订阅的消息都是以主题 Topic 分类的。而 Redis 构成的消息系统中,发布/订阅的消息都是以频道 Channel 分类的。

2️⃣subscribe

  • 格式:SUBSCRIBE channel *channel …+
  • 功能:Redis 客户端通过一个 subscribe命令可以同时订阅任意数量的频道。在输出了订阅了主题后,命令处于阻塞状态,等待相关频道的消息。

3️⃣psubscribe

  • 格式:PSUBSCRIBE pattern *pattern …+
  • 功能:订阅一个或多个符合给定模式的频道。
  • 说明:这里的模式只能使用通配符 。例如, it 可以匹配所有以 it 开头的频道,像 it.news、
    it.blog、it.tweets 等;news.*可以匹配所有以 news.开头的频道,像 news.global.today、news.it 等。

4️⃣publish

  • 格式:PUBLISH channel message
  • 功能:Redis 客户端通过一条 publish 命令可以发布一个频道的消息。返回值为接收到该消息的订阅者数量。

5️⃣unsubscribe

  • 格式:UNSUBSCRIBE *channel *channel …++
  • 功能:Redis 客户端退订指定的频道。
  • 说明:如果没有频道被指定,也就是一个无参数的 UNSUBSCRIBE 命令被执行,那么客户端使用 SUBSCRIBE命令订阅的所有频道都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。

6️⃣punsubscribe

  • 格式:PUNSUBSCRIBE *pattern *pattern …++
  • 功能:退订一个或多个符合给定模式的频道。
  • 说明:这里的模式只能使用通配符 *。如果没有频道被指定,其效果与 SUBSCRIBE 命令相同,客户端将退订所有订阅的频道。

7️⃣pubsub

  • 格式:PUBSUB [argument *argument …++
  • 功能:PUBSUB 是一个查看订阅与发布系统状态的内省命令集,它由数个不同格式的子命令组成,下面分别介绍这些子命令的用法。

🍀(1)pubsub channels

  • 格式:PUBSUB CHANNELS [pattern]
  • 功能:列出当前所有的活跃频道。活跃频道指的是那些至少有一个订阅者的频道。
  • 说明:pattern 参数是可选的。如果不给出 pattern 参数,将会列出订阅/发布系统中的所有活跃频道。如果给出 pattern参数,那么只列出和给定模式 pattern 相匹配的那些活跃频道。pattern 中只能使用通配符*。

🍀(2)pubsub numsub

  • 格式:PUBSUB NUMSUB [channel-1 … channel-N]
  • 功能:返回给定频道的订阅者数量。不给定任何频道则返回一个空列表。

🍀(3)pubsub numpat

  • 格式:PUBSUB NUMPAT
  • 功能:查询当前 Redis 所有客户端订阅的所有频道模式的数量总和

七、Redis 事务

Redis 的事务的本质是一组命令的批处理。这组命令在执行过程中会被顺序地、一次性全部执行完毕,只要没有出现语法错误,这组命令在执行期间是不会被中断。

1️⃣Redis 事务特性

Redis 的事务仅保证了数据的一致性,不具有像 DBMS 一样的 ACID 特性。

  • 这组命令中的某些命令的执行失败不会影响其它命令的执行,不会引发回滚。即不具备原子性。
  • 这组命令通过乐观锁机制实现了简单的隔离性。没有复杂的隔离级别。
  • 这组命令的执行结果是被写入到内存的,是否持久取决于 Redis 的持久化策略,与事务无关。

2️⃣Redis 事务实现

🍀(1)三个命令

Redis 事务通过三个命令进行控制。

  • muti:开启事务
  • exec:执行事务
  • discard:取消事务

🍀(2)基本使用

下面是定义并执行事务的用法:

在这里插入图片描述

事务执行后,再访问事务中定义的变量,其值是修改过后。

在这里插入图片描述

下面是定义但取消事务的举例:

在这里插入图片描述

事务取消后,事务中的命令是没有执行的。

在这里插入图片描述

3️⃣Redis 事务异常处理

🍀(1)语法错误

当事务中的命令出现语法错误时,整个事务在 exec 执行时会被取消。

在这里插入图片描述

exec 的提示是 exec 被忽略,事务被取消,因为之前的错误。 此时访问 age 的值,发现其仍为 19,并没有变为事务中设置的 20。

在这里插入图片描述

🍀(2)执行异常

如果事务中的命令没有语法错误,但在执行过程中出现异常,该异常不会影响其它命令的执行。

在这里插入图片描述

以上事务中第 2 条命令在执行时出现异常。因为 score 并非是整型,无法被增加 20 的操作。但该异常并不会影响其前后命令的正确执行。查看 score 与 name 的值,发现是执行成功的结果。

在这里插入图片描述

4️⃣Redis 事务隔离机制

🍀(1)为什么需要隔离机制

在并发场景下可能会出现多个客户端对同一个数据进行修改的情况。

例如:有两个客户端 C 左与 C 右,C 左需要申请 40 个资源,C 右需要申请 30 个资源。它们首先查看了当前拥有的资源数量,即 resources 的值。它们查看到的都是 50,都感觉资源数量可以满足自己的需求,于是修改资源数量,以占有资源。但结果却是资源出现了“超卖”情况。

在这里插入图片描述

为了解决这种情况,Redis 事务通过乐观锁机制实现了多线程下的执行隔离。

🍀(2)隔离的实现

Redis 通过 watch 命令再配合事务实现了多线程下的执行隔离。

在这里插入图片描述

以上两个客户端执行的时间顺序为:

时间C 左C 右
T1watch resources
T2get resourcesget resources
T3multi
T4decrby resources 40
T5decrby resources 30
T6exec

🍀(3)实现原理

其内部的执行过程如下:

    1. 当某一客户端对 key 执行了 watch 后,系统就会为该 key 添加一个 version 乐观锁,并初始化
      –version。例如初值为 1.0。
    1. 此后客户端 C 左将对该 key 的修改语句写入到了事务命令队列中,虽未执行,但其将该 key 的value 值与 version进行了读取并保存到了当前客户端缓存。此时读取并保存的是 version 的初值 1.0。
    1. 此后客户端 C 右对该 key 的值进行了修改,这个修改不仅修改了 key 的 value 本身,同时也增加了 version的值,例如使其 version 变为了 2.0,并将该 version 记录到了该 key 信息中。
    1. 此后客户端 C 左执行 exec,开始执行事务中的命令。不过,其在执行到对该 key进行修改的命令时,该命令首先对当前客户端缓存中保存的 version 值与当前 key 信息中的 version 值。如果缓存version 小于 key 的 version,则说明客户端缓存的 key 的 value已经过时,该写操作如果执行可能会破坏数据的一致性。所以该写操作不执行。

后记

在这里插入图片描述
↪️本文下接:XXXX
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/453193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023.04.23 学习周报

文章目录 摘要文献阅读1.题目2.摘要3.介绍4.模型4.1 研究区域4.2 自相关分析4.3 LSTM 5.实验与讨论5.1 高架道路不同位置空气污染物的变化5.2 高架道路不同位置空气污染物的相关性5.3 高架道路不同位置空气污染物预测 6.结论7.展望 度规张量1.曲率2.度量张量3.代码实现4.平行四…

基于遗传算法的梯级水电站群优化调度研究(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …

王道计组(23版)7_I/O系统

I/O控制方式 数据传输率低的外设&#xff1a; 程序查询方式 程序中断方式&#xff1a;外设准备就绪则主动向CPU发送中断请求 数据传输率高的外设&#xff1a; DMA方式&#xff1a;主存和I/O设备有一条直接数据通路&#xff0c;无需调用中断 通道方式&#xff1a;每个通道挂接若…

CTFSHOW web入门——web30

代码审计 把flag、system、php都给过滤了 passthru()函数同system()函数类似&#xff0c;都可以用来执行外部命令的&#xff0c;因此可以用passthru来代替system。 因此构造payload&#xff1a;?cpassthru(cat f*); 查看页面源代码即可获得flag

ROS学习第三十四节——URDF与Gazebo基本集成流程

https://download.csdn.net/download/qq_45685327/87718593 1.创建功能包 创建新功能包&#xff0c;导入依赖包: urdf xacro gazebo_ros gazebo_ros_control gazebo_plugins 2.编写URDF文件 demo01_helloworld.urdf <robot name"mycar"><link name"…

Android 一个获取网址时间的Demo

Android 一个获取网址时间的Demo 文章目录 Android 一个获取网址时间的Demo通过一个网址获取时间的代码关于Android NTP 时间Android 同步时间代码 前段时间有个客户想用局域网同步Android 设备的时间&#xff0c;开发后把这个demo分享一下。 效果&#xff1a; 这里也获取了阿…

Xshell中的基本命令

whoami 当我们刚登录上Xshell的时候&#xff0c;我们应该做什么呢&#xff1f;&#xff1f; 我们上次说了如何增加使用者&#xff0c;和删除使用者&#xff0c;今天我们说一下其他的基本命令。 我们刚开始登录的时候可以用root登录 那么我们怎么看自己事谁呢&#xff1f; …

C/C++占位符,%x和%p的区别

遇到的问题 今天遇到了一个很奇怪的问题&#xff0c;当使用malloc分配了一个堆空间后&#xff0c;分别尝试用cout和printf尝试打印该地址&#xff0c;出现了两个地址不一样的情况&#xff1a; int *pp (int*)malloc(10*sizeof(int)); *pp 1234; cout << pp << …

35. 搜索插入位置 58. 最后一个单词的长度

目录 35. 搜索插入位置 思路 代码 58. 最后一个单词的长度 思路1 代码1 思路2 代码2 35. 搜索插入位置 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。…

存钱罐出口欧盟CE认证ROHS标准测试

近年来生活水平提高&#xff0c;人们手头上的余钱也多了起来&#xff0c;家长们通常会给小孩子买存钱罐&#xff0c;培养小孩子的理财能力&#xff0c;养成良好的财富观&#xff0c;一般的存钱罐都被设计用来存放硬币&#xff0c;一般分为陶瓷&#xff0c;塑料和金属。上端有缝…

电脑蓝屏怎么办?一招教你修好

很多用户遇到电脑蓝屏问题之后不懂怎么去进行解决&#xff1f;电脑蓝屏之后&#xff0c;我们只需要花一分钟制作一个启动盘&#xff0c;然后用这个启动盘来进行系统的重装就可以了。那么具体要怎么去操作呢&#xff1f;以下带来具体的操作方法教学。 准备工作&#xff1a; 1、U…

备忘录设计模式(Memento Pattern)[论点:概念、组成角色、示例代码、框架中的运用、适用场景]

文章目录 概念组成角色示例代码框架中的运用适用场景 概念 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;主要用于保存对象的内部状态&#xff0c;以便在需要时恢复到先前的状态。这种模式有助于实现撤销、恢复或回滚操作&#xff0c;同…

软件测试之基础概念学习篇(需求 + 测试用例 + 开发模型 + 测试模型 + BUG)

文章目录 1. 什么是软件测试2. 软件测试和软件开发的区别3. 软件测试和软件调试的区别4. 什么是需求1&#xff09;以需求为依据设计测试用例 5. 测试用例是什么6. 什么是 BUG&#xff08;软件错误&#xff09;7. 五个开发模型1&#xff09;瀑布模型2&#xff09;螺旋模型3&…

有哪些方法可以防止企业内部数据泄露?

企业内部数据安全威胁多数源于企业内部&#xff0c;企业内部存在许多安全威胁&#xff0c;主要包括: 1. 员工误操作或疏忽&#xff1a;这是导致内部安全事故的最主要原因之一。员工对安全程序缺乏认识或执行不严谨&#xff0c;可能会无意中泄露敏感数据或引发系统漏洞。 2. 未经…

Windows 服务监控工具

在任何企业中&#xff0c;Windows 服务都是面向业务的应用程序的核心组件。这些 Windows 服务的有效运行对于防止网络和应用程序停机至关重要。这使得 Windows 服务监视成为任何网络管理策略的关键部分。 Windows 服务监视使管理员能够确保关键 Windows 服务的可用性&#xff…

mysql的函数 --- MySQL总结(二)

mysql的函数 文章目录 mysql的函数日期函数字符串函数其他函数 日期函数 上面的所以的函数都可以使用select进行展现相应的数据 这里的使用current_date()时间戳&#xff0c;其他日期都可以相似的操作进行对于时间的操作 字符串函数 后面这里的操作查找name表之中的id的字符集&…

重构这件“小”事儿 | 得物技术

本文以一个Web项目的业务代码重构实践作为依据&#xff0c;来探讨项目业务代码重构过程中遇到的开发问题&#xff0c;以及重构过程中的一些注意点&#xff0c;希望可以给项目开发和服务开发维护重构提供一些通用的参考与思路。 这里不探讨大型项目的重构实践&#xff0c;毕竟一…

SHELL的脚本编写(2)

目录 1.编写脚本for1.sh&#xff0c;使用for循环创建20账户&#xff0c;账户名前缀由用户从键盘输入&#xff0c;账户初始密码由用户输入&#xff0c;例如&#xff1a;test1、test2、test3、......、test10 首先创建并编写for1.sh 查看结果 2.编写脚本for2.sh&#xff0c;使用…

Java - Lambda 表达式

一、背景 Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。 Lambda 表达式&#xff08;Lambda expression&am…

【数据库】— 2NF、3NF、BCNF、最小函数依赖集例题

判断范式级别 设有关系模式W(C,P,S,G,T,R)&#xff0c;其中各属性的含义是&#xff1a;C课程&#xff0c;P教师&#xff0c;S学生&#xff0c;G成绩&#xff0c;T时间&#xff0c;R教室&#xff0c;根据定义有如下数据依赖集 D{ C→P&#xff0c;(S,C)→G&#xff0c;(T,R)→C&…