【Redis】6.计数其他命令

📅 2026/7/23 9:10:19 👁️ 阅读次数 📝 编程学习
【Redis】6.计数其他命令

文章目录

  • 2. Redis 常见数据类型
    • 2.2 String 字符串
      • 2.2.2 计数命令
        • 2.2.2.1 INCR:针对value+1
        • 2.2.2.2 INCRBY:针对value+n
        • 2.2.2.3 DECR:针对value-1
        • 2.2.2.4 DECYBY:针对value-n
        • 2.2.2.5 INCRBYFLOAT:针对value+/-小数
      • 2.2.3 其他命令
        • 2.2.3.1 APPEND:追加子串
        • 2.2.3.2 GETRANGE:返回范围子串
        • 2.2.3.3 SETRANGE:覆盖范围子串
        • 2.2.3.4 STRLEN:获取子串长度
      • 2.2.4 命令小结
      • 2.2.5 内部编码
      • 2.2.6 典型使用场景
        • 2.2.6.1 缓存(Cache)功能
        • 2.2.6.2 计数(Counter)功能
        • 2.2.6.3 共享会话(Session)
        • 2.2.6.4 手机验证码

2. Redis 常见数据类型

2.2 String 字符串

2.2.2 计数命令

2.2.2.1 INCR:针对value+1

key对应的string表示的数字加一。

如果key不存在,则视为key对应的value0

如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。

语法:

INCR key

命令有效版本:1.0.0之后

时间复杂度:O(1)

返回值:integer类型的加完后的数值。(64位/8字节表示的整数,相当于C++中的long longJava中的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 # incr操作的key如果不存在。就会把这个key的value当作0来使用 (integer) 1 127.0.0.1:6379>


2.2.2.2 INCRBY:针对value+n

key对应的string表示的数字加上对应的值。可以加上一个负数,也就是减。

如果key不存在,则视为key对应的value0

如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。

语法:

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 127.0.0.1:6379>

事实上,虽然incrby可以实现下面的减,但是可能不够人性化,别人可能会觉得,如果可以减那干嘛取名加呢?

所以虽然可以实现减,但是还是分开来设计了这几个命令。


2.2.2.3 DECR:针对value-1

key对应的string表示的数字减一。

如果key不存在,则视为key对应的value0

如果key对应的string不是一个整型,或者范围超过了64位有符号整型,则报错。

语法:

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 127.0.0.1:6379>


2.2.2.4 DECYBY:针对value-n

key对应的string表示的数字减去对应的值。

如果key不存在,则视为key对应的value0

如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。

语法:

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 127.0.0.1:6379>


2.2.2.5 INCRBYFLOAT:针对value+/-小数

key对应的string表示的浮点数加上对应的值。

如果对应的值是负数,则视为减去对应的值。

如果key不存在,则视为key对应的value0

如果key对应的不是string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。

语法:

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" 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个字节的。

语法:

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 "哇" # 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客户端能够自动的把二进制数据尝试翻译。

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+sxshell中的作用是“冻结当前画面”。

ctrl+q解除冻结。


2.2.3.2 GETRANGE:返回范围子串

返回key对应的string的子串,由startend确定(左闭右闭)。

可以使用负数表示倒数。-1代表倒数第一个字符,-2代表倒数第二个,其他的与此类似。(可以认为是len-1个元素)

超过范围的偏移量会根据string的长度调整成正确的值。

如果字符串保存的是汉字,这个时候随便(指的是一个汉字3个字节,但是你给他切开来了)进行字串切分可能切出来的不是正常的汉字。

redis中指定的区间,是闭区间。但是C++Java中大部分的区间都是前闭后开。

语法:

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 # 从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:覆盖范围子串

覆盖字符串的一部分,从指定的偏移开始。

语法:

SETRANGE key offset value

命令有效版本:2.2.0之后

时间复杂度:O(N),Nvalue的长度。由于一般给的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 # 对不存在的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这样的编码方式就能够表示中文等符号,一个char2字节,string使用utf8一个汉字3字节
  • MySQL的时候,varchar(N),此处N的单位就是字符,也是完整的汉字,这样的一个字符,也可能是多个字节

key存放的类似不是string时,报错。

语法:

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 127.0.0.1:6379>


2.2.4 命令小结

下表是字符串类型命令的效果、时间复杂度,开发人员可以参考此表,结合自身业务需求和数据大小选择合适的命令。

字符串类型命令小结:

O(k)可以当作O(1),因为这里的k是自己输入的个数,我们也不可能输入太多个。


2.2.5 内部编码

字符串类型的内部编码有3种:

  • int8个字节的长整型。
  • embstr:小于等于39个字节的字符串。适用于比较短的字符串。
  • raw:大于39个字节的字符串。在Redis 3.0及之后的版本中,判断字符串使用embstr还是raw临界值是 44 字节

Redis会根据当前值的类型和长度动态决定使用哪种内部编码实现。

整型类型示例如下:

127.0.0.1:6379> set key 666 OK 127.0.0.1:6379> object encoding key "int" 127.0.0.1:6379>

短字符串示例如下:

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>

长字符串示例如下:

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组成的缓存存储架构图:

下面的伪代码模拟了上图的业务数据访问过程:

  1. 假设业务是根据用户uid获取用户信息
UserInfogetUserInfo(longuid){...}
  1. 首先从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);returnuserInfo;}
  1. 如果没有从Redis中得到用户信息,及缓存miss,则进一步从MySQL中获取对应的信息,随后写入缓存并返回:
// 如果缓存未命中(miss)if(value==null){// 从数据库中,根据 uid 获取用户信息UserInfo userInfo=MySQL 执行 SQL:select*from user_info where uid=<uid>// 如果表中没有 uid 对应的用户信息if(userInfo==null){响应404returnnull;}// 将用户信息序列化成 JSON 格式String value=JSON 序列化(userInfo);// 写入缓存,为了防止数据腐烂(rot),设置过期时间为 1 小时(3600 秒)Redis 执行命令:set key value ex3600// 返回用户信息returnuserInfo;}

通过增加缓存功能,在理想情况下,每个用户信息,一个小时期间只会有一次MySQL查询,极大地提升了查询效率,也降低了MySQL的访问数。

上述描述的过程,相当于是把最近使用到的数据作为热点数据。(暗含了一层假设:某个数据一旦被用到了,那么很可能在最近这段时间就会被反复用到)

不过上述策略,存在一个明显的问题:

随着时间的推移,肯定是会有越来越多的keyredis上访问不到,从而从mysql读取并写入redis了,此时redis中的数据不是就越来越多嘛?

  1. 在把数据写给redis的同时,给这个key设置一个过期时间
  2. 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

记录视频播放次数:

// 在 Redis 中统计某视频的播放次数longincrVideoCounter(longvid){key="video:"+vid;longcount=Redis 执行命令:incr keyreturncounter;}

实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按照不同维度计数、避免单点问题、数据持久化到底层数据源等。


2.2.6.3 共享会话(Session)

如下图所示,一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自的服务器中。

但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

Session分散存储:

为了解决这个问题,可以使用Redis将用户的Session信息进行集中管理,如下图所示。

在这种模式下,只要保证Redis是高可用和可扩展性的,无论用户被均衡到哪台Web服务器上,都集中从Redis中查询、更新Session信息。

Redis集中管理Session


2.2.6.4 手机验证码

很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。

为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次,如下图所示。

短信验证码:

此功能可以用以下伪代码说明基本实现思路:

String 发送验证码(phoneNumber){key="shortMsg:limit:"+phoneNumber;// 设置过期时间为 1 分钟(60 秒)// 使用 NX,只在不存在 key 时才能设置成功bool r=Redis 执行命令:set key1ex60nxif(r==false){// 说明之前设置过该手机的验证码了longc=Redis 执行命令:incr keyif(c>5){// 说明超过了一分钟 5 次的限制了// 限制发送returnnull;}}// 说明要么之前没有设置过手机的验证码;要么次数没有超过 5 次String validationCode=生成随机的6位数的验证码();validationKey="validation:"+phoneNumber;// 验证码 5 分钟(300 秒)内有效Redis 执行命令:set validationKey validationCode ex300;// 返回验证码,随后通过手机短信发送给用户returnvalidationCode;}// 验证用户输入的验证码是否正确bool 验证验证码(phoneNumber,validationCode){validationKey="validation:"+phoneNumber;String value=Redis 执行命令:get validationKey;if(value==null){// 说明没有这个手机的验证码记录,验证失败returnfalse;}if(value==validationCode){returntrue;}else{returnfalse;}}

以上介绍了使用Redis的字符串数据类型可以使用的几个场景,但其适用场景远不止于此,开发人员可以结合字符串类型的特点以及提供的命令,充分发挥自己的想象力,在自己的业务中去找到合适的场景去使用Redis的字符串类型。