🌈个人主页: 秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏: https://blog.csdn.net/qinjh_/category_13079918.html

目录
[Redis 命令行客户端](#Redis 命令行客户端)
[Redis 常见数据类型](#Redis 常见数据类型)
[String 字符串](#String 字符串)
前言
💬 hello! 各位铁子们大家好哇。
今日更新了Redis相关内容
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
Redis 命令行客户端
redis-cli 可以使用两种方式连接 Redis 服务器。
第一种是交互式方式:通过 redis-cli -h { host } -p { port } 的方式连接到 Redis 服务,后续所有 的操作都是通过交互式的方式实现,不需要再执行 redis-cli 了,例如:

第二种是命令方式:用redis-cli -h { host } -p { port } { command } 就可以直接得到命令的返回 结果,例如:

这里有两点要注意:1)由于我们连接的 Redis 服务位于 127.0.0.1,端口也使用的是默认的 6379 端口,所以可以省略 -h { host } -p { port }。
Redis 客户端与服务端的交互过程图:
get和set

set的格式是 **set key value 。**key和val都是字符串。
对于这里的key和value,不需要加上引号,也是表示字符串类型。要是加上引号,也是可以的。
redis中的命令不区分大小写。
get命令:如果不存在就返回nil,和NULL是一个意思。存在就返回value。
Redis 常见数据类型
基本全局命令
Redis 有 5 种数据结构,但它们都是键值对种的值,对于键来说有一些通用的命令。
KEYS
返回所有满足样式(pattern)的 key。支持如下统配样式。
- h?llo 匹配 hello , hallo 和 hxllo
- h*llo 匹配 hllo 和 heeeello
- h[ae]llo 匹配 hello 和 hallo 但不匹配 hillo
- h[^e]llo 匹配 hallo , hbllo , ... 但不匹配 hello
- h[a-b]llo 匹配 hallo 和 hbllo
?匹配任意一个字符
* 匹配0个或者多个任意字符
abcde\] 只能匹配到a b c d e ,别的不行,相当于给出固定的选项 \[\^e\] 排除e,只有e匹配不了 \[a-b\] 匹配a-b这个范围内的字符包含两侧边界
语法:
KEYS pattern
时间复杂度:O(N)
返回值:匹配 pattern 的所有 key。
示例:

EXISTS
判断某个 key 是否存在。
语法:
EXISTS key [key ...]
时间复杂度:O(1)
返回值:key 存在的个数。

DEL
删除指定的 key。
语法:
DEL key [key ...]
时间复杂度:O(1)
返回值:删除掉的 key 的个数。
示例:

EXPIRE
为指定的 key 添加秒级的过期时间(Time To Live TTL)
语法:
EXPIRE key seconds
时间复杂度:O(1)
返回值:1 表示设置成功。0 表示设置失败。
示例:

7秒后,key值为hello就过期了
TTL
获取指定 key 的过期时间,秒级。
语法:
TTL key
时间复杂度:O(1)
返回值:剩余过期时间。-1 表示没有关联过期时间,-2 表示 key 不存在。
示例:

EXPIRE 和 TTL 命令都有对应的支持毫秒为单位的版本:PEXPIRE 和 PTTL
关于键过期机制,如图:

TYPE
返回 key 对应的数据类型。
语法:
TYPE key
返回值: none , string , list , set , zset , hash and stream .。
示例:

数据结构和内部编码
type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列 表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构,

实际上 Redis 针对每种数据结构都有自己的底层内部编码实现,而且是多种实现,这样 Redis 会 在合适的场景选择合适的内部编码,如下图:

可以看到每种数据结构都有至少两种以上的内部编码实现,例如 list 数据结构包含了 linkedlist 和 ziplist 两种内部编码。同时有些内部编码,例如 ziplist,可以作为多种数据结构的内部实现,可以通 过 object encoding 命令查询内部编码:

Redis 这样设计有两个好处:
- 可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码, 无需改动外部数据结构和命令,例如 Redis 3.2 提供了 quicklist,结合了 ziplist 和 linkedlist 两者的优 势,为列表类型提供了一种更为优秀的内部编码实现,而对用户来说基本无感知。
- 多种内部编码实现可以在不同场景下发挥各自的优势,例如 ziplist 比较节省内存,但是在列表元素 比较多的情况下,性能会下降,这时候 Redis 会根据配置选项将列表类型的内部实现转换为 linkedlist,整个过程用户同样无感知。
单线程架构
Redis 使用了单线程架构来实现高性能的内存数据库服务。
我们已经知道从客户端发送的命令经历了:发送命令、执行命令、返回结果三个阶段,其中我们 重点关注第 2 步。我们所谓的 Redis 是采用单线程模型执行命令的是指:虽然客户端看起来是同 时要求 Redis 去执行命令的,但微观角度,这些命令还是采用线性方式去执行的,只是原则上命令的 执行顺序是不确定的,但一定不会有两条命令被同步执行,可以想象 Redis 内部只有一个服务窗口,多个客户端按照它们达到的先后顺序被排队在窗口前,依次接受 Redis 的服 务,所以两条 incr 命令无论执行顺序,结果一定是 2,不会发生并发问题,这个就是 Redis 的单线程 执行模型。

宏观上同时要求服务的客户端

微观上客户端发送命令的时间有先后次序的

Redis 的单线程模型

为什么单线程还能这么快
通常来讲,单线程处理能力要比多线程差,例如有 10 000 公斤货物,每辆车的运载能力是每次 200 公斤,那么要 50 次才能完成;但是如果有 50 辆车,只要安排合理,只需要依次就可以完成任 务。那么为什么 Redis 使用单线程模型会达到每秒万级别的处理能力呢?可以将其归结为三点:
- 纯内存访问。Redis 将所有数据放在内存中,内存的响应时长大约为 100 纳秒,这是 Redis 达 到每秒万级别访问的重要基础。
- 非阻塞 IO。Redis 使用 epoll 作为 I/O 多路复用技术的实现,再加上 Redis 自身的事件处理模型 将 epoll 中的连接、读写、关闭都转换为事件,不在网络 I/O 上浪费过多的时间
- 单线程避免了线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现,让程序模 型更简单;其次多线程避免了在线程竞争同一份共享数据时带来的切换和等待消耗。
Redis 使用 I/O 多路复用模型

虽然单线程给 Redis 带来很多好处,但还是有一个致命的问题:对于单个命令的执行时间都是有 要求的。如果某个命令执行过长,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客户 端的阻塞,对于 Redis 这种高性能的服务来说是非常严重的,所以 Redis 是面向快速执行场景的数据 库。
String 字符串
字符串类型是 Redis 最基础的数据类型,关于字符串需要特别注意:1)首先 Redis 中所有的键的 类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的 元素类型是字符串类型,所以字符串类型能为其他 4 种数据结构的学习奠定基础。2)其次,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似 JSON、XML 格式的字符串;数字,可以是整型或者浮点型;甚至是二进制流数据,例如图片、音频、视频等。不过一个字符 串的最大值不能超过 512 MB。
由于 Redis 内部存储字符串完全是按照二进制流的形式保存的,所以 Redis 是不处理字符集 编码问题的,客户端传入的命令中使用的是什么字符集编码,就存储什么字符集编码。
字符串数据类型

