文章目录
- [2. Redis 常见数据类型](#2. Redis 常见数据类型)
-
- [2.2 String 字符串](#2.2 String 字符串)
-
- [2.2.2 计数命令](#2.2.2 计数命令)
-
- [2.2.2.1 INCR:针对value+1](#2.2.2.1 INCR:针对value+1)
- [2.2.2.2 INCRBY:针对value+n](#2.2.2.2 INCRBY:针对value+n)
- [2.2.2.3 DECR:针对value-1](#2.2.2.3 DECR:针对value-1)
- [2.2.2.4 DECYBY:针对value-n](#2.2.2.4 DECYBY:针对value-n)
- [2.2.2.5 INCRBYFLOAT:针对value+/-小数](#2.2.2.5 INCRBYFLOAT:针对value+/-小数)
- [2.2.3 其他命令](#2.2.3 其他命令)
-
- [2.2.3.1 APPEND:追加子串](#2.2.3.1 APPEND:追加子串)
- [2.2.3.2 GETRANGE:返回范围子串](#2.2.3.2 GETRANGE:返回范围子串)
- [2.2.3.3 SETRANGE:覆盖范围子串](#2.2.3.3 SETRANGE:覆盖范围子串)
- [2.2.3.4 STRLEN:获取子串长度](#2.2.3.4 STRLEN:获取子串长度)
- [2.2.4 命令小结](#2.2.4 命令小结)
- [2.2.5 内部编码](#2.2.5 内部编码)
- [2.2.6 典型使用场景](#2.2.6 典型使用场景)
-
- [2.2.6.1 缓存(Cache)功能](#2.2.6.1 缓存(Cache)功能)
- [2.2.6.2 计数(Counter)功能](#2.2.6.2 计数(Counter)功能)
- [2.2.6.3 共享会话(Session)](#2.2.6.3 共享会话(Session))
- [2.2.6.4 手机验证码](#2.2.6.4 手机验证码)
2. Redis 常见数据类型
2.2 String 字符串
2.2.2 计数命令
2.2.2.1 INCR:针对value+1
将
key对应的string表示的数字加一。如果
key不存在,则视为key对应的value是0。如果
key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。
语法:
mysql
INCR key
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
integer类型的加完后的数值。(64位/8字节表示的整数,相当于C++中的long long,Java中的long)
示例:
mysql
127.0.0.1:6379> EXISTS mykey
(integer) 1
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 "10"
OK
127.0.0.1:6379> INCR mykey
(integer) 11
127.0.0.1:6379> SET mykey "46756767986967967977987987"
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
127.0.0.1:6379> del mykey
(integer) 0
127.0.0.1:6379> INCR mykey # incr操作的key如果不存在。就会把这个key的value当作0来使用
(integer) 1
127.0.0.1:6379>

2.2.2.2 INCRBY:针对value+n
将
key对应的string表示的数字加上对应的值。可以加上一个负数,也就是减。如果
key不存在,则视为key对应的value是0。如果
key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。
语法:
mysql
INCRBY key decrement
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
integer类型的加完后的数值。
示例:
mysql
127.0.0.1:6379> del mykey
(integer) 1
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 3
(integer) 13
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> INCRBY mykey 3
(integer) 16
127.0.0.1:6379> SET mykey "234293482390480948029348230948"
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
127.0.0.1:6379> SET mykey "10"
OK
127.0.0.1:6379> INCRBY mykey -1 # 加上一个负数
(integer) 9
127.0.0.1:6379>

事实上,虽然
incrby可以实现下面的减,但是可能不够人性化,别人可能会觉得,如果可以减那干嘛取名加呢?所以虽然可以实现减,但是还是分开来设计了这几个命令。
2.2.2.3 DECR:针对value-1
将
key对应的string表示的数字减一。如果
key不存在,则视为key对应的value是0。如果
key对应的string不是一个整型,或者范围超过了64位有符号整型,则报错。
语法:
mysql
DECR key
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
integer类型的减完后的数值。
示例:
mysql
127.0.0.1:6379> del mykey
(integer) 1
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 "767678678678564545454586867868678678"
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
127.0.0.1:6379>

2.2.2.4 DECYBY:针对value-n
将
key对应的string表示的数字减去对应的值。如果
key不存在,则视为key对应的value是0。如果
key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。
语法:
mysql
DECRBY key decrement
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
integer类型的减完后的数值。
示例:
mysql
127.0.0.1:6379> del mykey
(integer) 1
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> DECRBY mykey 3
(integer) 4
127.0.0.1:6379> SET mykey "76879879879879878798676767676798"
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
127.0.0.1:6379>

2.2.2.5 INCRBYFLOAT:针对value+/-小数
将
key对应的string表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。
如果
key不存在,则视为key对应的value是0。如果
key对应的不是string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。
语法:
mysql
INCRBYFLOAT key increment
命令有效版本:
2.6.0之后时间复杂度:
O(1)返回值:加/减完后的数值。
示例:
mysql
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> INCRBYFLOAT mykey 0.1
"0.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 5.0e3
OK
127.0.0.1:6379> INCRBYFLOAT mykey 2.0e2
"5200"
127.0.0.1:6379> SET mykey 10.50
OK
127.0.0.1:6379> INCRBYFLOAT mykey -50
"-39.5"
127.0.0.1:6379>

很多存储系统和编程语言内部使用
CAS机制实现计数功能,会有一定的CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任何命令到了Redis服务端都要顺序执行。
2.2.3 其他命令
2.2.3.1 APPEND:追加子串
如果
key已经存在并且是一个string,命令会将value追加到原有string的后边。如果
key不存在,则效果等同于SET命令。
append返回值,长度的单位是字节。
redis的字符串,不会对字符编码做任何处理。(redis不认识字符,只认识字节)当前使用的
XShell终端,默认的字符编码是utf8。在终端中输入汉字之后,也就是按照utf8编码的。一个汉字在utf8字符集中,通常是3个字节的。
语法:
mysql
APPEND KEY VALUE
命令有效版本:
2.0.0之后时间复杂度:
O(1)。追加的字符串一般长度较短, 可以视为O(1)返回值:追加完成之后
string的长度。
示例:
mysql
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) 10
127.0.0.1:6379> get mykey
"Helloworld"
127.0.0.1:6379> APPEND mykey " hahaha"
(integer) 17
127.0.0.1:6379> get mykey
"Helloworld hahaha"
127.0.0.1:6379> APPEND mykey "哇" # XShell中汉字utf8编码,通常3字节
(integer) 20
127.0.0.1:6379> get mykey
"Helloworld hahaha\xe5\x93\x87"
127.0.0.1:6379>

在启动
redis客户端的时候,加上一个--raw这样的选项。就可以使
redis客户端能够自动的把二进制数据尝试翻译。
mysql
127.0.0.1:6379> quit
root@yudukai:~# redis-cli --raw
127.0.0.1:6379> keys *
mykey
127.0.0.1:6379> get mykey
Helloworld hahaha哇
127.0.0.1:6379>

操作
linux的时候,千万注意,不要乱按ctrl+s
ctrl+s在xshell中的作用是"冻结当前画面"。
ctrl+q解除冻结。
2.2.3.2 GETRANGE:返回范围子串
返回
key对应的string的子串,由start和end确定(左闭右闭)。可以使用负数表示倒数。
-1代表倒数第一个字符,-2代表倒数第二个,其他的与此类似。(可以认为是len-1个元素)超过范围的偏移量会根据
string的长度调整成正确的值。如果字符串保存的是汉字,这个时候随便(指的是一个汉字
3个字节,但是你给他切开来了)进行字串切分可能切出来的不是正常的汉字。
redis中指定的区间,是闭区间。但是C++和Java中大部分的区间都是前闭后开。
语法:
mysql
GETRANGE key start end
命令有效版本:
2.4.0之后时间复杂度:
O(N)。N为[start, end]区间的长度。由于string通常比较短, 可以视为是O(1)返回值:
string类型的子串
示例:
mysql
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 # 从0号元素(最开始元素)到-1号元素(最后元素)
"This is a string"
127.0.0.1:6379> GETRANGE mykey 10 100
"string"
127.0.0.1:6379>

2.2.3.3 SETRANGE:覆盖范围子串
覆盖字符串的一部分,从指定的偏移开始。
语法:
mysql
SETRANGE key offset value
命令有效版本:
2.2.0之后时间复杂度:
O(N),N为value的长度。由于一般给的value比较短, 通常视为O(1)。返回值:替换后的
string的长度。
示例:
mysql
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"
127.0.0.1:6379> setrange key1 1 a
(integer) 11
127.0.0.1:6379> get key1
"Hallo Redis"
127.0.0.1:6379> setrange key1 10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 57
127.0.0.1:6379> get key1
"Hallo Rediaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
127.0.0.1:6379> setrange key2 1 aaa # 对不存在的key进行setrange操作
(integer) 4
127.0.0.1:6379> get key2
"\x00aaa" # \x00其实就是凭空生成了一个字节,这个字节内的内容就是0x00,aaa就被追加到这个0x00的后面了
127.0.0.1:6379>



2.2.3.4 STRLEN:获取子串长度
获取
key对应的string的长度。(单位是字节)
C++中字符串的长度本身是用字节为单位的Java中的char基于unicode这样的编码方式就能够表示中文等符号,一个char是2字节,string使用utf8一个汉字3字节MySQL的时候,varchar(N),此处N的单位就是字符,也是完整的汉字,这样的一个字符,也可能是多个字节当
key存放的类似不是string时,报错。
语法:
mysql
STRLEN key
命令有效版本:
2.2.0之后时间复杂度:
O(1)返回值:
string的长度。或者当key不存在时,返回0。
示例:
mysql
127.0.0.1:6379> exists mykey
(integer) 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 nonexisting
(integer) 0
127.0.0.1:6379>

2.2.4 命令小结
下表是字符串类型命令的效果、时间复杂度,开发人员可以参考此表,结合自身业务需求和数据大小选择合适的命令。
字符串类型命令小结:
O(k)可以当作O(1),因为这里的k是自己输入的个数,我们也不可能输入太多个。

2.2.5 内部编码
字符串类型的内部编码有
3种:
int:8个字节的长整型。embstr:小于等于39个字节的字符串。适用于比较短的字符串。raw:大于39个字节的字符串。在Redis 3.0及之后的版本中,判断字符串使用embstr还是raw的临界值是 44 字节
Redis 会根据当前值的类型和长度动态决定使用哪种内部编码实现。
整型类型示例如下:
mysql
127.0.0.1:6379> set key 666
OK
127.0.0.1:6379> object encoding key
"int"
127.0.0.1:6379>
短字符串示例如下:
mysql
127.0.0.1:6379> set key1 "hello"
OK
127.0.0.1:6379> object encoding key1
"embstr"
127.0.0.1:6379> set key8 1.5 # redis存储小数,本质上还是把小数当作字符串来存储。与整数相比差别比较大了,整数直接使用int来存储,比较方便算数运算;小数用字符串存储,每次计算需要先转换成小数计算,然后计算结果转换成zi'f'x
OK
127.0.0.1:6379> object encoding key8
"embstr"
127.0.0.1:6379>
长字符串示例如下:
mysql
127.0.0.1:6379> set key2 "one string greater than 39 bytes ........"
OK
127.0.0.1:6379> object encoding key2
"embstr"
127.0.0.1:6379> set key3 "one string greater than 39 bytes ........................................................."
OK
127.0.0.1:6379> object encoding key3
"raw"
127.0.0.1:6379> set key4 "one string greater than 39 bytes ........." # 刚好42字符
OK
127.0.0.1:6379> object encoding key4
"embstr"
127.0.0.1:6379> set key5 "one string greater than 39 bytes .........." # 刚好43字符
OK
127.0.0.1:6379> object encoding key5
"embstr"
127.0.0.1:6379> set key6 "one string greater than 39 bytes ..........." # 刚好44字符
OK
127.0.0.1:6379> object encoding key6
"embstr"
127.0.0.1:6379> set key7 "one string greater than 39 bytes ............" # 刚好45字符
OK
127.0.0.1:6379> object encoding key7
"raw"
127.0.0.1:6379>

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

下面的伪代码模拟了上图的业务数据访问过程:
- 假设业务是根据用户
uid获取用户信息
c
UserInfo getUserInfo(long uid) {
...
}
- 首先从
Redis获取用户信息,我们假设用户信息保存在 "user:info:<uid>" 对应的键中:
c
// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执行命令:get key;
// 如果缓存命中(hit)
if (value != null) {
// 假设我们的用户信息按照 JSON 格式存储
UserInfo userInfo = JSON 反序列化(value);
return userInfo;
}
- 如果没有从
Redis中得到用户信息,及缓存miss,则进一步从MySQL中获取对应的信息,随后写入缓存并返回:
c
// 如果缓存未命中(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的访问数。
上述描述的过程,相当于是把最近使用到的数据作为热点数据。(暗含了一层假设:某个数据一旦被用到了,那么很可能在最近这段时间就会被反复用到)
不过上述策略,存在一个明显的问题:随着时间的推移,肯定是会有越来越多的
key在redis上访问不到,从而从mysql读取并写入redis了,此时redis中的数据不是就越来越多嘛?
- 在把数据写给
redis的同时,给这个key设置一个过期时间Redis也在内存不足的时候,提供了淘汰策略
与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的性能明显下降的。
2.2.6.2 计数(Counter)功能
许多应用都会使用
Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以使用
Redis来完成:用户每播放一次视频,相应的视频播放数就会自增1。
记录视频播放次数:

c
// 在 Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {
key = "video:" + vid;
long count = Redis 执行命令:incr key
return counter;
}
实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按照不同维度计数、避免单点问题、数据持久化到底层数据源等。
2.2.6.3 共享会话(Session)
如下图所示,一个分布式
Web服务将用户的Session信息(例如用户登录信息)保存在各自的服务器中。但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。
Session 分散存储:

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

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

此功能可以用以下伪代码说明基本实现思路:
c
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的字符串类型。