Redis的基本介绍/使用场景/基本操作

314 阅读19分钟

Redis的基本介绍/使用场景/基本操作

Redis官方网址:redis.io/
Redis下载地址:(github.com/tporadowski…)
Redis 常用命令查找网址:redisdoc.com/index.html
Redis命令大全以及应用场景地址:redisguide.com/

Redis介绍

Redis是由C语言编写的开源、基于内存、支持多种数据结构、高性能的Key-Value数据库。Redis当中的KeyValue最大大小约为512MB。

Redis是将数据储存在内存中的,通常情况下每秒读写次数达到千万级别。其次Redis使用ANSIC编写,因为C语言接近操作系统,所以Redis的执行效率很高。最后Redis的处理网络请求部分采用的是单线程,如果想充分利用CPU资源的话,可以多开几个Redis实例来达到目的。

image

优点:加速读写:因为缓存通常都是全内存的,而存储层通常读写性能不够强悍(例如MySQL),通过缓存的使用可以有效地加速读写,优化用户体验。降低后端负载:帮助后端减少访问量和复杂计算(例如很复杂的SQL语句),在很大程度降低了后端的负载。

缺点:数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。 代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。 运维成本:以Redis Cluster为例,加入后无形中增加了运维成本。

Redis的二进制安全

概念:只要一个二进制数据赋予了它本身数值以外的含义,就是二进制不安全的,比如:/0 对应的数字是0,它本来是0,但是C语言把这个0作为结束符进行使用,有了别的含义,因此C为数据不安全。C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。举个例子,如果有一种使用空字符来分割多个单词的特殊数据格式,如图所示,那么这种格式就不能使用C字符串来保存,因为C字符串所用的函数只会识别出其中的"Redis",而忽略之后的"Cluster"。  虽然数据库一般用于保存文本数据,但使用数据库来保存二进制数据的场景也不少见,因此,为了确保Redis可以适用于各种不同的使用场景,SDS(simple dynamid string )的 API都是二进制安全的(binary-safe),所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,它被读取时就是什么样。
这也是我们将SDS的buf属性称为字节数组的原因——Redis不是用这个数组来保存字符,而是用它来保存一系列二进制数据。
例如,使用SDS来保存之前提到的特殊数据格式就没有任何问题,因为SDS使用len属性的值而不是空字符来判断字符串是否结束,如图所示。 通过使用二进制安全的SDS,而不是C字符串,使得Redis不仅可以保存文本数据,还可以保存任意格式的二进制数据。 Redis的单线程模型:当Redis接受到多个并发请求的时候,redis会把这多个并发请求存到一个队列当中去,然后按照队列的先后顺序,一个一个的执行命令,也就是redis同一时刻只执行一个命令,只有这一个命令执行完了才会继续执行下一个任务。所以redis中的incr和decr是绝对的原子操作,绝对的线程安全,因此尽管redis是单线程模型的,但是性能还是很高。 image.png 参照上图,简单来说就是我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。 需要说明的是,这个I/O多路复用机制,redis还提供了select、epoll、evport、kqueue等多路复用函数库,大家可以自行去了解。

Redis作为单线程模型为什么性能还是那么高?

  1. 纯内存访问:数据存在内存当中,内存的响应时间是及快的。
  2. 非阻塞式I/O操作:redis采用epoll作为I/O多路复用技术的实现解放CPU,达到0拷贝。
  3. 由于Redis的读写都是基于内存的,读写速度都是非常快的,不会出现需要等待很长时间,所以瓶颈并不会出现在请求读写上,所以没必要使用多线程来利用CPU,如果使用多线程的话(线程数>CPU数情况下),多线程的创建、销毁、线程切换、线程竞争等开销所需要的时间会比执行读写所损耗的时间还多,那就南辕北辙了,当然这是在数据量小的时候才会这样,如果数据量到达一定量级了,那肯定是多线程比单线程快(线程数<=CPU数情况下)。

redis的远程连接(基于Windows)

Windows版的Redis有2个配置文件,一个是:redis.windows.conf,另一个是redis.windows-service.conf。

由于安装版的Redis服务自启动,是直接通过redis-server.exe启动的,但是,启动时并没有加载Redis的配置文件(redis.windows.conf),导致redis 中bind配置和密码设置不生效。因为Redis服务已经自启动,这里是不会再新启动的,故加载配置文件是失败的。也没有出现Redis启动的小盒子,需要注意的是Windows版的Redis安装时,默认启动加载的配置文件是redis.windows-service.conf,如下图所示:

image.png

因此我们应该修改redis.windows-service.conf文件

  • 搜索bind 127.0.0.1,并注释掉该行,该行表示的意思就是只允许本地主机进行连接,如果需要配置仅允许别的主机连接就更换其ip即可。
  • 搜索 protected-mode yes 修改为 protected-mode no,该行表示的意思就是为了禁止公网访问redis cache,加强redis安全的。如果启用了,则只能够通过127.0.0.1访问Redis服务器,如果从外网访问,则会返回相应的错误信息。
  • 如果想设置密码就更改requirepass foobared为requirepass 自定义密码,但需要注意前后不允许有空格。 Redis可以通过RDBAOF两种方式将数据持久化到磁盘上,其中这两种方式的区别如下:

持久化(基于Windows)

  • RDB:是在指定的时间间隔内将内存中的数据通过异步生成数据快照并且保存到磁盘中。

  • AOF:相对RDB方式,AOF方式的持久化更细粒度,把每次数据变化(写、删除操作)都记录AOF文件中,其中AOF又可以配置为always即实时将记录写到AOF文件中,everysec每隔一秒将记录写到AOF文件中,no由系统决定何时将记录写到AOF文件中。

修改redis的redis.windows-service.conf配置文件中的appendonly修改为yes,表示开启AOF持久化。开启后,启动redis服务端,发现多了一个appendonly.aof文件使用AOF做持久化,每一个命令以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写,使得 AOF文件的体积不会超出保存数据集状态所需的实际大小。

实际上,AOF持久化并不会立即将命令写入到硬盘文件中,而是写入到硬盘缓存,在接下来的策略中,配置多久来从硬盘缓存写入到硬盘文件。所以在一定程度下,还是会有数据丢失,不过你可以大大减少数据损失。

#每次操作都会立即写入aof文件中 
appendfsync always 
#每秒持久化一次(默认配置) 
appendfsync everysec 
#不主动进行同步操作,默认30s一次 
appendfsync no

当然always一定是效率最低的,个人认为everysec就够用了,数据安全性能又高。Redis也允许我们同时使用两种方式,再重启redis后会从AOF中恢复数据,因为AOF比RDB数据损失小。 配置好后,启动redis客户端,输入命令:

set test 测试

最后的flushall是清除所有的键值。打开appendonly.aof文件,去掉最后面的flushall,重启客户端和服务端,看数据是否真的持久化了 image.png 说明使用AOF持久化也成功了

应用场景

String

set、get、msetnx实现对象、图片、音频等缓存 对数据进行缓存是 Redis 最常见的用法之一,因为 Redis 把数据储存在内存而不是硬盘上面, 并且访问内存数据的速度比访问硬盘数据的速度要快得多, 所以用户可以通过把需要快速访问的数据储存在 Redis 里面来提升应用程序访问这些数据时的速度。因为 Redis 的字符串键不仅可以储存文本数据,Json 还可以储存二进制数据, 所以这个缓存程序不仅可以用来缓存网页等文本数据,对象,还可以用来缓存图片和视频等二进制数据。 比如说, 如果你正在运营一个图片网站, 那么你同样可以使用这个缓存程序来缓存网站上的热门图片, 从而提高用户访问这些热门图片的速度。再比如在构建应用程序的时候, 我们经常会需要批量地设置和获取多项信息。 以博客程序为例子:当用户想要注册成为博客的作者时, 程序就需要把这位作者的名字、账号、密码、注册时间等多项信息储存起来, 并在用户登录的时候取出这些信息。又比如说, 当博客的作者想要撰写一篇新文章的时候, 程序就需要把文章的标题、内容、作者、发表时间等多项信息储存起来, 并在用户阅读文章的时候取出这些信息。通过使用 MSET、MSETNX 命令以及 MGET 命令, 我们可以实现上面提到的这些批量设置操作和批量获取操作。 比如使用 MSET 命令和 MSETNX 命令将文章的标题、内容、作者、发表时间等多项信息储存到不同的字符串键里面, 并通过 MGsET 命令从这些键里面获取文章的各项信息。 g strlen、getrange实现预览功能 使用STRLEN命令可以实现文章长度计数功能,该功能用于显示文章内容的长度,读者可以通过这个长度值来了解一篇文章大概有多长, 从而决定是否阅读一篇文章。使用GETRANGE命令可以实现文章预览功能,该功能用于显示文章s开头的一部分内容, 这些内容可以帮助读者快速地了解文章本身, 并吸引读者进一步阅读整篇文章。