常见命令
SET
将 string 类型的 value 设置到 key 中。如果 key 之前存在,则覆盖,无论原来的数据类型是什么。之 前关于此 key 的 TTL 也全部失效。
语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
时间复杂度:O(1)
选项:
SET 命令支持多种选项来影响它的行为:
- EX seconds------使用秒作为单位设置 key 的过期时间。
- PX milliseconds------使用毫秒作为单位设置 key 的过期时间。
- NX ------只在 key 不存在时才进行设置,即如果 key 之前已经存在,设置不执行。
- XX ------只在 key 存在时才进行设置,即如果 key 之前不存在,设置不执行。
注意:由于带选项的 SET 命令可以被 SETNX 、 SETEX 、 PSETEX 等命令代替,所以之后的版本 中,Redis 可能进行合并。
返回值:
- 如果设置成功,返回 OK。
- 如果由于 SET 指定了 NX 或者 XX 但条件不满足,SET 不会执行,并返回 (nil)。
示例:

通过flushall命令,清除redis上所有的数据,把redis上所有的键值对都带走。在公司得谨慎使用

5秒后key2就失效了。

因为key1已经存在,所以带nx选项时,不执行。
GET
获取 key 对应的 value。如果 key 不存在,返回 nil。如果 value 的数据类型不是 string,会报错。
语法:
GET key
返回值:key 对应的 value,或者 nil 当 key 不存在
MSET
一次性设置多个 key 的值。
语法:
MSET key value [key value ...]
示例:

MGET
一次性获取多个 key 的值。如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil。
语法:
MGET key [key ...]
返回值:对应 value 的列表
示例:

多次 get vs 单次 mget

