String 字符串
聊到Redis,String类型绝对是出镜率最高的数据类型,没有之一。它简单归简单,但背后那些计数、追加、截取之类的操作,用好了能省不少事。下面咱们把常用的命令挨个过一遍,顺便聊聊实际场景中怎么用最顺手。
计数命令
INCR:针对value+1
这个命令顾名思义,就是把 key 对应的值当作数字,然后加 1。如果 key 本身不存在,Redis 会默认它的值是 0,然后再加 1,相当于直接设置成 1。但如果 key 里面存的东西不是整数,或者数值超出了 64 位有符号整型的范围,那就直接报错了,不会给你含糊。
语法:
INCR key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的加完后的数值(64位/8字节表示的整数,相当于C++中的long long,Ja va中的long)。
示例:
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 (integer) 1

INCRBY:针对value+n
跟 INCR 类似,只不过加多少由你指定。注意,你要是想减,也可以传一个负数进去,比如 INCRBY key -1,效果就相当于减1。不过 Redis 后来还是单独设计了 DECR 和 DECRBY 命令,因为光看名字,INCRBY 传负数总觉得别扭,不够“人性化”。
语法:
INCRBY key decrement
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的加完后的数值。
示例:
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

不过话说回来,虽然 INCRBY 传负数能实现减法,但设计上还是分开成了 DECR 和 DECRBY,毕竟从命名上更直观,谁也不想看到“加”命令里传个负数去减。
DECR:针对value-1
这个就是减1,跟 INCR 对应。同样,key 不存在就当作0,减完变成-1。
语法:
DECR key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的减完后的数值。
示例:
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

DECRBY:针对value-n
顾名思义,减指定的数值。同样,key 不存在时当作0,然后减去你给的值。
语法:
DECRBY key decrement
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的减完后的数值。
示例:
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

INCRBYFLOAT:针对value+/-小数
这个命令是专门处理浮点数的,想加想减都行,传负数就是减。支持科学计数法,比如 5.0e3 表示5000。key 不存在时当作0,然后加你指定的浮点数。
语法:
INCRBYFLOAT key increment
命令有效版本:2.6.0 之后
时间复杂度:O(1)
返回值:加/减完后的数值。
示例:
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"

很多存储系统内部用 CAS 机制实现计数,会有 CPU 开销,但在 Redis 里完全不用担心这个问题——因为 Redis 是单线程架构,命令都是顺序执行的,不存在并发竞争,计数操作天然就是原子性的。
其他命令
APPEND:追加子串
这个命令就是在字符串后面追加内容。如果 key 不存在,效果等同于 SET。注意,返回的是追加后字符串的长度,单位是字节。Redis 的字符串不关心字符编码,它只认识字节。比如你在 XShell 终端里输入汉字,默认是 UTF-8 编码,一个汉字通常占 3 个字节。所以追加汉字后,长度会按字节数增加。
语法:
APPEND KEY VALUE
命令有效版本:2.0.0 之后
时间复杂度:O(1)。追加的字符串一般长度较短,可以视为 O(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) 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 "哇" (integer) 20 127.0.0.1:6379> get mykey "Helloworld hahahaxe5x93x87"

如果你在启动 Redis 客户端时加上 --raw 选项,客户端就会自动将二进制数据尝试翻译成可读的字符,比如汉字就能正常显示了:
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,这个快捷键在 XShell 里是“冻结当前画面”,按了之后终端会卡住不动,按 Ctrl+Q 才能解除冻结。
GETRANGE:返回范围子串
返回 key 对应字符串的一个子串,范围由 start 和 end 指定,注意是左闭右闭区间。可以用负数,-1 表示最后一个字符,-2 表示倒数第二个,以此类推。如果指定的范围超出字符串实际长度,Redis 会自动调整到合法范围。如果你切的是汉字,就得小心了——一个汉字在 UTF-8 下是 3 个字节,如果你从中间切开,出来的可能就不是正常字符了。
语法:
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:覆盖范围子串
用指定的字符串从偏移量 offset 开始覆盖原字符串。如果 offset 超出了原字符串长度,Redis 会用空字节(\x00)填充中间的部分。如果 key 不存在,也会先创建空字符串再填充。
语法:
SETRANGE key offset value
命令有效版本:2.2.0 之后
时间复杂度: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" 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 (integer) 4 127.0.0.1:6379> get key2 "\x00aaa"



STRLEN:获取子串长度
返回 key 对应字符串的长度,单位是字节。注意,不是字符数。比如一个汉字在 UTF-8 下是 3 个字节,那 strlen 返回的就是 3。如果 key 不存在,返回 0。如果 key 存的不是字符串,会报错。
语法:
STRLEN key
命令有效版本:2.2.0 之后
时间复杂度:O(1)
返回值:string 的长度。或者当 key 不存在时,返回 0。
示例:
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

命令小结
下面这张表汇总了字符串类型常用命令的效果和时间复杂度,开发时可以根据业务需求和数据大小选合适的命令。注意,O(k) 通常可以当作 O(1),因为 k 一般是你自己输入的值,不会太大。