setnx实现分布式锁 使用Setnx实现分布式锁。(也可以使用第三方Redission框架实现,但是该框架底层还是使用的是Redis的Setnx)

incr、decrby实现分布式ID计数器计数器功能

  1. 分布式ID:通过执行 incr 命令来实现分布式系统全局的ID, 并且它还可以通过执行 SET 命令来保留指定数字之前的 ID , 从而避免用户为了得到某个指定的 ID 而生成大量无效 ID,(可以使用第三方雪花算法生成分布式ID)。
  2. 计数器:使用incr以及decrby命令可以实现计数器。比如网站的访客数量、用户执行某个操作的次数、某首歌或者某个视频的播放量、论坛帖子的回复数量等等, 记录这些信息都需要用到计数器。 我们可以将把计数器的值储存在一个字符串键里面, 并通过 INCRBY 命令和 DECRBY 命令, 对计数器的值执行加法操作和减法操作; 在有需要的时候, 用户还可以通过调用 GETSET 方法来清零计数器并取得清零之前的旧值。
  3. 计数器:为了保障系统的安全性和性能, 并保证系统的重要资源不被滥用, 应用程序常常会对用户的某些行为进行限制, 比如说:为了防止网站内容被网络爬虫抓取, 网站管理者通常会限制每个 IP 地址在固定时间段内能够访问的页面数量。比如一分钟之内最多只能访问 30 个页面,超过这一限制的用户将被要求进行身份验证, 确认本人并非网络爬虫, 又或者等到限制解除了之后再进行访问。又比如为了防止用户的账号遭到暴力破解, 网上银行通常会对访客的密码试错次数进行限制, 如果一个访客在尝试登录某个账号的过程中, 连续好几次输入了错误的密码, 那么这个账号将被冻结, 只能等到第二天再尝试登录, 有的银行还会向账号持有者的手机发送通知来汇报这一情况。实现这些限制机制的其中一种方法是使用限速器, 它可以限制用户在指定时间段之内能够执行某项操作的次数。我们可以把操作的最大可执行次数储存在一个字符串键里面, 然后在用户每次尝试执行被限制的操作之前, 使用 DECR 命令将操作的可执行次数减去一, 最后通过检查可执行次数的值来判断是否执行该操作。

重点回顾

  • Redis 的字符串键可以把单独的一个键和单独的一个值在数据库里面关联起来, 并且这个键和值既可以储存文字数据, 又可以储存二进制数据。
  • SET 命令在默认情况下会直接覆盖字符串键已有的值, 如果我们只想在键不存在的情况下为它设置值, 那么可以使用带有 NX 选项的 SET 命令; 相反地, 如果我们只想在键已经存在的情况下为它设置新值, 那么可以使用带有 XX 选项的 SET 命令。
  • 使用 MSET 、 MSETNX 以及 MGET 命令可以有效地减少程序的网络通信次数, 从而提升程序的执行效率。
  • Redis 用户可以通过制定命名格式来提升 Redis 数据的可读性并避免键名冲突。
  • 字符串值的正数索引以 0 为开始, 从字符串的开头向结尾不断递增; 字符串值的负数索引以 -1 为开始, 从字符串的结尾向开头不断递减。
  • GETRANGE key start end 命令接受的是闭区间索引范围, 位于 start 索引和 end 索引上的值也会被包含在命令返回的内容当中。
  • SETRANGE 命令在有需要时会自动对字符串值进行扩展, 并使用空字节填充新扩展空间中没有内容的部分。
  • APPEND 命令在键不存在时执行设置操作, 在键存在时执行追加操作。
  • Redis 会把能够被表示为 long long int 类型的整数以及能够被表示为 long double 类型的浮点数当做数字来处理。

Hash

所有String可以做的hash都可以做,在String中, 我们曾经看到过如何使用多个字符串键去储存相关联的一组数据。 比如在字符串键实现的文章储存程序中, 程序就会为每篇文章创建四个字符串键, 并把文章的标题、内容、作者和创建时间分别储存到这四个字符串键里面, 下图就展示了一个使用字符串键储存文章数据的例子。

使用多个字符串键储存文章 _images/IMAGE_ARTICLE_IN_STRINGS.png


使用多个字符串键储存相关联数据虽然在技术上是可行的, 但是在实际应用中却并不是最有效的方法, 这种储存方法至少存在以下三个问题:

  • 首先, 程序每储存一组相关联的数据, 就必须在数据库里面同时创建多个字符串键, 这样的数据越多, 数据库包含的键数量也会越多。 数量庞大的键会对数据库某些操作的执行速度产生影响, 并且维护这些键也会产生大量的资源消耗。
  • 其次, 为了在数据库里面标识出相关联的字符串键, 程序需要为它们加上相同的前缀, 但键名实际上也是一种数据, 储存键名也需要耗费内存空间, 因此重复出现的键名前缀实际上导致很多内存空间被白白浪费了。 此外, 带前缀的键名还降低了键名的可读性, 让人无法一眼看清键的真正用途, 比如键名 article::10086::author 就远不如键名 author 简洁, 而键名 article::10086::title 也远不如键名 title 来得简洁。
  • 最后, 虽然程序在逻辑上会把带有相同前缀的字符串键看作是相关联的一组数据, 但是在 Redis 看来, 它们只不过是储存在同一个数据库中的不同字符串键而已。 因此当程序需要处理一组相关联的数据时, 它就必须对所有有关的字符串键都执行相同的操作。 比如说, 如果程序想要删除 ID 为 10086 的文章, 那么它就必须把 article::10086::title 、 article::10086::content 等四个字符串键都删掉才行, 这给文章的删除操作带来了额外的麻烦, 并且还可能会因为漏删或者错删了某个键而发生错误。

为了解决以上问题, 我们需要一种能够真正地把相关联的数据打包起来储存的数据结构, 而这种数据结构就是本章要介绍的散列键。

Redis的散列键会将一个键和一个散列在数据库里面关联起来, 用户可以在散列里面为任意多个字段(field)设置值。 跟字符串键一样, 散列的字段和值既可以是文本数据, 也可以是二进制数据。 通过使用散列键, 用户可以把相关联的多项数据储存到同一个散列里面, 以便对这些数据进行管理, 又或者针对它们执行批量操作。 比如图 3-2 就展示了一个使用散列储存文章数据的例子, 在这个例子中, 散列的键为 article::10086 , 而这个键对应的散列则包含了四个字段, 其中:

  • "title" 字段储存着文章的标题 "greeting" ;
  • "content" 字段储存着文章的内容 "hello world" ;
  • "author" 字段储存着文章的作者名字 "peter" ;
  • "create_at" 字段储存着文章的创建时间 "1442744762.631885" 。

该图使用散列储存文章数据 _images/IMAGE_HASH_EXAMPLE.png

