深入剖析Redis系列(五) - Redis数据结构之字符串

前言

字符串类型Redis 最基础的数据结构。字符串类型 的值实际可以是 字符串简单复杂 的字符串,例如 JSONXML)、数字(整数、浮点数),甚至是 二进制(图片、音频、视频),但是值最大不能超过 512MB

正文

1. 相关命令

1.1. 常见命令

1.1.1. 设置值

set key value [ex seconds] [px milliseconds] [nx|xx]

set 命令有几个选项:

  1. ex seconds:为 设置 秒级过期时间
  2. px milliseconds:为 设置 毫秒级过期时间
  3. nx:键必须 不存在,才可以设置成功,用于 添加
  4. xx:与 nx 相反,键必须 存在,才可以设置成功,用于 更新

除了 set 选项,Redis 还提供了 setexsetnx 两个命令:

setex key seconds value
setnx key value

  • setex:设定键的值,并指定此键值对应的 有效时间
1
2
3
4
5
6
127.0.0.1:6379> setex key1 5 value1
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key1
(nil)
  • setnx:键必须 不存在,才可以设置成功。如果键已经存在,返回 0
1
2
3
4
5
6
127.0.0.1:6379> set key2 value1
OK
127.0.0.1:6379> setnx key2 value2
(integer) 1
127.0.0.1:6379> get key2
"value1"

1.1.2. 获取值

get key

如果要获取的 键不存在,则返回 nil)。

1
2
127.0.0.1:6379> get not_exist_key
(nil)

1.1.3. 批量设置值

mset key value [key value …]

下面操作通过 mset 命令一次性设置 4键值对

1
2
127.0.0.1:6379> mset a 1 b 2 c 3 d 4
OK

1.1.4. 批量获取值

mget key [key …]

通过下面操作 批量获取abcd 的值:

1
2
3
4
5
127.0.0.1:6379> mget a b c d
1) "1"
2) "2"
3) "3"
4) "4"

批量操作 命令,可以有效提高 开发效率,假如没有 mget 这样的命令,要执行 nget 命令的过程和 耗时 如下:

n次get时间 = n次网络时间 + n次命令时间

使用 mget 命令后,执行 nget 命令的过程和 耗时 如下:

n次get时间 = 1次网络时间 + n次命令时间

Redis 可以支撑 每秒数万读写操作,但这指的是 Redis 服务端 的处理能力,对于 客户端 来说,一次命令除了 命令时间 还是有 网络时间

假设 网络时间1 毫秒,命令时间为 0.1 毫秒(按照每秒处理 1 万条命令算),那么执行 1000get 命令和 1mget 命令的区别如表所示:

操作 时间
1000次get操作 1000 1 + 1000 0.1 = 1100ms = 1.1s
1次mget操作 1 1 + 1000 0.1 = 101ms = 0.101s

1.1.5. 计数

incr key

incr 命令用于对值做 自增操作,返回结果分为三种情况:

  • 值不是 整数,返回 错误
  • 值是 整数,返回 自增 后的结果。
  • 键不存在,按照值为 0 自增,返回结果为 1
1
2
3
4
127.0.0.1:6379> exists key
(integer) 0
127.0.0.1:6379> incr key
(integer) 1

除了 incr 命令,Redis 还提供了 decr自减)、incrby自增指定数字)、decrby自减指定数字)、incrbyfloat自增浮点数)等命令操作:

decr key
incrby key increment
decrby key decrement
incrbyfloat key increment

很多 存储系统编程语言 内部使用 CAS 机制实现 计数功能,会有一定的 CPU 开销。但在 Redis 中完全不存在这个问题,因为 Redis单线程架构,任何命令到了 Redis 服务端 都要 顺序执行

1.2. 不常用命令

1.2.1. 追加值

append key value

append 可以向 字符串尾部 追加值。

1
2
3
4
5
6
127.0.0.1:6379> get key
"redis"
127.0.0.1:6379> append key world
(integer) 10
127.0.0.1:6379> get key
"redisworld"

1.2.2. 字符串长度

strlen key

比如说,当前值为 redisworld,所以返回值为 10

1
2
3
4
127.0.0.1:6379> get key
"redisworld"
127.0.0.1:6379> strlen key
(integer) 10

1.2.3. 设置并返回原值

getset key value

getsetset 一样会 设置值,但是不同的是,它同时会返回 键原来的值,例如:

1
2
3
4
127.0.0.1:6379> getset hello world
(nil)
127.0.0.1:6379> getset hello redis
"world"

1.2.4. 设置指定位置的字符

setrange key offeset value

下面操作将值由 pest 变为了 best

1
2
3
4
5
6
127.0.0.1:6379> set redis pest
OK
127.0.0.1:6379> setrange redis 0 b
(integer) 4
127.0.0.1:6379> get redis
"best"

1.2.5. 获取部分字符串

getrange key start end

startend 分别是 开始结束偏移量偏移量0 开始计算,例如获取值 best前两个字符 的命令如下:

1
2
127.0.0.1:6379> getrange redis 0 1
"be"

最后给出 字符串 类型命令的 时间复杂度 说明:

2. 内部编码

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

  • int8 个字节的 长整型

  • embstr小于等于 39 个字节的字符串。

  • raw大于 39 个字节的字符串。

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

  • 整数类型
1
2
3
4
127.0.0.1:6379> set key 8653
OK
127.0.0.1:6379> object encoding key
"int"
  • 短字符串
1
2
3
4
5
#小于等于39个字节的字符串:embstr
127.0.0.1:6379> set key "hello,world"
OK
127.0.0.1:6379> object encoding key
"embstr"
  • 长字符串
1
2
3
4
5
6
7
#大于39个字节的字符串:raw
127.0.0.1:6379> set key "one string greater than 39 byte........."
OK
127.0.0.1:6379> object encoding key
"raw"
127.0.0.1:6379> strlen key
(integer) 40

3. 典型使用场景

3.1. 缓存功能

下面是一种比较典型的 缓存 使用场景,其中 Redis 作为 缓存层MySQL 作为 存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑 高并发 的特性,所以缓存通常能起到 加速读写降低后端压力 的作用。

整个功能的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public UserInfo getUserInfo(long id) {
String userRedisKey = "user:info:" + id;
String value = redis.get(userRedisKey);
UserInfo userInfo;
if (value != null) {
userInfo = deserialize(value);
} else {
userInfo = mysql.get(id); if (userInfo != null) {
redis.setex(userRedisKey, 3600, serialize(userInfo));
}
return userInfo;
}
}

3.2. 计数

许多应用都会使用 Redis 作为 计数 的基础工具,它可以实现 快速计数查询缓存 的功能,同时数据可以 异步落地 到其他 数据源。一般来说,视频播放数系统,就是使用 Redis 作为 视频播放数计数 的基础组件,用户每播放一次视频,相应的视频播放数就会自增 1

1
2
3
4
public long incrVideoCounter (long id) {
String key = "video:playCount:" + id;
return redis.incr(key);
}

实际上,一个真实的 计数系统 要考虑的问题会很多:防作弊、按照 不同维度 计数,数据持久化底层数据源等。

3.3. 共享Session

一个 分布式 Web 服务将用户的 Session 信息(例如 用户登录信息)保存在 各自 的服务器中。这样会造成一个问题,出于 负载均衡 的考虑,分布式服务 会将用户的访问 均衡 到不同服务器上,用户 刷新一次访问 可能会发现需要 重新登录,这个问题是用户无法容忍的。

为了解决这个问题,可以使用 Redis 将用户的 Session 进行 集中管理。在这种模式下,只要保证 Redis高可用扩展性的,每次用户 更新 或者 查询 登录信息都直接从 Redis 中集中获取。

3.4. 限速

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

此功能可以使用 Redis 来实现,伪代码如下:

1
2
3
4
5
6
7
8
9
String phoneNum = "138xxxxxxxx";
String key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
boolean isExists = redis.set(key, 1, "EX 60", "NX");
if (isExists != null || redis.incr(key) <= 5) {
// 通过
} else {
// 限速
}

上述就是利用 Redis 实现了 限速功能,例如 一些网站 限制一个 IP 地址不能在 一秒钟之内 访问超过 n 次也可以采用 类似 的思路。

小结

本文简单的介绍了 Redis字符串数据结构基本命令内部编码相关应用场景

参考

《Redis 开发与运维》


欢迎关注技术公众号: 零壹技术栈

零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

文章作者: Chen Vainlgory
文章链接: https://geek.vainlgory.top/2018/09/24/深入剖析Redis系列(五) - Redis数据结构之字符串/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 零壹技術棧 | VainlgoryのBlog
微信打赏
支付宝打赏