Redis String 字符串详细讲解
个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创Redis String 字符串
收录于专栏[redis]
本专栏旨在分享学习Redis的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
概述
常见命令
SET
GET
编辑MGET
MSET
多次 get VS 单次 mget
SETNX
计数命令
INCR
编辑INCRBY
DECR
DECRBY
INCRBYFLOAT
其他命令
APPEND
GETRANGE
SETRANGE
STRLEN
命令小结
内部编码
经典使用场景
缓存功能
计数(Counter)功能
共享会话(Session)
手机验证码
概述
字符串类型是 Redis 最基础的数据类型,关于字符串需要特别注意:
1)首先 Redis 中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他 4 种数据结构的学习奠定基础。
2)其次,如图 2-7所示,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似 JSON、XML 格式的字符。
由于 Redis 内部存储字符串完全是按照二级制流的形式保存的,所以 Redis 是不处理字符集编码问题的,客户端传入的命令中使用的是什么字符集编码,就存储什么字符集编码。
常见命令
SET
将 string 类型的 value 设置到 key 中。如果 key 之前存在,则覆盖,无论原来的数据类型是什么。之前关于 key 的 TTL 也全部失效。
语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
命令有效版本:1.0.0 之后
时间复杂度:O(1)
选项:
SET 命令支持多种选型来影响它的行为:
EX seconds —— 使用秒作为单位设置 key 的过期时间。
PX milliseconds —— 使用毫秒作为单位设置 key 的过期时间。
NX —— 只在 key 不存在时才进行设置,即如果 key 之前已经存在,设置不执行。
XX —— 只在 key 存在时才能进行设置,即如果 key 之前不存在,设置不执行。
注意:由于带选项的 SET 命令可以被 SETNX、SETEX、PSETEX 等命令代替,所以之后的版本中,Redis 可能进行合并。
返回值:
1. 如果设置成功,返回 OK
2. 如果由于 SET 指定了 NX 或者 XX 但条件不满足,SET 不会执行,返回 nil
- 补充:清除 redis 上所有的数据(删库)=> mysql 里的 drop database,FLUSHALL 可以把 redis 上所有的键值对都带走~(以后再公司,尤其是生产环境的数据库,千万千万不敢敲!!!),不过可以在当前学习阶段,敲一敲。
示例:
如果 key 之前存在,则覆盖
NX —— 只在 key 不存在时才进行设置,即如果 key 之前已经存在,设置不执行。
XX —— 只在 key 存在时才能进行设置,即如果 key 之前不存在,设置不执行。
EX seconds —— 使用秒作为单位设置 key 的过期时间。
PX milliseconds —— 使用毫秒作为单位设置 key 的过期时间。
GET
获取 key 对应的 value。如果 key 不存在,返回 nil。如果 value 的数据类型不是 string,会报错。
语法:
GET key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:key 对应的 value,或者 nil 当 key 不存在。
示例:
MGET
一次性获取多个 key 值。如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil。
语法:
MGET key [key ...]
命令有效时间:1.0.0 之后
时间复杂度:O(N) N 是 key 数量
返回值:对应 value 的列表
示例:
MSET
一次性设置多个 key 的值。
语法:
MSET key value [key value ...]
命令有效版本:1.0.1 之后
时间复杂度:O(N) N 是 key 数量
返回值:永远是 OK
示例:
多次 get VS 单次 mget
使用 mget/mset 由于可以有效减少网络时间,所以性能相较更高。假设网络耗时 1 毫秒,命令执行时间耗时 0.1 毫秒,则执行时间如下图所示:
学会使用批量操作,可以有效提高业务处理效率,但要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单一命令执行时间过长,导致 redis 阻塞。
SETNX
设置 key-value 但只允许在 key 之前不存在的情况下。
语法:
SETNX key value
命令有效时间:1.0.0 之后
时间复杂度:O(1)
返回值:1 表示设置成功。0 表示没有设置。
SETNX,SETEX,PSEREX 同样如此,就是针对 set 的一些常见用法,进行了缩写。之所以这样做,就是为了操作更符合人的直觉。
计数命令
INCR
将 key 对应的 string 表示的数字加一。如果 key 不存在,则视为 key对应的 value 是0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
语法:
INCR key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型加完后的数值。
示例:
INCRBY
将 key 对应的 string 表示的数字加上对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整形,则报错。
语法:
INCRBY key decrement
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的加完后的数值。
示例:
DECR
将 key 对应的 string 表示的数字减一。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整数或者整型范围超过 64 位有符号整型,则报错。
DECR key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的减完后的数值。
示例:
DECRBY
将 key 对应的 string 表示的数字减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整数,则报错。
语法:
DECRBY key decrment
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的减完后的数值。、
示例:
INCRBYFLOAT
将 key 对应的 string 表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的不是 string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。
语法:
INCRBYFLOAT key increment
命令有效版本:2.6.0 之后
时间复杂度:O(1)
返回值:加/减完之后的数值。
示例:
注意:很多存储系统和编程语言内部使用 CAS 机制实现计数功能,会有一定的 CPU 开销,但在 Redis 中完全不存在这个问题,因为 Redis 是单线程架构,任何命令到了 Redis 服务端就要顺序执行。
incr 操作的 key 如果不存在,就会把这个 key 的 value 当做 0 来使用。
其他命令
APPEND
如果 key 已经存在并且是一个 string,命令会将 value 追加到原有 string 后边。如果 key 不存在,则效果等同于 SET 命令。
语法:
APPEND KEY VALUE
命令有效版本:2.0.0 之后
时间复杂度:O(1) 追加的字符串一般长度较短,可以视为 O(1)
返回值:追加完成后 string 的长度。
示例:
GETRANGE
返回 key 对应的 string 的字串,由 start 和 end 确定(左闭右闭)。可以使用负数表示倒数。-1 代表倒数第一个字符,-2 代表倒数第二个,其他的与此类似。超过范围的偏移量会根据 string 的长度调整正确的值。
语法:
GETRANGE key start end
命令有效版本:2.4.0 之后
时间复杂度:O(N) N 为 [start, end] 区间的长度,由于 string 通常比较短,可以视为是 O(1)
返回值:string 类型的字串
示例:
SETRANGE
覆盖字符串的一部分,从指定的偏移量开始。
语法:
SETRANGE key offset value
命令有效版本:2.2.0 之后
时间复杂度:O(N) N 为 value 的长度,由于一般给的 value 比较短,通常视为 O(1)
返回值:替换后的 string 的长度。
示例:
STRLEN
获取 key 对应的 string 的长度。当 key 存放的类似不是 string 时,报错。
语法:
STRLEN key
命令有效时间:2.2.0 之后
时间复杂度:O(1)
返回值:string 的长度。或者当 key 不存在时,返回 0。
示例:
命令小结
下面是字符串类型命令的效果、时间复杂度,开发人员可以参考此表,结合自身业务需求和数据大小选择合适的命令。
内部编码
字符串类型的内部编码有三种:
1. int :8个字节的长整型
2. embstr:小于等于 39 个字节的字符串。
3. raw :大于 39 个字节的字符串
Redis 会根据当前值的类型和长度动态决定使用哪种内部编码实现。
整型类型示例如下:
短字符串示例如下:
长字符串示例如下:
经典使用场景
缓存功能
下图是比较典型的缓存使用场景,其中 Redis 作为缓冲层,MySQL 作为存储层,绝大部分请
求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
下面的伪代码模拟了图 2-10 的业务数据访问过程:
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 的访问数。
与 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)
如图 2-12 所示,一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自
的服务器中,但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。
为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如图所示,在这种模式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从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 的字符串类型。