目录
字符串类型是 Redis 最基础的数据类型,关于字符串需要特别注意:
1) 首先 Redis 中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他4种数据结构的学习奠定基础。
2) 其次,如下图所示,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似JSON、XML格式的字符串; 数字,可以是整型或者浮点型; 甚至是二进制流数据,例如图片、音频、视频等。不过一个字符串的最大值不能超过 512 MB。
1. 常见命令
SET
将 string 类型的 value 设置到 key 中。如果 key之前存在,则覆盖,无论原来的数据类型是什么。之前关于此 key 的 TTL 也全部失效。
语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
时间复杂度:0(1)
选项:
SET 命令支持多种选项来影响它的行为:
注意: 由于带选项的 SET命令可以被 SETNX、SETEX、PSETEX 等命令代替,所以之后的版本
中,Redis 可能进行合并。
返回值:
- 如果设置成功,返回 OK。
- 如果由于 SET指定了 NX 或者 XX但条件不满足,SET不会执行,并返回(nil)。
示例:
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> set mykey "hello"
OK
127.0.0.1:6379> get mykey
"hello"
127.0.0.1:6379> set mykey "world" nx # key不存在才设置
(nil)
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> set mykey "world" xx # key存在才设置
(nil)
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379> set mykey "world" nx # key不存在才设置
OK
127.0.0.1:6379> get mykey
"world"
127.0.0.1:6379> set mykey "will expire in 10s" ex 10 # 设置过期时间
OK
127.0.0.1:6379> get mykey
"will expire in 10s"
127.0.0.1:6379> get mykey # 10s之后
(nil)
GET
获取 key 对应的 value。如果 key不存在,返回 nil。如果 value 的数据类型不是 string,会报错。
语法:
时间复杂度:O(1)
返回值: key对应的 value,或者 nil 当 key 不存在。
示例:
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379> set mykey "hello"
OK
127.0.0.1:6379> get mykey
"hello"
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> hset mykey name Bob
(integer) 1
127.0.0.1:6379> get mykey
(error) WRONGTYPE Operation against a key holding the wrong kind of value
MGET
一次性获取多个 key 的值。如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil。
语法:
MGET key [key ...]
时间复杂度: O(N) N 是 key数量
返回值: 对应 value 的列表
示例:
127.0.0.1:6379> set key1 "hello"
OK
127.0.0.1:6379> set key2 "world"
OK
127.0.0.1:6379> mget key1 key2 noexisting
1) "hello"
2) "world"
3) (nil)
MSET
一次性设置多个 key 的值。
语法:
MSET key value [key value ...]
时间复杂度: O(N) N 是 key数量
返回值: 永远是 OK
示例:
127.0.0.1:6379> mset key1 "value1" key2 "value2"
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key2
"value2"
多次 get vs 单次 mget
使用 mget/mset 由于可以有效地减少了网络时间,所以性能相较更高。假设网络耗时 1毫秒,命令执行时间耗时 0.1毫秒,则执行时间如表所示。
1000次 get 和1次 mget 对比
学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单一命令执行时间过长,导致 Redis 阻塞。
SETNX
设置 key-value 但只允许在 key 之前不存在的情况下。
语法:
SETNX key value
时间复杂度: 0(1)
返回值: 1表示设置成功。0表示没有设置。
示例:
127.0.0.1:6379> setnx key "hello"
(integer) 1
127.0.0.1:6379> setnx key "world"
(integer) 0
127.0.0.1:6379> get key
"hello"
SET、SET NX和 SETXX 的执行流程如图所示。
图 SET、SET NX、SETXX执行流程
2. 计数命令
INCR
将 key 对应的 string表示的数字加一。如果 key不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
INCR key
时间复杂度:O(1)
返回值: integer类型的加完后的数值。
示例:
127.0.0.1:6379> setnx key "hello"
(integer) 1
127.0.0.1:6379> setnx key "world"
(integer) 0
127.0.0.1:6379> get key
"hello"
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> incr mykey
(integer) 1
127.0.0.1:6379> set mykey "10"
OK
127.0.0.1:6379> incr mykey
(integer) 11
127.0.0.1:6379> set mykey "999999999999999999999999999999"
OK
127.0.0.1:6379> incr mykey
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set mykey "not a number"
OK
127.0.0.1:6379> incr mykey
(error) ERR value is not an integer or out of range
INCRBY
将 key 对应的 string表示的数字加上对应的值。如果 key不存在,则视为 key 对应的 value是0。如
果 key 对应的 string不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
INCRBY key decrement
时间复杂度:O(1)
返回值: integer类型的加完后的数值。
示例:
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> incrby mykey 3
(integer) 3
127.0.0.1:6379> set mykey "10"
OK
127.0.0.1:6379> incrby mykey 4
(integer) 14
127.0.0.1:6379> incrby mykey "not a number"
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set mykey "999999999999999999999999999"
OK
127.0.0.1:6379> incrby mykey 3
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set mykey "not a number"
OK
127.0.0.1:6379> incrby mykey 3
(error) ERR value is not an integer or out of range
DECR
将 key 对应的 string表示的数字减一。如果 key不存在,则视为 key对应的 value 是 0。如果 key对
应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
DECR key
时间复杂度: O(1)
返回值: integer类型的减完后的数值。
示例:
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> decr mykey
(integer) -1
127.0.0.1:6379> set mykey "10"
OK
127.0.0.1:6379> decr mykey
(integer) 9
127.0.0.1:6379> set mykey "999999999999999999999999"
OK
127.0.0.1:6379> decr mykey
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set mykey "not a number"
OK
127.0.0.1:6379> decr mykey
(error) ERR value is not an integer or out of range
DECYBY
将 key 对应的 string表示的数字减去对应的值。如果 key不存在,则视为 key对应的 value是 0。如
果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
DECRBY key decrement
时间复杂度:0(1)
返回值:integer类型的减完后的数值。
示例:
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> decrby mykey 3
(integer) -3
127.0.0.1:6379> set mykey "10"
OK
127.0.0.1:6379> decrby mykey 3
(integer) 7
127.0.0.1:6379> decrby mykey "not a number"
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set mykey "9999999999999999999999999"
OK
127.0.0.1:6379> decrby mykey 3
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set mykey "not a number"
OK
127.0.0.1:6379> decrby mykey 3
(error) ERR value is not an integer or out of range
INCRBYFLOAT
将 key 对应的 string表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的不是 string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。
语法:
INCRBYFLOAT key increment
时间复杂度: O(1)
返回值: 加/减完后的数值。
示例:
127.0.0.1:6379> set mykey "10.50"
OK
127.0.0.1:6379> incrbyfloat mykey 0.1
"10.6"
127.0.0.1:6379> incrbyfloat mykey -5
"5.6"
127.0.0.1:6379> set mykey 1.0e3
OK
127.0.0.1:6379> incrbyfloat mykey 2.0e2
"1200"
很多存储系统和编程语言内部使用 CAS 机制实现计数功能,会有一定的 CPU开销,但在 Redis 中完全不存在这个问题,因为 Redis 是单线程架构,任何命令到了 Redis 服务端都要顺序执行。
3. 其他命令
APPEND
如果 key已经存在并且是一个 string,命令会将 value 追加到原有 string 的后边。如果 key 不存在
则效果等同于 SET 命令。
语法:
APPEND KEY VALUE
时间复杂度:0(1). 追加的字符串一般长度较短, 可以视为 0(1)
返回值: 追加完成之后 string 的长度。
示例:
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> append mykey "hello"
(integer) 5
127.0.0.1:6379> get mykey
"hello"
127.0.0.1:6379> append mykey " world"
(integer) 11
127.0.0.1:6379> get mykey
"hello world"
GETRANGE
返回 key 对应的 string的子串,由 start和end确定(左闭右闭)。可以使用负数表示倒数。-1代表
倒数第一个字符,-2代表倒数第二个,其他的与此类似。超过范围的偏移量会根据 string 的长度调整成正确的值。
语法:
GETRANGE key start end
命令有效版本:2.4.0 之后
时间复杂度:O(N). N为[start,end]区间的长度. 由于 string通常比较短, 可以视为是 O(1)
返回值: string 类型的子串
示例:
127.0.0.1:6379> set mykey "This is a string"
OK
127.0.0.1:6379> getrange mykey 0 3
"This"
127.0.0.1:6379> getrange mykey -3 -1
"ing"
127.0.0.1:6379> getrange mykey 0 -1
"This is a string"
127.0.0.1:6379> getrange mykey 10 100
"string"
SETRANGE
覆盖字符串的一部分,从指定的偏移开始。
语法:
SETRANGE key offset value
时间复杂度: O(N), N为 value 的长度. 由于一般给的 value 比较短, 通常视为 O(1).
返回值: 替换后的 string 的长度。
示例:
127.0.0.1:6379> set key1 "hello world"
OK
127.0.0.1:6379> setrange key1 6 "redis"
(integer) 11
127.0.0.1:6379> get key1
"hello redis"
STRLEN
获取 key 对应的 string 的长度。当 key 存放的类似不是 string 时,报错。
语法:
时间复杂度: O(1)
返回值: string的长度。或者当 key 不存在时,返回 0。
示例:
127.0.0.1:6379> set mykey "hello world"
OK
127.0.0.1:6379> strlen mykey
(integer) 11
127.0.0.1:6379> strlen noexisting
(integer) 0
4. 命令小结
下表是字符串类型命令的效果、时间复杂度,开发人员可以参考此表,结合自身业务需求和数
据大小选择合适的命令。
字符串类型命令小结
5. 内部编码
字符串类型的内部编码有3种:
Redis 会根据当前值的类型和长度动态决定使用哪种内部编码实现。
整型类型示例如下:
127.0.0.1:6379> set key 666
OK
127.0.0.1:6379> object encoding key
"int"
短字符串示例如下:
# 小于等于 39 个字节的字符串
127.0.0.1:6379> set key "hello"
OK
127.0.0.1:6379> object encoding key
"embstr"
长字符串示例如下:
# 大于 39 个字节的字符串
127.0.0.1:6379> set key "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
OK
127.0.0.1:6379> object encoding key
"raw"
6. 典型使用场景
缓存(Cache) 功能
下图是比较典型的缓存使用场景,其中 Redis 作为缓冲层,MySQL作为存储层,绝大部分请
求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
Redis+MySQL组成的缓存存储架构
1) 假设业务是根据用户 uid 获取用户信息
UserInfo getUserInfo(long uid) {
...
}
2) 首先从 Redis 获取用户信息,我们假设用户信息保存在"user:info:<uid>"对应的键中:
// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执⾏命令:get key;
// 如果缓存命中(hit)
if (value != null) {
// 假设我们的⽤⼾信息按照 JSON 格式存储
UserInfo userInfo = JSON 反序列化(value);
return userInfo;
}
3) 如果没有从 Redis 中得到用户信息,及缓存 miss,则进一步从 MySQL中获取对应的信息,随后写入缓存并返回:
// 如果缓存未命中(miss)
if (value == null) {
// 从数据库中,根据 uid 获取⽤⼾信息
UserInfo userInfo = MySQL 执⾏ SQL:select * from user_info where uid = <uid>
// 如果表中没有 uid 对应的⽤⼾信息
if (userInfo == null) {
响应 404
return null;
}
// 将⽤⼾信息序列化成 JSON 格式
String value = JSON 序列化(userInfo);
// 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒)
Redis 执⾏命令:set key value ex 3600
// 返回⽤⼾信息
return userInfo;
}
通过增加缓存功能,在理想情况下,每个用户信息,一个小时期间只会有一次 MySQL查询,极大地提升了查询效率,也降低了 MySQL的访问数。
计数(Counter)功能
许多应用都会使用 Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数
据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以使用
Redis 来完成:用户每播放一次视频,相应的视频播放数就会自增 1。
记录视频播放次数
// 在 Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {
key = "video:" + vid;
long count = Redis 执⾏命令:incr key
return counter;
}
共享会话(Session)
如下图所示,一个分布式 Web 服务将用户的 Session 信息 (例如用户登录信息) 保存在各自的服务器中,但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到
不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。
Session 分散存储
为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如图 2-13所示,在这种模式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从Redis 中查询、更新Session 信息。
Redis 集中管理 Session
手机验证码
很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码,
然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次,如下图所示。
短信验证码
此功能可以用以下伪代码说明基本实现思路:
String 发送验证码(phoneNumber) {
key = "shortMsg:limit:" + phoneNumber;
// 设置过期时间为 1 分钟(60 秒)
// 使⽤ NX,只在不存在 key 时才能设置成功
bool r = Redis 执⾏命令:set key 1 ex 60 nx
if (r == false) {
// 说明之前设置过该⼿机的验证码了
long c = Redis 执⾏命令:incr key
if (c > 5) {
// 说明超过了⼀分钟 5 次的限制了
// 限制发送
return null;
}
}
// 说明要么之前没有设置过⼿机的验证码;要么次数没有超过 5 次
String validationCode = ⽣成随机的 6 位数的验证码();
validationKey = "validation:" + phoneNumber;
// 验证码 5 分钟(300 秒)内有效
Redis 执⾏命令:set validationKey validationCode ex 300;
// 返回验证码,随后通过⼿机短信发送给⽤⼾
return validationCode ;
}
// 验证⽤⼾输⼊的验证码是否正确
bool 验证验证码(phoneNumber, validationCode) {
validationKey = "validation:" + phoneNumber;
String value = Redis 执⾏命令:get validationKey;
if (value == null) {
// 说明没有这个⼿机的验证码记录,验证失败
return false;
}
if (value == validationCode) {
return true;
} else {
return false;
}
}
以上介绍了使用 Redis 的字符串数据类型可以使用的几个场景,但其适用场景远不止于此,开发
人员可以结合字符串类型的特点以及提供的命令,充分发挥自己的想象力,在自己的业务中去找到合适的场景去使用 Redis 的字符串类型。