内部编码
Redis 字符串类型实际存储时,内部有三种编码方式:
- int:8 个字节的长整型。
- embstr:小于等于 39 个字节的字符串(在 Redis 3.0 之后,这个临界值是 44 字节)。适用于比较短的字符串。
- raw:大于 39 个字节的字符串。
Redis 会根据当前值的类型和长度,动态选择最合适的编码方式。比如,整数直接用 int 存储,方便算术运算;小数则当作字符串存储,因为每次计算需要转换,性能不如整数。
整型示例:
127.0.0.1:6379> set key 666 OK 127.0.0.1:6379> object encoding key "int"
短字符串示例:
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 OK 127.0.0.1:6379> object encoding key8 "embstr"
长字符串示例:
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 ........." OK 127.0.0.1:6379> object encoding key4 "embstr" 127.0.0.1:6379> set key5 "one string greater than 39 bytes .........." OK 127.0.0.1:6379> object encoding key5 "embstr" 127.0.0.1:6379> set key6 "one string greater than 39 bytes ..........." OK 127.0.0.1:6379> object encoding key6 "embstr" 127.0.0.1:6379> set key7 "one string greater than 39 bytes ............" OK 127.0.0.1:6379> object encoding key7 "raw"

典型使用场景
缓存(Cache)功能
这是 Redis 最经典的用法之一。Redis 作为缓冲层,MySQL 作为存储层,绝大部分请求的数据直接从 Redis 获取,只有缓存未命中时才会去查 MySQL。由于 Redis 支持高并发,缓存能显著加速读写,同时降低后端数据库的压力。

下面的伪代码模拟了典型的业务数据访问流程:
- 根据用户 uid 获取用户信息。
UserInfo getUserInfo(long uid) {
...
}
- 先从 Redis 获取,假设用户信息保存在 "user:info:
" 这个键中:
String key = "user:info:" + uid;
String value = Redis 执行命令:get key;
if (value != null) {
// 假设用户信息是 JSON 格式
UserInfo userInfo = JSON 反序列化(value);
return userInfo;
}
- 如果缓存未命中,则从 MySQL 查询,然后写入 Redis 并返回:
if (value == null) {
UserInfo userInfo = MySQL 执行 SQL:select * from user_info where uid =
if (userInfo == null) {
响应 404
return null;
}
String value = JSON 序列化(userInfo);
// 设置过期时间 1 小时,防止数据腐烂
Redis 执行命令:set key value ex 3600
return userInfo;
}
理想情况下,每个用户信息每小时只需要查一次 MySQL,效率提升非常明显。这背后其实暗含了一个假设:某个数据一旦被访问,短时间内很可能被反复访问——也就是热点数据。
不过这个策略有个问题:随着时间的推移,Redis 里的 key 会越来越多,内存肯定扛不住。解决办法有两个:一是给每个 key 设置过期时间;二是依赖 Redis 本身的内存淘汰策略,在内存不足时自动清理不常用的数据。
另外,关于键名的设计,Redis 没有表、字段这种命名空间,所以键名最好有清晰的业务含义。推荐格式:业务名:对象名:唯一标识:属性。比如 vs:user_info:6379:name。如果当前 Redis 只服务一个业务,可以省略业务名。如果键名过长,可以用团队内约定的缩写,比如 user:6379:friends:messages:5217 可以缩写成 u:6379:fr:m:5217,毕竟过长的键名会影响性能。
计数(Counter)功能
很多应用用 Redis 做计数工具,比如视频播放次数、点赞数等。用户每播放一次视频,对应的计数就自增 1。Redis 的 INCR 命令是原子操作,非常方便。

long incrVideoCounter(long vid) {
key = "video:" + vid;
long count = Redis 执行命令:incr key
return counter;
}
当然,真实的计数系统要考虑的远不止这些:防作弊、多维度统计、高可用、数据持久化等等,但 Redis 作为计数底座,地位不可替代。
共享会话(Session)
在分布式 Web 服务中,如果每个服务器各自保存用户的 Session 信息,就会出现一个问题:用户请求被负载均衡到不同服务器时,可能因为 Session 不共享而需要重新登录。解决办法是把 Session 集中存到 Redis 里,所有 Web 服务器都从 Redis 读写 Session,这样无论请求落到哪台机器,体验都一致。


手机验证码
很多 App 在登录时要求输入手机号并接收验证码,为了防止刷信息,通常会限制每分钟发送次数,比如不能超过 5 次。用 Redis 可以轻松实现这个限流逻辑:

伪代码说明:
String 发送验证码(phoneNumber) {
key = "shortMsg:limit:" + phoneNumber;
// 设置过期时间为 1 分钟,使用 NX 确保只有第一次能设置成功
bool r = Redis 执行命令:set key 1 ex 60 nx
if (r == false) {
// 说明之前已经设置过,计数加 1
long c = Redis 执行命令:incr key
if (c > 5) {
// 超过限制,返回 null
return null;
}
}
String validationCode = 生成随机的 6 位数的验证码();
validationKey = "validation:" + phoneNumber;
// 验证码 5 分钟有效
Redis 执行命令:set validationKey validationCode ex 300;
return validationCode;
}
bool 验证验证码(phoneNumber, validationCode) {
validationKey = "validation:" + phoneNumber;
String value = Redis 执行命令:get validationKey;
if (value == null) {
return false;
}
return value.equals(validationCode);
}
以上只是字符串类型的几个典型场景。实际开发中,只要理解了它的特点——简单、高效、原子性,再加上一点点想象力,很多业务场景都能找到用武之地。