使用 mget / mset 由于可以有效地减少了网络时间,所以性能相较更高。
学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不 是无节制的,否则可能造成单一命令执行时间过长,导致 Redis 阻塞。
SETNX
设置 key-value 但只允许在 key 之前不存在的情况下。
语法:
SETNX key value
返回值:1 表示设置成功。0 表示没有设置。
计数命令
INCR
将 key 对应的 string 表示的数字加一。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对 应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
INCR key
返回值:integer 类型的加完后的数值。
示例:

INCRBY
将 key 对应的 string 表示的数字加上对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如 果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
incrby key decrement
返回值:integer 类型的加完后的数值。
DECR
将 key 对应的 string 表示的数字减一。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对 应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
DECR key
返回值:integer 类型的减完后的数值
DECYBY
将 key 对应的 string 表示的数字减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如 果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
DECRBY key decrement
示例:

INCRBYFLOAT
将 key 对应的 string 表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的不是 string,或者不是一个浮点数,则报 错。允许采用科学计数法表示浮点数。
语法:
INCRBYFLOAT key increment
返回值:加/减完后的数值。
示例:

其他命令
APPEND
如果 key 已经存在并且是一个 string,命令会将 value 追加到原有 string 的后边。如果 key 不存在, 则效果等同于 SET 命令。
语法:
APPEND KEY VALUE
返回值:追加完成之后 string 的长度。
示例:

append返回值,长度的单位是字节。
redis的字符串不会对字符编码做任何处理。
xshell终端默认的字符编码是utf8,一个汉字在utf8字符集中,通常是3个字节。
如果想要它显示汉字,可以在启动redis客户端的时候,加上一个 --raw的选项,这样redis客户端就能够自动把二进制数据尝试翻译:
GETRANGE
返回 key 对应的 string 的子串,由 start 和 end 确定(左闭右闭)。可以使用负数表示倒数。-1 代表 倒数第一个字符,-2 代表倒数第二个,其他的与此类似。超过范围的偏移量会根据 string 的长度调整 成正确的值。
语法:
GETRANGE key start end
返回值:string 类型的子串
示例:

SETRANGE
覆盖字符串的一部分,从指定的偏移开始。
语法:
SETRANGE key offset value
返回值:替换后的 string 的长度。
示例:

STRLEN
获取 key 对应的 string 的长度。当 key 存放的类似不是 string 时,报错。
语法:
STRLEN key
返回值:string 的长度。或者当 key 不存在时,返回 0。
示例:

命令小结
字符串类型命令小结

内部编码
字符串类型的内部编码有 3 种:
- int:8 个字节的长整型。
- embstr:小于等于 39 个字节的字符串。
- raw:大于 39 个字节的字符串。
Redis 会根据当前值的类型和长度动态决定使用哪种内部编码实现。
示例:

典型使用场景
缓存(Cache)功能
下图是比较典型的缓存使用场景,其中 Redis 作为缓冲层,MySQL 作为存储层,绝大部分请 求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和 降低后端压力的作用。
Redis + MySQL 组成的缓存存储架构:

下面的伪代码模拟了上图的业务数据访问过程:
假设业务是根据用户 uid 获取用户信息:

首先从 Redis 获取用户信息,我们假设用户信息保存在 "user:info:" 对应的键中:

如果没有从 Redis 中得到用户信息,及缓存 miss,则进一步从 MySQL 中获取对应的信息,随后写 入缓存并返回:

通过增加缓存功能,在理想情况下,每个用户信息,一个小时期间只会有一次 MySQL 查询,极大地提 升了查询效率,也降低了 MySQL 的访问数。
与 MySQL 等关系型数据库不同的是,Redis 没有表、字段这种命名空间,而且也没有对键名 有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目 的可维护性,比较推荐的方式是使用 "业务名:对象名:唯一标识:属性" 作为键名。例如 MySQL 的数据库名为 vs,用户表名为 user_info,那么对应的键可以使用 "vs:user_info:6379"、"vs:user_info:6379:name" 来表示,如果当前 Redis 只会被一个业务使用,可以省略业务名 "vs:"。如果键名过长,则可以使用团队内部都认同的缩写替代,例如 "user:6379:friends:messages:5217" 可以被 "u:6379:fr:m:5217" 代替。毕竟键名过长,还 是会导致 Redis 的性能明显下降的。
计数(Counter)功能
许多应用都会使用 Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数 据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以使用 Redis 来完成:用户每播放一次视频,相应的视频播放数就会自增 1。

共享会话(Session)
如下图所示,一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自 的服务器中,但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到 不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一 次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。
Session 分散存储

为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如下图所示,在这种模 式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从 Redis 中查询、更新 Session 信息。
Redis 集中管理 Session

手机验证码
很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码, 然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访 问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过 5 次,如下图所示。
此功能可以用以下伪代码说明基本实现思路:

