目录
1. String 字符串
字符串类型是 Redis 最基础的数据类型,关于字符串需要特别注意:
首先 Redis 中所有的键(key)的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他 4 种数据结构的学习奠定基础。
其次,如下图所示,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似 JSON、XML 格式的字符串;数字,可以是整型或者浮点型;甚至是二进制流数据,例如图片、⾳频、视频等。不过一个字符串的最大值不能超过 512 MB。
字符串数据类型:
1.1 String常见命令
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 存在时才进行设置,让新的 value 覆盖旧的 value,可能会改变原来的数据类型,即如果 key 之前不存在,设置不执行。
注意:由于带选项的 SET 命令可以被 setnx、 setex、 psetex等命令代替,所以之后的版本中,Redis 可能进行合并。
返回值:如果设置成功,返回 OK。如果由于 set 指定了 nx 或者 xx 但条件不满足,set 不会执行,并返回 (nil)。
FLUSHALL:表示清空所有数据(类似于 MySQL 里的 drop database)。
示例:
get
获取 key 对应的 value。如果 key 不存在,返回 nil。如果 value 的数据类型不是 string,会报错。
对于 GET 来说,只是支持字符串类型的 value,如果 value 是其他类型,那么使用 GET 获取就会出错。
语法: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 表示没有设置。
SET、SET NX 和 SET XX 执行流程:
1.2 计数命令
incr
将 key 对应的 string 表示的数字加一。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型(相当于 C++ 中的 long long),则报错。
语法:incr key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:integer 类型的加完后的数值。
incr 操作的 key 如果不存在,就会把这个 key 的 value 当作 0 来使用。
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 decrement
命令有效版本: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 服务端都要顺序执行。
1.3 其他命令
append
如果 key 已经存在并且是⼀个 string,命令会将 value 追加到原有 string 的后边。如果 key 不存在,则效果等同于 SET 命令。
语法:append key value
命令有效版本:2.0.0 之后
时间复杂度:O(1) 追加的字符串一般长度较短,可以视为 O(1)
返回值:追加完成之后 string 的长度。append 的返回值长度的单位是字节,Redis 的字符串不会对字符编码做任何处理。
示例:
当前 XShell 终端默认的字符编码是 utf-8,在终端中输入汉字之后,也就是按照 utf8 编码。一个汉字在 utf8 字符集中通常是 3 个字节的。
在启动 Redis 客户端时,加上一个 --raw 这样的选项,就可以使 Redis 客户端能够自动的把二进制数据尝试翻译。
getrange
返回 key 对应的 string 的子串,由 start 和 end 确定(左闭右闭,是闭区间)。可以使用负数表示倒数,-1 代表倒数第一个字符(下标为 len - 1 的元素),-2 代表倒数第二个,其他的与此类似。超过范围的偏移量会根据 string 的长度调整成正确的值。
语法:getrange key start end
命令有效版本:2.4.0 之后
时间复杂度:O(N) ,N 为 [start, end] 区间的长度,由于 string 通常比较短,可以视为是 O(1)
返回值:string 类型的子串
示例:
如果字符串中保存的是汉字,此时进行子串切分很可能切出来的就不是完整的汉字了。上述的代码是强行切出了中间的四个字节,这么一切,切出的结果在 utf8 码表上就不知道能查出什么了。上述问题在 C++ 中也同样存在(C++ 字符串中的基本单位是字节),需要我们手动处理。但 Java 就不会(Java 中字符串的基本单位是字符,占 2 个字节的字符),Java 中相当于 String 帮我们把汉字的编码转换都处理好了。
setrange
覆盖字符串的一部分,从指定的偏移开始。
语法:setrange key offset value
命令有效版本:2.2.0 之后
时间复杂度:O(N), N 为 value 的长度,由于一般给的 value 比较短,通常视为 O(1)。
返回值:替换后的 string 的长度。
示例:
如果 value 是一个中文字符串,进行 setrange 时是可能会出问题的。
这里凭空生成了一个字节,这个字节里的内容就是 "0x00",aaa 就被追加到 "0x00" 后面了。setange 针对不存在的 key 也是可以操作的,不过会把 offset 之前的内容填充成 "0x00"。
setlen
获取 key 对应的 string 的长度。当 key 存放的类型不是 string 时,报错。
语法:setlen key
命令有效版本:2.2.0 之后
时间复杂度:O(1)
返回值:string 的长度。或者当 key 不存在时,返回 0。单位是字节。(在 C++ 中,字符串的长度本身就是用字节为单位的)在 MySQL 中,varchar(N) 的 N 的单位就是字符,MySQL 中的字符也是完整的汉字,这样的一个字符也可能是多个字节。
示例:
1.4 String命令总结和内部编码
下表是String字符串类型命令的效果、时间复杂度:
String字符串类型的内部编码有 3 种:
- int:64 位 / 8 个字节的长整型。
- embstr:小于等于 39 个字节的字符串,压缩字符串,适用于表示比较短的字符串。
- raw:大于 39 个字节的字符串,普通字符串,适用于表示更长的字符串,只是单纯的持有字节数组。
Redis 会根据当前值的类型和长度动态决定使用哪种内部编码实现。
整型类型示例如下:
短字符串示例如下:
Redis 存储小数,本质上还是当作字符串来存储,这就和整数相比差别很大了。整数直接使用 int 来存储(准确来说是一个 long long(C++)),比较方便进行算术运算。小数则是使用字符串来存储,意味着每次进行算术运算都需要把字符串转成小数来进行运算,结果再转回字符串保存。
长字符串示例如下:
1.5 String典型使用场景
1.5.1 缓存(Cache)功能
下图是比较典型的缓存使用场景,其中 Redis 作为缓冲层,MySQL 作为存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
Redis + MySQL 组成的缓存存储架构:
整体思路:应用服务器访问数据时,先查询 Redis。如果 Redis 上数据存在,就直接从 Redis 中取出数据交给应用服务器,不继续访问数据库了。如果 Redis 上数据不存在,此时再读取 MySQL,把读到的结果返回给应用服务器,同时把这个数据也写入到 Redis 中。
上述策略存在一个明显的问题:随着时间的推移,会有越来越多的 key 在 Redis 上访问不到,从而从 MySQL 中读取并写入 Redis 了,此时 Redis 中的数据不是会越来越多吗?
解决方法:
- 在把数据写给 Redis 的同时,给这个 key 设置一个过期时间。
- Redis 也在内存不足时,提供了淘汰策略。
下面的伪代码模拟了上图的业务数据访问过程:
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 的性能明显下降的。
1.5.2 计数(Counter)功能
许多应用都会使用 Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以使用 Redis 来完成:用户每播放⼀次视频,相应的视频播放数就会自增 1。
记录视频播放次数:
这里写入统计数据仓库(可能是 MySQL,也可能是 HDFS)的步骤往往是异步的,所以并不是说来一个播放请求,这里就必须立即马上写一个数据。
// 在 Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {
key = "video:" + vid;
long count = Redis 执⾏命令:incr key
return counter;
}
实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按照不同维度计数、避免单点问题、数据持久化到底层数据源等。
1.5.3 共享会话(Session)
如下图所示,一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自的服务器中,但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。
Session 分散存储:
为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如下图所示,在这种模式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从 Redis 中查询、更新 Session 信息。
Redis 集中管理 Session:
1.5.4 手机验证码
很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过 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 的字符串类型。
2. Hash 哈希
几乎所有的主流编程语言都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中,哈希类型是指值本身又是⼀个键值对结构,形如 key = "key",value = { { field1, value1 }, ..., { fieldN, valueN } },Redis 键值对和哈希类型二者的关系可以用下图来表示。
字符串和哈希类型对比:
哈希类型中的映射关系通常称为 field-value,用于区分 Redis 整体的键值对(key-value),注意这里的 value 是指 field 对应的值,不是键(key)对应的值,请注意 value 在不同上下文的作用。
2.1 Hash 哈希常见命令
hset
设置 hash 中指定的字段(field)的值(value)。
语法:hset key field value [field value ...]
命令有效版本:2.0.0 之后
时间复杂度:插⼊一组 field 为 O(1),插⼊ N 组 field 为 O(N)
返回值:添加的字段的个数,也就是设置成功的键值对的个数。
示例:
hget
获取 hash 中指定字段的值。
语法:hget key field
命令有效版本:2.0.0 之后
时间复杂度:O(1)
返回值:字段对应的值或者 nil。
示例:
hexists
判断 hash 中是否有指定的字段。
语法:hexists key field
命令有效版本:2.0.0 之后
时间复杂度:O(1)
返回值:1 表示存在,0 表示不存在。
示例:
hdel
删除 hash 中指定的字段。
语法:hdel key field [field ...]
命令有效版本:2.0.0 之后
时间复杂度:删除一个元素为 O(1),删除 N 个元素为 O(N)。
返回值:本次操作删除的字段个数。
示例:
hkeys
获取 hash 中的所有字段。
语法:hkeys key
命令有效版本:2.0.0 之后
时间复杂度:O(N) ,N 为 field 的个数,当前的 O(N) 可以说成是 O(1)。
返回值:字段列表。
示例:
hvals
获取 hash 中的所有的值。
语法:hvals key
命令有效版本:2.0.0 之后
时间复杂度:O(N) N 为 field 的个数。如果 field(哈希)非常大,那么这个操作就可能导致 Redis 服务器被阻塞住。
返回值:所有的值。
示例:
hgetall
获取 hash 中的所有字段以及对应的值。这个操作的风险比较大,但多数情况下,我们不需要查询所有的 field,可能只查其中几个 field。
语法:hgetall key
命令有效版本:2.0.0 之后
时间复杂度:O(N), N 为 field 的个数。
返回值:字段和对应的值。
示例:
此处前面的序号仅仅是标识下返回元素的顺序,和下标无关,hash 类型没有下标的概念。
hmget
一次获取 hash 中多个字段的值。
语法:hmget key field [field ...]
命令有效版本:2.0.0 之后
时间复杂度:只查询⼀个元素为 O(1),查询多个元素为 O(N) N 为查询元素个数。
返回值:字段对应的值或者 nil。
示例:
注意:多个 value 的顺序和 field 的顺序是匹配的。
在使用命令 hkeys,hvals,hgetall 完成所有的遍历操作时,都是存在一定风险的,如果 hash 的元素个数太多,执行的耗时就比较长,那么就会存在阻塞 Redis 的可能。
如果开发人员只需要获取部分 field,可以使用 hmget,如果一定要获取全部 field,可以尝试使用 hscal 命令,该命令采用渐进式遍历哈希类型(敲一次命令,遍历一小部分,时间是可控的,连续执行多次就可以完成整个遍历过程)。
是否有 hmset 一次设置多个 field 和 value 呢?有的,但是并不需要使用,因为 hset 已经支持一次设置多个 field 和 value 了。
hlen
获取 hash 中的所有字段的个数。
语法:hlen key
命令有效版本:2.0.0 之后
时间复杂度:O(1)
返回值:字段个数。
示例:
hsetnx
在字段不存在的情况下,设置 hash 中的字段和值。
语法:hsetnx key field value
命令有效版本:2.0.0 之后
时间复杂度:O(1)
返回值:1 表示设置成功,0 表示失败。
示例:
incrby
将 hash 中字段对应的数值添加指定的值。
语法:incrby key field increment
命令有效版本:2.0.0 之后
时间复杂度:O(1)
返回值:该字段变化之后的值。
示例:
hincrbyfloat
hincrby 的浮点数版本。
语法:hincrbyfloat key field increment
命令有效版本:2.6.0 之后
时间复杂度:O(1)
返回值:该字段变化之后的值。
示例:
2.2 Hash命令总结和内部编码
下表是哈希类型命令的效果、时间复杂度:
哈希的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认 512 个)、同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时(这两个配置项是可以写到 redis.conf 文件中的),Redis 会使用 ziplist 作为哈希的内部实现,ziplist 使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比 hashtable 更加优秀。
- hashtable(哈希表):当哈希类型无法满足 ziplist 的条件时,Redis 会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度为 O(1)。
下面的示例演示了哈希类型的内部编码,以及响应的变化。
1. 当 field 个数比较少且没有大的 value 时,内部编码为 ziplist
2. 当有 value 大于 64 字节时,内部编码会转换为 hashtable
3. 当 field 个数超过 512 时,内部编码也会转换为 hashtable
2.3 Hash典型使用场景
下图为关系型数据表记录的两条用户信息,用户的属性表现为表的列,每条用户信息表现为行。
关系型数据表保存用户信息:
这里的 uid 不存储可以吗?直接使用 key 中的 id 来进行区分,存储空间不是又进一步的节省了吗?
不存储这个 uid 也可以。但是在工程实践中,一般都会把 uid 在 value 中再存一份,后续写到相关的代码时,使用起来会比较方便。
如果使用 string(JSON)的格式来表示 UserInfo,万一只想要获取其中的某个 field 或者修改某个 field,就需要把整个 JSON 都读出来,解析成对象,操作 field,再重写转成 JSON 字符串,再写回去。
相比于使用 JSON 格式的字符串缓存用户信息,哈希类型变得更加直观,并且在更新操作上变得更灵活,可以使用 field 表示对象的每个属性(数据表的每个列),此时就可以很方便的修改 / 获取任何一个属性的值了。可以将每个用户的 id 定义为键后缀,多对 field-value 对应用户的各个属性,类似如下伪代码:
UserInfo getUserInfo(long uid) {
// 根据 uid 得到 Redis 的键
String key = "user:" + uid;
// 尝试从 Redis 中获取对应的值
userInfoMap = Redis 执⾏命令:hgetall key;
// 如果缓存命中(hit)
if (value != null) {
// 将映射关系还原为对象形式
UserInfo userInfo = 利⽤映射关系构建对象(userInfoMap);
return userInfo;
}
// 如果缓存未命中(miss)
// 从数据库中,根据 uid 获取⽤⼾信息
UserInfo userInfo = MySQL 执⾏ SQL:select * from user_info where uid = <uid>
// 如果表中没有 uid 对应的⽤⼾信息
if (userInfo == null) {
响应 404
return null;
}
// 将缓存以哈希类型保存
Redis 执⾏命令:hmset key name userInfo.name age userInfo.age city userInfo.city
// 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒)
Redis 执⾏命令:expire key 3600
// 返回⽤⼾信息
return userInfo;
}
但是需要注意的是哈希类型和关系型数据库有两点不同之处:
- 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,而关系型数据库一旦添加新的列,所有行都要为其设置值,即使为 null,如下图所示。
- 关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。
关系型数据库稀疏性:
2.4 缓存方式对比
截至目前为止,我们已经能够用三种方法缓存用户信息,下面给出三种方案的实现方法和优缺点分析。
set user:1:name James
set user:1:age 23
set user:1:city Beijing
- 优点:实现简单,针对个别属性变更也很灵活。
- 缺点:占用过多的键,内存占用量较大,同时用户信息在 Redis 中比较分散,缺少内聚性,所以这种方案基本没有实用性。
2. 序列化字符串类型,例如 JSON 格式
set user:1 经过序列化后的⽤⼾对象字符串
- 优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化方案选择合适,内存的使用效率很高。
- 缺点:本身序列化和反序列需要一定开销,同时如果总是操作个别属性则非常不灵活。
3. 哈希类型
hmset user:1 name James age 23 city Beijing
- 优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。
- 缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较大消耗。
本篇完。
下一篇是Redis存储⑤Redis五大数据类型之 List 和 Set。