与之前使用字符串键储存文章数据的做法相比, 使用散列储存文章数据只需要在数据库里面创建一个键, 并且因为散列的字段名不需要添加任何前缀, 所以它们可以直接反映字段值储存的是什么数据。 Redis 为散列键提供了一系列操作命令, 通过使用这些命令, 用户可以:

  • 为散列的字段设置值, 又或者只在字段不存在的情况下为它设置值。
  • 从散列里面获取给定字段的值。
  • 对储存着数字值的字段执行加法操作或者减法操作。
  • 检查给定字段是否存在于散列当中。
  • 从散列里面删除指定字段。
  • 查看散列包含的字段数量。
  • 一次为散列的多个字段设置值, 又或者一次从散列里面获取多个字段的值。
  • 获取散列包含的所有字段、所有值又或者所有字段和值。

重点回顾

  • 散列键会将一个键和一个散列在数据库里面关联起来, 用户可以在散列里面为任意多个字段设置值。 跟字符串键一样, 散列的字段和值既可以是文本数据, 也可以是二进制数据。
  • 用户可以通过散列键把相关联的多项数据储存到同一个散列里面, 以便对其进行管理, 又或者针对它们执行批量操作。
  • 因为 Redis 并没有为散列提供相应的减法操作命令, 所以如果用户想对字段储存的数字值执行减法操作的话, 那么就需要将负数增量传递给 HINCRBY 命令或 HINCRBYFLOAT 命令。
  • Redis 散列包含的字段在底层是以无序方式储存的, 根据字段插入的顺序不同, 包含相同字段的散列在执行 HKEYS 、 HVALS 和 HGETALL 等命令时可能会得到不同的结果, 因此用户在使用这三个命令的时候, 不应该对命令返回元素的排列顺序作任何假设。
  • 字符串键和散列键虽然在操作方式上非常相似, 但是因为它们都拥有各自独有的优点和缺点, 所以在一些情况下, 这两种数据结构是没有办法完全代替对方的。 因此用户在构建应用程序的时候, 应该根据自己的实际需要来选择使用相应的数据结构。

Hash与String比较

String与hash的命令比较图

字符串散列
SET —— 为一个字符串键设置值。HSET —— 为散列的给定字段设置值。
SETNX —— 仅在字符串键不存在的情况下为它设置值。HSETNX —— 仅在散列不包含指定字段的情况下,为它设置值。
GET —— 获取字符串键的值。HGET —— 从散列里面获取给定字段的值。
STRLEN —— 获取字符串值的字节长度。HSTRLEN —— 获取给定字段值的字节长度。
INCRBY —— 对字符串键储存的数字值执行整数加法操作。HINCRBY —— 对字段储存的数字值执行整数加法操作。
INCRBYFLOAT —— 对字符串键储存的数字值执行浮点数加法操作。HINCRBYFLOAT —— 对字段储存的数字值执行浮点数加法操作。
MSET —— 一次为多个字符串键设置值。HMSET —— 一次为散列的多个字段设置值。
MGET —— 一次获取多个字符串键的值。HMGET —— 一次获取散列中多个字段的值。
EXISTS —— 检查给定的键是否存在于数据库当中, 这个命令可以用于包括字符串键在内的所有数据库键, 本书稍后将在《数据库》一章对这个命令进行详细的介绍。HEXISTS —— 检查给定字段是否存在于散列当中。
DEL —— 从数据库里面删除指定的键, 这个命令可以用于包括字符串键在内的所有数据库键, 本书稍后将在《数据库》一章对这个命令进行详细的介绍。HDEL —— 从散列中删除给定字段,以及它的值。

对于表中列出的字符串命令和散列命令来说, 它们之间的最大区别就是前者处理的是字符串键, 而后者处理的则是散列键, 除此之外, 这些命令要做的事情几乎都是相同的。 Redis 之所以会选择同时提供字符串键和散列键这两种数据结构, 原因在于它们虽然在操作上非常相似, 但是各自却又拥有不同的优点, 这使得它们在某些场合无法被对方替代, 本节接下来将分别介绍这两种数据结构各自的优点。

散列键的优点

散列的最大优势, 就是它只需要在数据库里面创建一个键, 就可以把任意多的字段和值储存到散列里面。 相反地, 因为每个字符串键只能储存一个键值对, 所以如果用户要使用字符串键去储存多个数据项的话, 那么就只能在数据库里面创建多个字符串键。

下图就展示了使用字符串键和散列键储存相同数量的数据项时, 数据库中创建的字符串键和散列键。使用字符串键和散列键去储存相同数量的数据项

_images/IMAGE_DB_COMPARE.png

从图中可以看到, 为了储存相同的四个数据项, 程序需要用到四个字符串键, 又或者一个散列键。 按此计算, 如果我们需要储存一百万篇文章, 那么在使用散列键的情况下, 程序只需要在数据库里面创建一百万个散列键就可以了; 但是如果使用字符串键的话, 那么程序就需要在数据库里面创建四百万个字符串键。

数据库键数量增多带来的问题主要和资源有关:

  1. 为了对数据库以及数据库键的使用情况进行统计, Redis 会为每个数据库键储存一些额外的信息, 并因此带来一些额外的内存消耗。 对于单个数据库键来说, 这些额外的内存消耗几乎可以忽略不计, 但是, 当数据库键的数量达到上百万、上千万甚至更多的时候, 这些额外的内存消耗就会变得比较可观。

  2. 当散列包含的字段数量比较少的时候, Redis 就会使用特殊的内存优化结构去储存散列中的字段和值: 与字符串键相比, 这种内存优化结构储存相同数据所需的内存要少得多。 使用内存优化结构的散列越多, 内存优化结构带来的效果也就越明显。 在一定条件下, 对于相同的数据, 使用散列键进行储存比使用字符串键进行储存要节约一半以上的内存, 有时候甚至会更多。

  3. 除了需要耗费更多内存之外, 更多的数据库键也需要占用更多的 CPU 。 每当 Redis 需要对数据库中的键进行处理时, 数据库包含的键越多, 进行处理所需的 CPU 资源就会越多, 处理所耗费的时间也会越长, 典型的情况包括:

    • 统计数据库和数据库键的使用情况;
    • 对数据库执行持久化操作, 又或者根据持久化文件还原数据库;
    • 通过模式匹配在数据库里面查找某个键, 或者执行类似的查找操作;

但是这些操作的执行时间都会受到数据库键数量的影响。

最后, 除了资源方面的优势之外, 散列键还可以有效地组织起相关的多项数据, 让程序产生出更容易理解的数据, 使得针对数据的批量操作变得更为方便。 比如在上面展示的图 3-23 中, 使用散列键储存文章数据的做法就比使用字符串键储存文章数据的做法要来得更为清晰、易懂。

字符串键的优点

虽然使用散列键可以有效地节约资源并更好地组织数据, 但是字符串键也有自己的优点:

  1. 虽然散列键命令和字符串命令在部分功能上有重合的地方, 但是字符串键命令提供的操作比散列键命令要更为丰富。 比如说, 字符串能够使用 SETRANGE 命令和 GETRANGE 命令设置或者读取字符串值的其中一部分, 又或者使用 APPEND 命令将新内容追加到字符串值的末尾, 而散列键并不支持这些操作。
  2. 本书稍后将在《自动过期》一章对 Redis 的键过期功能进行介绍, 这一功能可以在指定时间到达时, 自动删除指定的键。 因为键过期功能针对的是整个键, 用户无法为散列中的不同字段设置不同的过期时间, 所以当一个散列键过期的时候, 它包含的所有字段和值都将被删除。 与此相反, 如果用户使用字符串键储存信息项的话, 就不会遇到这样的问题: 用户可以为每个字符串键分别设置不同的过期时间, 让它们根据实际的需要自动被删除掉。

字符串键和散列键的选择

我们可以从资源占用、支持的操作以及过期时间三个方面对比了字符串键和散列键的优缺点。

比较的范畴结果
资源占用字符串键在数量较多的情况下,将占用大量的内存和 CPU 时间。 与此相反,将多个数据项储存到同一个散列里面可以有效地减少内存和 CPU 消耗。
支持的操作散列键支持的所有命令,几乎都有相应的字符串键版本,但字符串键支持的 SETRANGE 、 GETRANGE 等操作散列键并不具备。
过期时间字符串键可以为每个键单独设置过期时间,独立删除某个数据项;而散列一旦到期,它包含的所有字段和值都会被删除。

