文章目录
Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 ! 本篇文章给大家讲解的是 Redis 中 string 类型的相关内容 , 我们会从常见命令、底层编码方式、应用场景三方面给大家介绍
本专栏旨在为初学者提供一个全面的 Redis 学习路径,从基础概念到实际应用,帮助读者快速掌握 Redis 的使用和管理技巧。通过本专栏的学习,能够构建坚实的 Redis 知识基础,并能够在实际学习以及工作中灵活运用 Redis 解决问题 .
专栏地址 : Redis 入门实践
正文开始
我们目前谈到的数据类型 , 都是针对 value 来说的
Redis 中的字符串 , 直接就是按照二进制数据的方式来存储的 , 我们可以变相的将 string 视为字节数组
底层不会做任何的编码转化 , 也就是说存进去的是什么 , 取出来的就是什么 .
那 Redis 的字符串不仅可以存储文本数据 , 它还可以存储整数、JSON、xml、二进制的数据 (图片 / 视频 / 音频) …
但是视频 / 音频体积可能会比较大 , 而 Redis 对于 string 类型 , 限制了大小最大是 512M
那 Redis 他的底层是不会做任何的编码转换的
我们之前在学习 MySQL 的时候 , 默认的字符集是拉丁文 , 插入中文就会失败 .
而 Redis 存进去什么就是什么 , 直接取出来即可 , 就不用再判断字符集等等
一 . 常见命令
1.1 set
set 的语法 : set key value [ex 超时时间(秒) | px 超时时间(毫秒)] [nx | xx]
Redis 官方文档特殊符号的说明 :
[]
就相当于一个独立的单元 , 表示可选项 (可有可无)
|
表示或者 , 多个条件之间只能出现一个
[]
和[]
是可以同时存在的
比如 我们的这一条指令 : set key value ex 10 , 他就相当于这两条指令
- set key value
- expire key 10
另外 , nx 指的是如果 key 不存在 , 才去设置 ; 如果 key 存在 , 则不去设置 (返回 nil)
xx 指的是如果 key 存在 , 才去设置 value (相当于更新操作) ; 如果 key 不存在 , 则不设置 (返回 nil)
那为了方便 set 命令的使用 , Redis 就出了一些简写 , 比如 : setnx、setex、setpx …
还需要注意的是 , 如果 key 不存在 , 那就会创建出新的键值对 , 如果 key 存在 , 则是让新的 value 覆盖旧的 value , 也有可能修改之前的数据类型 , 原来存在的过期时间 (ttl) 也会失效
接下来 , 我们就来模拟一下 set 命令的使用
清除之前的数据 : flushdb / flushall
127.0.0.1:6379> set k1 123 # 设置键值对
OK
127.0.0.1:6379> get k1 # 获取键值对
"123"
127.0.0.1:6379> set k2 123 ex 10 # 设置过期时间为 10s
OK
127.0.0.1:6379> ttl k2 # 获取剩余时间
(integer) 5
127.0.0.1:6379> ttl k2 # 键值对如果过期 , 就会返回 -2
(integer) -2
127.0.0.1:6379> get k2 # 此时再去获取 k2 就获取不到了
(nil)
127.0.0.1:6379> set k3 123 nx # nx 指的是不存在才去创建
OK
127.0.0.1:6379> get k3
"123"
127.0.0.1:6379> set k1 456 nx # 此时 k1 已经存在 , nx 再去创建就会失败 , 返回 nil
(nil)
127.0.0.1:6379> get k1 # 得到的还是旧的值
"123"
127.0.0.1:6379> set k4 666 xx # xx 指的是存在才去创建 (覆盖)
(nil) # 此时 k4 并不存在 , 就会创建失败 , 返回 nil
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> set k3 789 xx # k3 此时存在 , xx 创建就会成功
OK
127.0.0.1:6379> get k3 # 之前 k3 是 123 , 现在变成 789 , 证明 xx 创建成功
"789"
1.2 get
语法 : get key
我们根据指定的 key 就可以获取到 value 的值
127.0.0.1:6379> set k1 123 # 设置键值对
OK
127.0.0.1:6379> get k1 # 获取键值对
"123"
对于 get 来说 , 只支持字符串类型的 value , 如果 value 是其他类型 , 查询就会失败
127.0.0.1:6379> lpush k5 11 22 33 # k5 是 list 类型
(integer) 3
127.0.0.1:6379> get k5 # value 是其他类型 , get 就会失败
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> type k5 # 我们也可以通过 type 查看 k5 的类型
list
1.3 mset、mget
mset 和 mget 可以一次操作多组键值对
一次网络通信传输就可以进行多组操作
语法 :
mset key1 value1 [key2 value2 …]
mget key1 [key2 …]
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 设置多组键值对
OK
127.0.0.1:6379> mget k1 k2 k3 # 获取多组键值对
1) "v1"
2) "v2"
3) "v3"
那 mset 和 mget 的时间复杂度为 O(N)
此处的 N 不是整个 Redis 服务器中所有的 key 的数量 , 而是当前命令中给出的 key 的数量 , 那我们也可以变相的认为他的时间复杂度为 O(1)
1.4 setnx、setex、psetex
setnx : 不存在才去设置 , 存在的话就会设置失败
127.0.0.1:6379> setnx k1 v1 # setnx 指的是不存在才去设置
(integer) 1
127.0.0.1:6379> get k1 # 获取 k1
"v1"
127.0.0.1:6379> setnx k1 other # 此时 k1 已经存在, setnx 再去设置 k1 就会失败
(integer) 0
127.0.0.1:6379> get k1 # 获取到的还是之前的值
"v1"
setex : 设置超时时间 , 单位为 s
语法 : setex key seconds value
127.0.0.1:6379> setex k2 10 v2 # 设置超时时间(单位: s)
OK
127.0.0.1:6379> ttl k2
(integer) 5
127.0.0.1:6379> ttl k2
(integer) -2
psetex : 也是设置超时时间 , 单位是 ms
语法 : psetex key milliseconds value
127.0.0.1:6379> psetex k3 100 v3 # 设置超时时间(单位: ms)
OK
127.0.0.1:6379> ttl k3
(integer) -2
1.5 incr、incrby
这一组命令 , 实际上操作的是数字
incr 表示针对 value + 1
incrby 表示针对 value + n
incrbyfloat 表示针对 value +/- 小数
decr 表示针对 value - 1
decrby 表示针对 value - n
我们分别来看
incr 语法 : incr key , 返回值就是 +1 之后的值
要求 key 对应的 value 必须要是整数
127.0.0.1:6379> set k1 10 # 设置键值对, 要求 value 为数值
OK
127.0.0.1:6379> incr k1 # 对 k1 进行自增
(integer) 11 # 返回值代表 +1 之后的数
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> set k2 hello # 设置键值对, 但是 value 设置成字符串类型
OK
127.0.0.1:6379> incr k2 # 对字符串进行自增操作就会失败
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set k3 1.5 # 对小数进行 incr 操作也会失败
OK
127.0.0.1:6379> incr k3
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set k4 999999999999999999999999999999999999999999999999999999 # 对一个非常大的数进行自增操作也会失败
OK
127.0.0.1:6379> incr k4
(error) ERR value is not an integer or out of range
那如果我们操作的 key 不存在 , 会怎样处理
127.0.0.1:6379> incr k5
(integer) 1 # 返回值代表 +1 之后的数
127.0.0.1:6379> get k5 # 返回值为 1 代表之前的数是 0
"1"
这也就是说 , incr 操作的 key 如果不存在 , 就会把这个 key 的 value 当做 0 来使用
incrby : incrby 可以对 key 进行 +n 的操作
语法 : incrby key increment
127.0.0.1:6379> set k6 10 # 设置键值对, 要求 value 为数值
OK
127.0.0.1:6379> incrby k6 100 # 对 k6 进行 +100 的操作
(integer) 110 # 返回值代表 +n 之后的结果
127.0.0.1:6379> get k6
"110"
127.0.0.1:6379> set k7 hello # 设置键值对, 但是 value 设置成字符串类型
OK
127.0.0.1:6379> incrby k7 100 # 对字符串进行 incrby 操作就会失效
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set k8 3.14 # 设置键值对, 但是 value 设置成浮点数类型
OK
127.0.0.1:6379> incrby k8 100 # 对小数进行 incrby 操作就会失效
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set k9 99999999999999999999999999999999999999999999999999999999999999 # 对一个非常大的数进行自增操作也会失败
OK
127.0.0.1:6379> incrby k9 100
(error) ERR value is not an integer or out of range
127.0.0.1:6379> incrby k10 100 # 对一个不存在的 key 进行 incrby 操作, 他会从 0 开始 +n
(integer) 100
127.0.0.1:6379> get k10
"100"
那 incrby 加上一个负数 , 就相当于减法了
但是没人这样用 , 大家都直接使用 decr / decrby 这个命令了
127.0.0.1:6379> set k6 10 # 设置键值对, 要求 value 为数值
OK
127.0.0.1:6379> incrby k6 -1 # n 设置成负数就代表减法
(integer) 109
1.6 decr、decrby、incrbyfloat
decr 就是把 key 对应的 value 进行 -1 操作
decr 跟 incr 的要求相同
- key 对应的 value 必须是整数
- 如果这个 key 对应的 value 不存在 , 则当做 0 来处理
127.0.0.1:6379> set k1 21 # 设置键值对, 要求 value 为数值
OK
127.0.0.1:6379> decr k1 # -1 操作
(integer) 20 # 返回值同样是 -1 之后的结果
127.0.0.1:6379> set k2 hello # 设置键值对, 但是 value 设置成字符串类型
OK
127.0.0.1:6379> decr k2 # 对字符串进行 incrby 操作就会失效
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set k3 9999999999999999999999999999999999999999999999999999 # 对一个非常大的数进行自减操作也会失败
OK
127.0.0.1:6379> decr k3
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set k4 1.5 # 设置键值对, 但是 value 设置成浮点数类型
OK
127.0.0.1:6379> decr k4 # 对小数进行 incrby 操作就会失效
(error) ERR value is not an integer or out of range
127.0.0.1:6379> decr k5 # 对一个不存在的 key 进行 decr 操作, 会把 key 视为 0
(integer) -1 # 返回值为 -1 就代表 key 原本是 0
127.0.0.1:6379> get k5
"-1"
decrby 就是把 key 对应的 value 进行 -n 的操作
127.0.0.1:6379> set k1 20 # 设置键值对, 要求 value 为数值
OK
127.0.0.1:6379> decrby k1 10 # 对 k1 进行 -n 操作
(integer) 10
127.0.0.1:6379> get k1
"10"
127.0.0.1:6379> decrby k1 -10 # 如果填写的负数, 那就会认为是 +n 的效果
(integer) 20
incrbyfloat 是针对浮点数进行运算的
127.0.0.1:6379> set k1 10.5 # 设置键值对, 要求 value 为浮点数
OK
127.0.0.1:6379> incrbyfloat k1 0.5 # 对 k1 + 0.5
"11"
127.0.0.1:6379> incrbyfloat k1 -0.5 # 对 k1 - 0.5
"10.5"
那 incrbyfloat 是没有对应的 decrbyfloat 这样对应的操作的 , 我们只能将操作数修改成负数来达到减法的效果
上面的这五个操作 , 他们的时间复杂度都是 O(1) 的
另外 , 由于 Redis 处理命令的时候 , 是单线程的模型 , 那多个客户端同时针对同一个 key 进行 incr 操作 , 也就不会引起所谓的 “线程安全” 问题
1.7 append
string 作为字符串 , 他也支持一些常用的操作 , 比如 : 字符串的拼接、获取 / 修改字符串的部分内容、获取字符串的长度
字符串的拼接 : append
获取字符串的部分内容 : getrange
修改字符串的部分内容 : setrange
获取字符串的长度 : strlen
那我们先来看 append , 如果 key 已经存在并且 value 是一个 string 类型 , 那就会将 value 追加到原来的 string 的后面 ; 如果 key 不存在 , 则效果等同于 set 命令 , 也就是直接创建出一个新的键值对对象
语法 : append key value
他的时间复杂度也是 O(1) , 返回值代表追加完成之后 string 的长度
127.0.0.1:6379> set k1 hello # 设置键值对
OK
127.0.0.1:6379> append k1 world # 追加字符串
(integer) 10 # 返回值代表追加完成之后 string 的长度
127.0.0.1:6379> get k1 # 我们可以看到, 字符串已经被追加
"helloworld"
127.0.0.1:6379> append k2 hahaha # 如果要追加的 key 不存在, 则是创建出一个新的键值对
(integer) 6
127.0.0.1:6379> get k2
"hahaha"
那如果我们追加一个中文呢 ? 返回值是多少 ?
127.0.0.1:6379> append k3 你好
(integer) 6
我们可以看到 , 返回值为 6 , 要注意 : append 返回值的长度的单位是字节
Redis 的字符串不会对字符编码做任何处理 , 也就是说 Redis 不认识字符 , 只认识字节
那为什么会打印 6 呢 ?
这是因为我们使用的 SSH 终端 , 默认的是 UTF8 编码 , 在终端输入汉字之后 , 也就是按照 UTF8 编码的 , 那一个汉字在 UTF8 字符集中 , 通常是 3 个字节的 .
那刚才也说了 , 我们 Redis 不会对字符编码做任何处理 , 那我们看看获取一下 k3 是什么样子的
127.0.0.1:6379> get k3
"\xe4\xbd\xa0\xe5\xa5\xbd"
\xe4\xbd\xa0\xe5\xa5\xbd 就是十六进制的 “你好”
我们可以在启动 Redis 客户端的时候 , 加上一个 --raw 这样的选项 , 就可以使 Redis 客户端能够自动的把二进制数据尝试翻译
那我们就可以重新启动客户端
先按 Ctrl + D 退出 Redis 客户端
不要按到 Ctrl + S , Ctrl + S 是 XShell 的一个特色功能 , 是用来锁定当前屏幕的 , 我们可以通过 Ctrl + Q 取消锁定
然后输入 redis-cli --raw 重新启动客户端
1.8 getrange
getrange 的作用是获取字符串的子串 , 也就是 Java 中的 substring
语法 : getrange key start end , 其中 start 和 end 是左闭右闭的
另外 , Redis 的下标支持负数 , 比如 -1 就代表倒数第一个元素
127.0.0.1:6379> set k1 helloworld
OK
127.0.0.1:6379> getrange k1 0 -1 # 第一个元素~最后一个元素
helloworld
127.0.0.1:6379> getrange k1 1 -2 # 第二个元素~倒数第二个元素
elloworl
那我们要是切除中文呢
127.0.0.1:6379> getrange k2 0 -1 # 第一个字符~最后一个字符 就相当于获取所有字符, 没啥问题
你好
127.0.0.1:6379> getrange k2 1 -2 # 但是如果进行切割, 那切除的结果在 utf8 码表上不知道能查出一些什么
���
1.9 setrange
setrange 的作用就是修改字符串的一部分
语法 : setrange key offset value
其中 , offset 代表偏移量 , 指的是从第几个字符开始进行替换
那什么时候结束 , 不需要我们考虑 , 这是根据 value 的长度自动判断的
返回值就代表替换之后新的字符串的长度
127.0.0.1:6379> set k1 helloworld
OK
127.0.0.1:6379> setrange k1 1 aaa # 从 1 开始, 修改成 aaa
10 # 返回值就代表替换之后新的字符串的长度
127.0.0.1:6379> get k1
haaaoworld
127.0.0.1:6379> setrange k1 1 bbbbbbb # 从 1 号位置开始, 修改成 bbbbbbb
10
127.0.0.1:6379> get k1
hbbbbbbbld
127.0.0.1:6379> setrange k1 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # 如果修改的 value 过长也会影响总长度
55
127.0.0.1:6379> get k1
hbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
如果当前咱们 value 是一个中文字符串 , 进行 setrange 的时候 , 是有可能出现问题的
127.0.0.1:6379> set k2 你好世界
OK
127.0.0.1:6379> setrange k2 1 111111
12
127.0.0.1:6379> get k2
�111111��界
如果我们针对一个不存在的 key 进行替换操作 , 会产生什么样的效果呢 ?
127.0.0.1:6379> setrange k4 1 hahaha # 从 1 号位置开始修改不存在的字符串
7 # 六个字符返回值为 7?
127.0.0.1:6379> get k4
hahaha
那我们的 hahaha 是六个字节 , 但是范围值却变成了 7 , 这是怎么回事 ?
这是因为我们设置的是从第一个元素开始进行 , 那 Redis 的实现的策略是在前面凭空生成了一个字节 , 这个字节里面的内容就是 0x00 .
1.10 strlen
strlen 的作用就是获取到字符串的长度 , 单位是字节 . 当 key 对应的 value 存储的不是 string 的时候就会报错
127.0.0.1:6379> set k1 helloworld
OK
127.0.0.1:6379> strlen k1 # 获取字符个数
10
127.0.0.1:6379> set k2 你好
OK
127.0.0.1:6379> strlen k2 # 一个汉字三个字节
6
127.0.0.1:6379> strlen k3 # 获取不存在的 key 就会返回 0
0
127.0.0.1:6379> lpush k4 111 222 333
3
127.0.0.1:6379> strlen k4 # 获取其他类型就会报错
WRONGTYPE Operation against a key holding the wrong kind of value
小结
二 . string 的编码方式
字符串的内部编码有三种 :
- int : 保存整数 , 内部是 64 位 / 8 字节 的整数
- embstr : 保存比较短的字符串
- raw : 保存比较长的字符串
我们可以通过 object encoding 命令查看当前数据的编码方式
127.0.0.1:6379> set k1 123 # 设置整数类型的字符串
OK
127.0.0.1:6379> object encoding k1 # 查看 value 的编码方式
"int"
127.0.0.1:6379> set k2 hello # 设置字符串类型的字符串(比较短的)
OK
127.0.0.1:6379> object encoding k2 # 查看 value 的编码方式
"embstr"
127.0.0.1:6379> set k3 qwertyuiopasdfghjklzxcvbnmqertyuiopasdfghjklzcvbnm # 设置字符串类型的字符串(比较长的)
OK
127.0.0.1:6379> object encoding k3 # 查看 value 的编码方式
"raw"
那如果我们存入一个小数呢 , 类型是什么 ?
127.0.0.1:6379> set k4 3.14 # 存储一个小数
OK
127.0.0.1:6379> object encoding k4
"embstr" # 我们可以看到编码方式为 embstr
Redis 存储小数是使用字符串来存储的 , 这也意味着每次进行算术运算 , 都需要把字符串先转化成小数进行运算 , 然后再将结果转化回字符串
三 . 应用场景
3.1 缓存
3.2 计数器
实际中要开发⼀个成熟、稳定的真实计数系统 , 要面临的挑战不是这么简单的 , 我们还需要考虑防作弊、按
照不同维度计数、避免单点问题、数据持久化等等
避免单点问题指的是不能只考虑当前主机的数据库 , 也要考虑分布式系统中其他主机的数据
3.3 共享会话
我们先看这种情况
如果每个应用服务器都维护自己的会话数据 , 此时各个应用服务器之间数据不共享 , 用户请求访问到不同的服务器上就可能出现一些不能正确登录的情况了
那更合理的做法就是将会话单独保存到 Redis 中 , 任何服务器要获取用户的登录状态都需要到 Redis 中获取
3.4 手机验证码
一般手机验证码的流程是这样的
- 生成验证码 : 用户输入手机号 , 然后点击获取验证码
但是一般会限制点击获取验证码 , 比如 1min 以内最多获取 5 次验证码 或者 每次获取验证码必须间隔 30s
- 检查验证码 : 把短信收到的验证码提交到系统中 , 由系统来去验证是否正确
我们可以用伪代码来理解这个问题
// phoneNumber 相当于用户的手机号
String 发送验证码 (phoneNumber) {
key = "shortMsg:limit:" + phoneNumber;
// 设置过期时间为 1 分钟 (60 秒)
// 使⽤ NX, 只在不存在 key 时才能设置成功
boolean r = Redis 执行命令: set key 1 ex 60 nx // 1 代表第一次发送
// 设置失败 -> 代表用户之前操作过验证码
if (r == false) {
// 查看一下目前已经发送的次数
// incr 返回值是用户发送验证码的次数
long c = Redis 执⾏命令: incr key // 发送失败代表用户又发了一次验证码, 所以要 +1
if (c > 5) {
// 说明超过了⼀分钟 5 次的限制了
// 限制发送
return null;
}
}
// 说明要么之前没有设置过⼿机的验证码
// 要么次数没有超过 5 次
String validationCode = ⽣成随机的 6 位数的验证码 ();
validationKey = "validation:" + phoneNumber;// 编写验证码的前缀
// 验证码 5 分钟 (300 秒) 内有效
Redis 执⾏命令: set validationKey validationCode ex 300;// 将验证码存入到 Redis 中
// 返回验证码, 随后通过手机短信发送给⽤⼾
return validationCode;
}
// 验证用户输⼊的验证码是否正确
// phoneNumber 代表手机号
// validationCode 代表用户输入的验证码
boolean 验证验证码 (phoneNumber, validationCode) {
// 根据手机号构造出 Redis 中存储的 key
validationKey = "validation:" + phoneNumber;
// 去 Redis 中查询该电话号是否存在验证码
String value = Redis 执⾏命令:get validationKey;
// 验证码不存在
if (value == null) {
// 验证失败
return false;
}
// 验证码存在
// 判断 Redis 中存储的验证码和用户输入的验证码是否相同
if (value == validationCode) {
return true;
} else {
return false;
}
}
对于 Redis 中的 string 数据类型 , 已经讲解完毕
如果对你有帮助的话 , 还请一键三连~