Redis中String类型的计数其他命令
作者:流星白龙
String 字符串
计数命令
INCR:针对value+1
将 key 对应的 string 表示的数字加一。
如果 key 不存在,则视为 key 对应的 value 是 0。
如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
INCR key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的加完后的数值。(64位/8字节表示的整数,相当于C++中的long long,Java中的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>

INCRBY:针对value+n
将 key 对应的 string 表示的数字加上对应的值。可以加上一个负数,也就是减。
如果 key 不存在,则视为 key 对应的 value 是 0。
如果 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可以实现下面的减,但是可能不够人性化,别人可能会觉得,如果可以减那干嘛取名加呢?
所以虽然可以实现减,但是还是分开来设计了这几个命令。
DECR:针对value-1
将 key 对应的 string 表示的数字减一。
如果 key 不存在,则视为 key 对应的 value 是 0。
如果 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>

DECYBY:针对value-n
将 key 对应的 string 表示的数字减去对应的值。
如果 key 不存在,则视为 key 对应的 value 是 0。
如果 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>

INCRBYFLOAT:针对value+/-小数
将 key 对应的 string 表示的浮点数加上对应的值。
如果对应的值是负数,则视为减去对应的值。
如果 key 不存在,则视为 key 对应的 value 是 0。
如果 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 服务端都要顺序执行。
其他命令
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+s在xshell中的作用是“冻结当前画面”。
ctrl+q解除冻结。
GETRANGE:返回范围子串
返回 key 对应的 string 的子串,由 start 和 end 确定(左闭右闭)。
可以使用负数表示倒数。-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>

SETRANGE:覆盖范围子串
覆盖字符串的一部分,从指定的偏移开始。
语法:
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 # 对不存在的key进行setrange操作 (integer) 4 127.0.0.1:6379> get key2 "\x00aaa" # \x00其实就是凭空生成了一个字节,这个字节内的内容就是0x00,aaa就被追加到这个0x00的后面了 127.0.0.1:6379>



STRLEN:获取子串长度
获取 key 对应的 string 的长度。(单位是字节)
- C++中字符串的长度本身是用字节为单位的
- Java中的char基于unicode这样的编码方式就能够表示中文等符号,一个char是2字节,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>

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

内部编码
字符串类型的内部编码有 3 种:
- int:8 个字节的长整型。
- 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>

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

下面的伪代码模拟了上图的业务数据访问过程:
- 假设业务是根据用户 uid 获取用户信息
UserInfo getUserInfo(long uid) {
...
}
- 首先从
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;
}
- 如果没有从
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 的访问数。
上述描述的过程,相当于是把最近使用到的数据作为热点数据。(暗含了一层假设:某个数据一旦被用到了,那么很可能在最近这段时间就会被反复用到)
不过上述策略,存在一个明显的问题:
随着时间的推移,肯定是会有越来越多的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 的性能明显下降的。
计数(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 信息进行集中管理,如下图所示。
在这种模式下,只要保证 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 的字符串类型。
到此这篇关于Redis中String类型的计数其他命令的文章就介绍到这了,更多相关Redis String计数命令内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