既然字符串键和散列键各有优点, 那么我们在构建应用程序的时候, 什么时候应该使用字符串键, 而什么时候又应该使用散列键呢? 对于这个问题, 以下总结了一些选择的条件和方法:

  1. 如果程序需要为每个数据项单独设置过期时间, 那么使用字符串键。
  2. 如果程序需要对数据项执行诸如 SETRANGE 、 GETRANGE 或者 APPEND 等操作, 那么优先考虑使用字符串键。 当然, 用户也可以选择把数据储存在散列里面, 然后将类似 SETRANGE 、 GETRANGE 这样的操作交给客户端执行。
  3. 如果程序需要储存的数据项比较多, 并且你希望尽可能地减少储存数据所需的内存, 那么就应该优先考虑使用散列键。
  4. 如果多个数据项在逻辑上属于同一组或者同一类, 那么应该优先考虑使用散列键。

List

介绍

List是一种线性的有序结构, 先进先出,存储的元素既可以是文字数据, 也可以是二进制数据, 并且列表中的元素可以出现重复

先进先出队列(first in first out queue)是一种非常常见的数据结构, 一般都会包含入队(enqueue)和出队(dequeue)这两个操作, 其中入队操作会将一个元素放入到队列中, 而出队操作则会从队列中移除最先被入队的元素。 image.png Redis 为列表提供了丰富的操作命令, 通过这些命令, 用户可以:

  • 将新元素推入到列表的左端或者右端;
  • 移除位于列表最左端或者最右端的元素;
  • 移除列表最右端的元素, 然后把被移除的元素推入到另一个列表的左端;
  • 获取列表包含的元素数量;
  • 获取列表在指定索引上的单个元素, 又或者获取列表在指定索引范围内的多个元素;
  • 为列表的指定索引设置新元素, 或者把新元素添加到某个指定元素的前面或者后面;
  • 对列表进行修剪, 只保留指定索引范围内的元素;
  • 从列表里面移除指定元素;
  • 执行能够阻塞客l户端的推入和移r除操作;

应用场景

rpush、lpop、lpush、rpop实现秒杀功能 电商网站都会在节日时推出一些秒杀活动, 这些活动会放出数量有限的商品供用户抢购, 秒杀系统的一个特点就是在短时间内会有大量用户同时进行相同的购买操作, 如果使用事务或者锁去实现秒杀程序, 那么就会因为锁和事务的重试特性而导致性能低下, 并且由于重试的存在, 成功购买商品的用户可能并不是最早执行购买操作的用户, 因此这种秒杀系统实际上是不公平的。解决上述问题的其中一个方法就是把用户的购买操作都放入到先进先出队列里面, 然后以队列方式处理用户的购买操作, 这样程序就可以在不使用锁或者事务的情况下实现秒杀系统, 并且得益于先进先出队列的特性, 这种秒杀系统可以按照用户执行购买操作的顺序来判断哪些用户可以成功执行购买操作, 因此它是公平的。

lrange、llen实现分页功能 对于互联网上每一个具有一定规模的网站来说, 分页程序都是必不可少的: 新闻站点、博客、论坛、搜索引擎等等, 都会使用分页程序将数量众多的信息分割为多个页面, 使得用户可以以页为单位浏览网站提供的信息, 并以此来控制网站每次取出的信息数量。 下图就展示了一个使用分页程序对用户发表的论坛主题进行分割的例子。 image.png 该图展示了一个使用列表实现分页程序的方法, 这个程序可以将给定的元素有序地放入到一个列表里面, 然后使用 LRANGE 命令从列表中取出指定数量的元素, 从而实现分页这一概念。

lrem、lpush、lrange实现待办事项功能

待办事项功能, 这些软件通常会提供一些列表,用户可以将他们要做的事情记录在待办事项列表里面, 并将已经完成的事项放入到已完成事项列表里面。 比如图 4-25 就展示了一个使用待办事项软件记录日常生活事项的例子。 下图为使用待办事项软件记录日常生活事项 _images/IMAGE_TRELLO.png

该图展示了一个使用列表实现的待办事项程序, 这个程序的核心概念是使用两个列表来分别记录待办事项和已完成事项:

  • 当用户添加一个新的待办事项时, 程序就把这个事项放入到待办事项列表中。
  • 当用户完成待办事项列表中的某个事项时, 程序就把这个事项从待办事项列表中移除, 并将它放入到已完成事项列表中。

rpush、llen、blpopRedis实现消息队列发送邮件激活账号功能、 为了验证用户身份的有效性, 网站在注册新用户的时候, 会向用户给定的邮件地址发送一封激活邮件, 用户只有在点击了验证邮件里面的激活链接之后, 新注册的帐号才能够正常使用。因为邮件发送操作需要进行复杂的网络信息交换, 所以它并不是一个快速的操作,

def register(username, password, email):
    # 创建新帐号
    create_new_account(username, password)
    # 发送激活邮件
    send_validate_email(email)
    # 向用户返回注册结果
    ui_print("帐号注册成功,请访问你的邮箱并激活帐号。")

如果我们直接在 send_valid_email() 函数里面执行邮件发送操作的话, 那么用户可能就需要等待一段较长的时间才能看到 ui_print() 函数打印出的反馈信息。为了解决这个问题, 在执行 send_validate_email() 函数的时候, 我们可以不立即执行邮件发送操作, 而是将邮件发送任务放入到一个队列里面, 然后由后台的线程负责实际执行。 这样的话, 程序只需要执行一个入队操作, 然后就可以直接向用户反馈注册结果了, 这比实际地发送邮件之后再向用户反馈结果要快得多。

阻塞弹出操作的应用

消息队列之所以使用 BLPOP 命令而不是 LPOP 命令来实现出队操作, 是因为阻塞弹出操作可以让消息接收者在队列为空的时候自动阻塞, 而不必手动进行休眠, 从而使得消息处理程序的编写变得更为简单直接, 并且还可以有效地节约系统资源。 作为对比, 以下代码展示了在使用 LPOP 命令实现出队操作的情况下, 如何实现类似上面展示的消息处理程序:

while True:
    # 尝试获取消息,如果没有消息,那么返回 None
    email_address = mq.get_message()
    if email_address is not None:
        # 有消息,发送邮件
        send_email(email_address)
    else:
        # 没有消息可用,休眠一百毫秒之后再试
        sleep(0.1)

因为缺少自动的阻塞操作, 所以这个程序在没有取得消息的情况下, 只能以一百毫秒一次的频率去尝试获取消息, 如果队列为空的时间比较长, 那么这个程序就会发送很多多余的 LPOP 命令, 并因此浪费很多 CPU 资源和网络资源。

重点回顾

  • Redis 的列表是一种线性的有序结构, 它可以按照元素推入到列表中的顺序来储存元素, 并且列表中的元素可以出现重复。
  • 用户可以使用 LPUSH 、 RPUSH 、 RPOP 、 LPOP 等多个命令, 从列表的两端推入或者弹出元素, 也可以通过 LINSERT 命令, 将新元素插入到列表已有元素的前面或后面。
  • 用户可以使用 LREM 命令从列表中移除指定的元素, 又或者直接使用 LTRIM 命令对列表进行修剪。
  • 当用户传给 LRANGE 命令的索引范围超出了列表的有效索引范围时, LRANGE 命令将对传入的索引范围进行修正, 并根据修正后的索引范围来获取列表元素。
  • BLPOP 、 BRPOP 和 BRPOPLPUSH 是阻塞版本的弹出和推入命令, 如果用户给定的所有列表都为空, 那么执行命令的客户端将被阻塞, 直到给定的阻塞时限到达又或者某个给定列表非空为止。

Set

介绍

Set允许用户将任意多个各不相同的元素储存到集合里面, 这些元素既可以是文本数据, 也可以是二进制数据。 虽然上一章介绍的列表键也允许我们储存多个元素, 但是跟列表相比, 集合有以下两个明显的区别:

  1. List可以储存重复元素, 而Set只会储存非重复元素, 尝试将一个已存在的元素添加到集合将被忽略。
  2. List以有序方式储存元素, 而Set则以无序方式储存元素。 这两个区别带来的差异主要跟命令的复杂度有关:
  • 在执行像 LINSERT 和 LREM 这样的List命令时, 即使命令只针对单个列表元素, 程序有时候也不得不遍历整个列表以确定指定的元素是否存在, 因此这些命令的复杂度都为 O(N) 。
  • 另一方面, 对于Set来说, 因为所有针对单个元素的集合命令都不需要遍历整个集合, 所以它们的复杂度都为 O(1) 。

因此当我们需要储存多个元素时, 就可以考虑这些元素是否可以以无序的方式储存, 并且是否不会出现重复, 如果是的话, 那么就可以使用集合来储存这些元素, 从而有效地利用集合操作的效率优势。 Redis 为集合键提供了一系列操作命令, 通过使用这些命令, 用户可以:

  • 将新元素添加到集合里面,或者从集合里面移除已有的元素。
  • 将指定的元素从一个集合移动到另一个集合。
  • 获取集合包含的所有元素。
  • 获取集合包含的元素数量。
  • 检查给定元素是否存在于集合。
  • 从集合里面随机地获取指定数量的元素。
  • 对多个集合执行交集、并集、差集计算。

使用场景

sadd、scard命令实现唯一计数器功能 我们可以使用字符串键以及散列键这两种键去实现计数器功能,但是这样实现的计数器的作用都非常单纯: 每当某个动作被执行时, 程序就可以调用计数器的加法操作或者减法操作, 对动作的执行次数进行记录。以上这种简单的计数行为在大部分时候都是有用的, 但是在某些情况下, 我们需要一种要求更为严格的计数器, 这种计数器只会对特定的动作或者对象进行一次计数而不是多次计数。 举个例子, 一个网站的受欢迎程度通常可以用浏览量和用户数量这两个指标进行描述:

  • 浏览量记录的是网站页面被用户访问的总次数, 网站的每个用户都可以重复地对同一个页面进行多次访问, 而这些访问会被浏览量计数器一个不漏地被记录起来。
  • 至于用户数量记录的则是访问网站的 IP 地址数量, 即使同一个 IP 地址多次访问相同的页面, 用户数量计数器也只会对这个 IP 地址进行一次计数。 对于网站的浏览量, 我们可以继续使用字符串键或者散列键实现的计数器进行计数; 但如果我们想要记录网站的用户数量, 那么就需要构建一个新的计数器, 这个计数器对于每个特定的 IP 地址只会进行一次计数, 我们把这种对每个对象只进行一次计数的计数器称之为唯一计数器(unique counter)。

sadd、srem、sismember、smembers、scard命令实现标签分类功能

  • 比如论坛可能会允许用户为帖子添加标签, 这些标签既可以对帖子进行归类, 又可以让其他用户快速地了解到帖子要讲述的内容;
  • 又比如说, 一个图书分类网站可能会允许用户为自己收藏的每一本书添加标签, 使得用户可以快速地找到被添加了某个标签的所有图书, 并且网站还可以根据用户的这些标签进行数据分析, 从而帮助用户找到他们可能会感兴趣的图书;
  • 除此之外, 购物网站也可以为自己的商品加上标签, 比如“新上架”、“热销中”、“原装进口”等等, 方便顾客了解每件商品的不同特点和属性;

通过程序, 我们可以为不同的对象添加任意多个标签: 同一个对象的所有标签都会被放到同一个集合里面, 集合里的每一个元素就是一个标签。

sadd、srem、sismember、smembers、scard命令实现点赞功能

通过这一功能, 用户可以给自己喜欢的内容进行点赞, 也可以查看给相同内容进行了点赞的其他用户, 还可以查看给相同内容进行点赞的用户数量, 诸如此类。除了点赞之外, 很多网站还有诸如“+1”、“顶”、“喜欢”等功能, 这些功能的名字虽然各有不同, 但它们在本质上和点赞功能是一样的。我们可以使用Set来储存对内容进行了点赞的用户, 从而确保每个用户只能对同一内容点赞一次, 并通过使用不同的Set命令来实现查看点赞数量、查看所有点赞用户以及取消点赞等功能。

sadd、srem、sismember、smembers、scard命令实现社交关系功能

微博、twitter 以及类似的社交网站都允许用户通过加关注或者加好友的方式, 构建一种社交关系: 这些网站上的每个用户都可以关注其他用户, 也可以被其他用户关注。 通过正在关注名单(following list), 用户可以查看自己正在关注的用户及其人数; 而通过关注者名单(follower llist), 用户可以查看有哪些人正在关注自己, 以及有多少人正在关注自己。

  • 程序为每个用户维持两个集合, 一个集合储存用户的正在关注名单, 而另一个集合则储存用户的关注者名单。
  • 当一个用户(关注者)关注另一个用户(被关注者)的时候, 程序会将被关注者添加到关注者的正在关注名单里面, 并将关注者添加到被关注者的关注者名单里面。
  • 当关注者取消对被关注者的关注时, 程序会将被关注者从关注者的正在关注名单中移除, 并将关注者从被关注者的关注者名单中移除。

sadd、srem、srandmember、smembers、scard命令实现抽奖功能

为了推销商品并反馈消费者, 商家经常会举办一些抽奖活动, 每个符合条件的消费者都可以参加这种抽奖, 而商家则需要从所有参加抽奖的消费者里面选出指定数量的获奖者, 并给他们赠送物品、金钱或者购物优惠。 我们可以利用Set的无序且去重实现的抽奖程序, 这个程序会把所有参与抽奖活动的玩家都添加到一个集合里面, 然后通过 SRANDMEMBER 命令随机地选出获奖者。

共同关注与推荐关注功能

当我们在微博上访问某个用户的个人页面时, 页面上就会展示出我们和这个用户都在关注的人, 就像下图所示那样。

_images/IMAGE_WEIBO_COMMON_FOLLOWING.png

除了共同关注之外, 一些社交网站还会通过算法和数据分析, 为用户推荐一些他可能会感兴趣的人, 比如下图 就展示了 twitter 是如何向用户推荐他可能会感兴趣的关注对象的。

图 5-13 twitter 的推荐关注功能示例

_images/IMAGE_TWITTER_RECOMMEND_FOLLOW.png

sinter、sinterstore实现共同关注功能

要实现共同关注功能, 程序需要做的就是计算出两个用户的正在关注集合之间的交集。

srandmember、sunionstore实现推荐关注功能

程序需要从用户的正在关注集合中随机地选出指定数量的人作为种子用户, 然后对这些种子用户的正在关注集合执行并集计算, 最后再从这个并集里面随机地选出一些人作为推荐关注的对象。 用户 peter 关注了 tom 、 david 、 jack 、 mary 和 sam 这五个用户, 而这五个用户又分别关注了下所示的一些用户, 从结果来看, 推荐程序随机选中了 david 、 sam 和 mary 作为种子用户, 然后又从这三个用户的正在关注集合的并集中, 随机地选出了 10 个人作为 peter 的推荐关注对象。

下图为peter用户的正在关注关系图

_images/IMAGE_FOLLOW_RELATIONSHIP.png

需要注意的是, 这里展示的推荐关注程序使用的是非常简单的推荐算法, 它假设用户会对自己正在关注的人的关注对象感兴趣, 但实际的情况可能并非如此。 为了获得更为精准的推荐效果, 实际的社交网站通常会使用更为复杂的推荐算法, 有兴趣的读者可以自行查找这方面的资料。

sadd、srem、smembers、sinter实现筛选器功能 在光顾网店或者购物网站的时候, 我们经常会看见下图这样的商品筛选器, 对于不同的条件, 这些筛选器会给出不同的选项, 用户可以通过点击不同的选项来快速找到自己想要的商品。 _images/IMAGE_PRODUCT_FILTER.png

比如上图展示的笔记本电脑筛选器来说, 如果我们点击图中“品牌”一栏的“ThinkPad”图标, 那么筛选器将只在页面里展示 ThinkPad 品牌的笔记本电脑。 如果我们继续点击“尺寸”一栏中的“13.3英寸”选项, 那么筛选器将只在页面里展示 ThinkPad 品牌的 13.3 英寸笔记本电脑, 诸如此类。

实现商品筛选器的其中一种方法是使用反向索引, 这种数据结构可以为每个物品添加多个关键字, 然后根据关键字去反向地获取相应的物品。 举个例子, 对于 "X1 Carbon" 这台笔记本电脑来说, 我们可以为它添加 "ThinkPad" 、 "14inch" 、 "Windows" 等关键字, 然后通过这些关键字来反向获取 "X1 Carbon" 这台电脑。

实现反向索引的关键是要在物品和关键字之间构建起双向的映射关系, 比如对于刚刚提到的 "X1 Carbon" 电脑来说, 反向索引程序需要构建出图 5-16 所示的两种映射关系:

  • 第一种映射关系将 "X1 Carbon" 映射至它带有的各个关键字;
  • 而第二种映射关系则将 "ThinkPad" 、 "14inch" 、 "Windows" 等多个关键字映射至 "X1 Carbon" 。

X1 Carbon 电脑及其关键字的映射关系

_images/IMAGE_X1_INDEX_1.png _images/IMAGE_X1_INDEX_2.png

上图展示了一个使用集合实现的反向索引程序, 对于用户给定的每一件物品, 这个程序都会使用一个集合去储存物品带有的多个关键字; 与此同时, 对于这件物品的每一个关键字, 程序都会使用一个集合去储存关键字与物品之间的映射。 因为构建反向索引所需的这两种映射都是一对多映射, 所以使用集合来储存这两种映射关系的做法是可行的。

下面展示了以上代码在数据库中为物品创建的各个集合, 而图 5-18 则展示了以上代码在数据库中为关键字创建的各个集合。


反向索引程序为物品创建的集合 _images/IMAGE_II_ITEM_INDEX.png

反向索引程序为关键字创建的集合 _images/IMAGE_II_KEYWORD_INDEX.png

重点回顾

  • 集合允许用户储存任意多个各不相同的元素。
  • 所有针对单个元素的集合操作,复杂度都为 O(1) 。
  • 在使用 SADD 命令向集合中添加元素时, 已存在于集合中的元素会自动被忽略。
  • 因为集合以无序的方式储存元素, 所以两个包含相同元素的集合在使用 SMEMBERS 命令时可能会得到不同的结果。
  • SRANDMEMBER 命令不会移除被随机选中的元素, 而 SPOP 命令的做法则与此相反。
  • 因为集合计算需要使用大量的计算资源, 所以我们应该尽量储存并重用集合计算的结果, 在有需要的情况下, 还可以把集合计算放到从服务器中进行。

ZSet

  • 有序集合(Sorted Set)
    • ZADD:添加或更新成员

    • ZREM:移除指定的成员

    • ZSCORE:获取成员的分值

    • ZINCRBY:对成员的分值执行自增或自减操作

    • ZCARD:获取有序集合的大小

    • ZRANK、ZREVRANK:获取成员在有序集合中的排名

    • ZRANGE、ZREVRANGE:获取指定索引范围内的成员

    • 示例:排行榜

    • ZRANGEBYSCORE、ZREVRANGEBYSCORE:获取指定分值范围内的成员

    • ZCOUNT:统计指定分值范围内的成员数量

    • 示例:时间线

    • ZREMRANGEBYRANK:移除指定排名范围内的成员

    • ZREMRANGEBYSCORE:移除指定分值范围内的成员

    • ZUNIONSTORE、ZINTERSTORE:有序集合的并集运算和交集运算

    • 示例:商品推荐

    • ZRANGEBYLEX、ZREVRANGEBYLEX:返回指定字典序范围内的成员

    • ZLEXCOUNT:统计位于字典序指定范围内的成员数量

    • ZREMRANGEBYLEX:移除位于字典序指定范围内的成员

    • 示例:自动补全

    • ZPOPMAX、ZPOPMIN:弹出分值最高和最低的成员

    • BZPOPMAX、BZPOPMIN:阻塞式最大/最小元素弹出操作

基本操作

Redis支持五种基本的数据结构,分别是

  • String(字符串)
  • Hash(哈希)
  • List(列表)
  • Set(集合)
  • Zset(即Sorted Set有序集合)

键的命名格式

被储存的内容数据库中的键键的值
文章的标题article::10086::title'message'
文章的内容article::10086::content'hello world'
文章的作者article::10086::author'john'
文章的创建时间戳article::10086::create_at'1461145575.631885'

以上表格以文章数据储存示例,Article 程序使用了多个字符串键去储存文章信息, 并且每个字符串键的名字都是以 article::<id>::<attribute> 格式命名的, 这是一种 Redis 使用惯例: Redis 用户通常会为逻辑上相关联的键设置相同的前缀, 并通过分隔符来区分键名的各个部分, 以此来构建一种键的命名格式。

比如对于 article::10086::title 、 article::10086::author 这些键来说, article 前缀表明这些键都储存着与文章信息相关的数据, 而分隔符 :: 则区分开了键名里面的前缀、ID 以及具体的属性。 除了 :: 符号之外, 常用的键名分隔符还包括 . 符号, 比如 article.10086.title ; 或者 -> 符号, 比如 article->10086->title ; 又或者 | 符号, 比如 article|10086|title ; 诸如此类。

分隔符的选择通常只是一个个人喜好的问题, 而键名的具体格式也可以根据需要进行构造: 比如说, 如果你不喜欢 article::<id>::<attribute> 格式, 那么也可以考虑使用 article::<attribute>::<id> 格式, 诸如此类。 唯一需要注意的是, 一个程序应该只使用一种键名分隔符, 并且持续地使用同一种键名格式, 以免造成混乱。

通过使用相同的格式去命名逻辑上相关联的键, 我们可以让程序产生的数据结构变得更容易被理解, 并且在有需要的时候, 还可以根据特定的键名格式, 在数据库里面以模式匹配的方式查找指定的键。

Redis常用的数据类型

  • redisTemplate // 主要是一些系统命令以及一些别的方法
  • ValueOperations valueOperations = redisTemplate.opsForValue(); // 操作字符串
  • HashOperations hashOperations = redisTemplate.opsForHash(); // 操作hash
  • ListOperations listOperations = redisTemplate.opsForList(); // 操作list
  • SetOperations setOperations = redisTemplate.opsForSet(); // 操作set
  • ZSetOperations zSetOperations = redisTemplate.opsForZSet(); // 操作有序set
  • StreamOperations streamOperations = redisTemplate.opsForStream();// 操作Stream(自行查找API) 对key的绑定便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须显式的再次指定Key,即BoundKeyOperations:
  • BoundValueOperations
  • BoundSetOperations
  • BoundListOperations
  • BoundSetOperations
  • BoundHashOperations

RedisTemplate

判断是否有key所对应的值,有则返回true,没有则返回false

Boolean hasKey(K key)

给 key 添加过期时间

//可根据unit设置时间单位,timeout设置数量
Boolean expire(K key, final long timeout, final TimeUnit unit)//指定日期过期
Boolean expireAt(K key, final Date date)

根据Key模糊匹配

Set<K> keys(K pattern)

删除key

//删除指定Key
Boolean hasKey(K key);
//批量删除Key
Long delete(Collection<K> keys)

ValueOperations

使用ValueOperations操作String类型

设置key的value

void set(K key, V value);

设置key的value和过期时间

//根据时间单位以及数量来设置过期时间
void set(K key, V value, long timeout, TimeUnit unit);

//根据时间段设置过期时间
default void set(K key, V value, Duration timeout)

如果key不存在,则设置key以保存字符串value,以及设置过期时间


Boolean setIfAbsent(K key, V value);

default Boolean setIfAbsent(K key, V value, Duration timeout)

Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);

如果key存在,则设置key以保存字符串value,以及设置过期时间

Boolean setIfPresent(K key, V value);

Boolean setIfPresent(K key, V value, long timeout, TimeUnit unit);

default Boolean setIfPresent(K key, V value, Duration timeout)

批量设置Key和Value

void multiSet(Map<? extends K, ? extends V> map);

//仅当提供的键不存在时,才使用tuple中提供的键值对将多个键设置为多个值。
Boolean multiSetIfAbsent(Map<? extends K, ? extends V> map);

获取key的值。

V get(Object key);

设置key的value并返回其旧值。

V getAndSet(K key, V value);

获取多个keys 。值按请求键的顺序返回。

List<V> multiGet(Collection<K> keys);

根据Key查询到Value的值并递增

//将key下存储为Value值的整数值加一。
Long increment(K key);

//根据Key将Value的值加上delta
Long increment(K key, long delta);

//根据Key将Value的值加上浮点数
Double increment(K key, double delta);

根据Key查询到Value的值并递减

//将key下存储为Value值的整数值减一。
Long decredment(K key);

//将key下存储为Value值的整数值减delta。
Long decrement(K key, long delta);

将value附加到key 。

Integer append(K key, String value);

获取begin和end之间的key值的子字符串。

String get(K key, long start, long end);

用给定的value覆盖从指定offset开始的key部分。

void set(K key, V value, long offset);

获取存储在key的值的长度

Long size(K key);

将位设置在key中存储的值的offset处。

Boolean setBit(K key, long offset, boolean value);

获取key处值offset处的位值。

Boolean getBit(K key, long offset);

获取/操作存储在给定key处的不同位宽和任意非(必要)对齐偏移量的特定整数字段。

List<Long> bitField(K key, BitFieldSubCommands subCommands);

HashOperations

使用HashOperations操作Hash类型 删除指定的key和hashkey

Long delete(H key, Object... hashKeys);

判断指定的key和hashkey是否存在

Boolean hasKey(H key, Object hashKey);

从key处的哈希中获取给定hashKey的值

HV get(H key, Object hashKey);

从key处的哈希中获取给定多个hashKeys的值

List<HV> multiGet(H key, Collection<HK> hashKeys);

将哈希hashKey的value增加给定的delta

List<HV> multiGet(H key, Collection<HK> hashKeys);

将哈希hashKey的value增加给定的delta

Long increment(H key, HK hashKey, long delta);

Double increment(H key, HK hashKey, double delta);

在 key 处获取散列的key集(字段)

Set<HK> keys(H key);

返回与hashKey关联的值的长度。如果key或hashKey不存在,则返回0

Long lengthOfValue(H key, HK hashKey);

获取key处的哈希大小

Long size(H key);

使用m中提供的数据将多个哈希字段设置为多个值

void putAll(H key, Map<? extends HK, ? extends HV> m);

设置哈希hashKey的value

void put(H key, HK hashKey, HV value);

仅当hashKey不存在时才设置 hash hashKey的value

Boolean putIfAbsent(H key, HK hashKey, HV value);

在key处获取哈希的条目集(值)

List<HV> values(H key);

获取存储在key的整个哈希

Map<HK, HV> entries(H key);

使用Cursor在key处遍历 hash 中的条目

Cursor<Map.Entry<HK, HV>> scan(H key, ScanOptions options);

ListOperations

使用ListOperations操作List类型 从key的列表中获取begin和end之间的元素。

List<V> range(K key, long start, long end);

获取在start和end之间的元素处key列表

void trim(K key, long start, long end);

获取存储在key的列表的大小

Long size(K key);

从列表左侧插入元素

Long leftPush(K key, V value);

在list集合左边推送多个键值对

Long leftPushAll(K key, V... values);

在list集合左边推送多个键值对

Long leftPushAll(K key, Collection<V> values);

仅当列表存在时,才将values添加到key中。

Long leftPushIfPresent(K key, V value);

在pivot之前将value插入key 。

Long leftPush(K key, V pivot, V value);

从列表右侧插入元素

Long rightPush(K key, V value);

从列表右侧插入多个元素

Long rightPushAll(K key, V... values);

从列表右侧插入value为集合的元素

Long rightPushAll(K key, Collection<V> values);

仅当列表存在时才将values附加到key

Long rightPushIfPresent(K key, V value);

在pivot之前将value插入key 。

Long rightPush(K key, V pivot, V value);

设置值,有则覆盖,没有则新增

void set(K key, long index, V value);

删除元素

Long remove(K key, long count, Object value);

查找元素

V index(K key, long index);

返回列表中指定值在 at key处第一次出现的索引。

Long indexOf(K key, V value);

返回指定值在列表中最后一次出现的索引 at key 。 需要 Redis 6.0.6 或更高版本。

Long lastIndexOf(K key, V value);

删除并返回存储在key的列表中的第一个元素。

V leftPop(K key);

从存储在key的列表中删除并返回第一个元素。 阻止连接,直到元素可用或达到timeout 。

V leftPop(K key, long timeout, TimeUnit unit);

从存储在key的列表中删除并返回第一个元素。 阻止连接,直到元素可用或达到timeout 。

default V leftPop(K key, Duration timeout) 

删除并返回存储在key的列表中的最后一个元素。

V rightPop(K key);

从存储在key的列表中删除并返回最后一个元素。 阻止连接,直到元素可用或达到timeout 。

V rightPop(K key, long timeout, TimeUnit unit);

从存储在key的列表中删除并返回最后一个元素。 阻止连接,直到元素可用或达到timeout 。

default V rightPop(K key, Duration timeout)

从sourceKey列表中删除最后一个元素,将其附加到destinationKey并返回其值。

V rightPopAndLeftPush(K sourceKey, K destinationKey);

从srcKey列表中删除最后一个元素,将其附加到dstKey并返回其值。 阻止连接,直到元素可用或达到timeout 。

V rightPopAndLeftPush(K sourceKey, K destinationKey, long timeout, TimeUnit unit);

从srcKey列表中删除最后一个元素,将其附加到dstKey并返回其值。 阻止连接,直到元素可用或达到timeout 。

default V rightPopAndLeftPush(K sourceKey, K destinationKey, Duration timeout)

SetOperations

使用SetOperations操作Set类型 在key处添加给定的values 。

Long add(K key, V... values);

从 set at key中删除给定values并返回已删除元素的数量。

Long remove(K key, Object... values);

从key的集合中移除并返回一个随机成员。

V pop(K key);

从key的集合中移除并返回count随机成员。

List<V> pop(K key, long count);

将value从key移动到destKey

Boolean move(K key, V value, K destKey);

在key处获取集合的大小。

Long size(K key);

检查设置在key是否包含value 。

Boolean isMember(K key, Object o);

返回在key和otherKey处与所有给定集合相交的成员。

Set<V> intersect(K key, K otherKey);

返回在key和otherKeys与所有给定集合相交的成员。

Set<V> intersect(K key, Collection<K> otherKeys);

返回在keys处与所有给定集合相交的成员。

Set<V> intersect(Collection<K> keys);

在key和otherKey处与所有给定的集合相交,并将结果存储在destKey中。

Long intersectAndStore(K key, K otherKey, K destKey);

在key和otherKeys处与所有给定集合相交并将结果存储在destKey中。

Long intersectAndStore(K key, Collection<K> otherKeys, K destKey);

在keys处与所有给定集合相交并将结果存储在destKey中。

Long intersectAndStore(Collection<K> keys, K destKey);

联合给定keys和otherKey处的所有集合。

Set<V> union(K key, K otherKey);

联合给定keys和otherKeys处的所有集合。

Set<V> union(K key, Collection<K> otherKeys);

联合所有给定keys的集合。

Set<V> union(Collection<K> keys);

联合给定key和otherKey的所有集合,并将结果存储在destKey中。

Long unionAndStore(K key, K otherKey, K destKey);

联合给定key和otherKeys的所有集合,并将结果存储在destKey中。

Long unionAndStore(K key, Collection<K> otherKeys, K destKey);

联合给定keys的所有集合并将结果存储在destKey中。

Long unionAndStore(Collection<K> keys, K destKey);

区分给定key和otherKey的所有集合。

Set<V> difference(K key, K otherKey);

区分给定key和otherKeys的所有集合。

Set<V> difference(K key, Collection<K> otherKeys);

区分给定keys的所有集合。

Set<V> difference(Collection<K> keys);

区分给定key和otherKey的所有集合并将结果存储在destKey中。

Long differenceAndStore(K key, K otherKey, K destKey);

destKey给定key和其他键的所有集合,并将结果存储在otherKeys中。

Long differenceAndStore(K key, Collection<K> otherKeys, K destKey);

区分给定keys的所有集合并将结果存储在destKey中。

Long differenceAndStore(Collection<K> keys, K destKey);

在key处获取 set 的所有元素。

Set<V> members(K key);

从key的集合中获取随机元素。

V randomMember(K key);

从key的集合中获取count不同的随机元素。

Set<V> distinctRandomMembers(K key, long count);

从key的集合中获取count随机元素。

List<V> randomMembers(K key, long count);

在key处迭代 set 中的元素。 重要提示:完成后调用Cursor.close()以避免资源泄漏。

Cursor<V> scan(K key, ScanOptions options);

ZSetOperations

使用ZSetOperations操作ZSet类型 将value添加到key的排序集,或者如果它已经存在则更新它的score 。

Boolean add(K key, V value, double score);

如果它不存在,则将value添加到key的排序集。

Boolean addIfAbsent(K key, V value, double score);

将tuples添加到key的排序集中,或者如果它已经存在则更新它的score 。

Long add(K key, Set<TypedTuple<V>> tuples);

如果它尚不存在,则将tuples添加到key的排序集中。

Long addIfAbsent(K key, Set<TypedTuple<V>> tuples);

从排序集中删除values 。返回已移除元素的数量。

Long remove(K key, Object... values);

通过increment value集中元素的得分。

Double incrementScore(K key, V value, double delta);

确定排序集中具有value的元素的索引。

Long rank(K key, Object o);

当得分从高到低时,确定排序集中具有value的元素的索引。

Long reverseRank(K key, Object o);

从排序集中获取start和end之间的元素。

Set<V> range(K key, long start, long end);

从排序集中获取start和end之间的一组org.springframework.data.redis.connection.RedisZSetCommands.Tuple 。

Set<TypedTuple<V>> rangeWithScores(K key, long start, long end);

从排序集中获取分数在min和max之间的元素。

Set<V> rangeByScore(K key, double min, double max);

获取一组org.springframework.data.redis.connection.RedisZSetCommands.Tuple ,其中分数在排序集中的min和max之间。

Set<TypedTuple<V>> rangeByScoreWithScores(K key, double min, double max);

获取从start到end范围内的元素,其中分数在排序集中的min和max之间。

Set<V> rangeByScore(K key, double min, double max, long offset, long count);

从start到end的范围内获取一组org.springframework.data.redis.connection.RedisZSetCommands.Tuple ,其中分数在排序集中的min和max之间。

Set<TypedTuple<V>> rangeByScoreWithScores(K key, double min, double max, long offset, long count);

从从高到低排序的有序集合中获取从start到end范围内的元素。

Set<V> reverseRange(K key, long start, long end);

从从高到低排序的有序集合中,从start到end的范围内获取org.springframework.data.redis.connection.RedisZSetCommands.Tuple的集合。

Set<TypedTuple<V>> reverseRangeWithScores(K key, long start, long end);

从从高到低排序的排序集中获取分数在min和max之间的元素。

Set<V> reverseRangeByScore(K key, double min, double max);

获取org.springframework.data.redis.connection.RedisZSetCommands.Tuple的集合,其中分数介于从高到低排序的排序集中的min和max之间。

Set<TypedTuple<V>> reverseRangeByScoreWithScores(K key, double min, double max);

获取从start到end范围内的元素,其中分数在排序集中排序的高 - > 低的min和max之间。

Set<V> reverseRangeByScore(K key, double min, double max, long offset, long count);

从start到end的范围内获取org.springframework.data.redis.connection.RedisZSetCommands.Tuple的集合,其中分数在从排序的高 -> 低排序集的min和max之间。

Set<TypedTuple<V>> reverseRangeByScoreWithScores(K key, double min, double max, long offset, long count);

计算排序集中的元素数,分数在min和max之间。

Long count(K key, double min, double max);

计算排序集中的元素数,其值介于Range#min和Range#max之间,应用字典顺序。

Long lexCount(K key, Range range);

返回使用给定key存储的有序集合的元素数。

Long size(K key);

使用key获取排序集的大小。

Long zCard(K key);

使用键key从排序集中获取具有value的元素的分数。

Double score(K key, Object o);

使用key从有序集合中移除start和end之间的元素。

Long removeRange(K key, long start, long end);

从带有键的排序集中删除org.springframework.data.redis.connection.RedisZSetCommands.Range中的元素。

Long removeRangeByLex(K key, Range range);

使用key从排序集中删除分数在min和max之间的元素。

Long removeRangeByScore(K key, double min, double max);

在key和otherKeys处联合排序集并将结果存储在目标destKey中。

Long unionAndStore(K key, K otherKey, K destKey);

在key和otherKeys处联合排序集并将结果存储在目标destKey中。

Long unionAndStore(K key, Collection<K> otherKeys, K destKey);

在key和otherKeys处联合排序集并将结果存储在目标destKey中。

default Long unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate)

在key和otherKeys处联合排序集并将结果存储在目标destKey中。

Long unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);

在key和otherKey处相交排序集并将结果存储在目标destKey中。

Long intersectAndStore(K key, K otherKey, K destKey);

在key和otherKeys处相交排序集并将结果存储在目标destKey中。

Long intersectAndStore(K key, Collection<K> otherKeys, K destKey);

在key和otherKeys处相交排序集并将结果存储在目标destKey中。

default Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate) 

在key和otherKeys处相交排序集并将结果存储在目标destKey中。

Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);

在key处迭代 zset 中的元素。 重要提示:完成后调用Cursor.close()以避免资源泄漏。

Cursor<TypedTuple<V>> scan(K key, ScanOptions options);

从key获取所有具有字典顺序的元素,其值介于org.springframework.data.redis.connection.RedisZSetCommands.Range.getMin()和org.springframework.data.redis.connection.RedisZSetCommands.Range.getMax()之间。

default Set<V> rangeByLex(K key, Range range);

获取所有元素 n 个元素,其中 n = org.springframework.data.redis.connection.RedisZSetCommands.Limit.getCount() ,从org.springframework.data.redis.connection.RedisZSetCommands.Limit.getOffset()开始,按字典顺序从ZSET key的值介于org.springframework.data.redis.connection.RedisZSetCommands.Range.getMin()和org.springframework.data.redis.connection.RedisZSetCommands.Range.getMax()之间。

Set<V> rangeByLex(K key, Range range, Limit limit);

从key获取所有具有反向字典顺序的元素,其值在org.springframework.data.redis.connection.RedisZSetCommands.Range.getMin()和org.springframework.data.redis.connection.RedisZSetCommands.Range.getMax()之间.

default Set<V> reverseRangeByLex(K key, Range range);

获取所有元素 n 个元素,其中 n = org.springframework.data.redis.connection.RedisZSetCommands.Limit.getCount() ,从org.springframework.data.redis.connection.RedisZSetCommands.Limit.getOffset() ) 开始,具有反向字典顺序来自 ZSET key ,其值介于org.springframework.data.redis.connection.RedisZSetCommands.Range.getMin()和org.springframework.data.redis.connection.RedisZSetCommands.Range.getMax()之间。

Set<V> reverseRangeByLex(K key, Range range, Limit limit);