参考资料 : 《Redis 设计与实现》
- String
- 字符数组
- 数值型【int】
- bitmap
- List
- Set
- Sorted Set
- Hash
Help
redis-cli 中,使用 help 可以查看帮助,不同帮助按照组划分,@
前缀
# 通用组
@generic
# 不同数据类型
@string
@list
@set
@sorted_set
@hash
@pubsub
@transactions
@connection
@server
@scripting
@hyperloglog
@cluser
@geo
@stream
String
基本操作
# 设置一个键值
set key val
# 获取一个键的值
get key
# 已存在就不设置
setnx key val
# 同时设置超时时间
setex key second val
# 设置多个
mset key val [key val...]
# 取出多个
mget key [key...]
# 设置多个值,如果都不存在【原子性的】
msetnx key val [key val...]
字符串操作
# 追加
append key content
# 截取一部分【这里支持反向索引,如 getrange k1 0 -1 获取全部】
getrange key start end
# 取旧的值,设置新的值
getset
# 设置一部分
setrange key start end
# 获取长度
strlen key
计算操作
# 自增 1
incr key
# 自增指定值
incrby key num
# 自减 1
decr key
# 自减指定值
decrby key num
# 增加浮点数值
incrbyfloat key num
位图操作 [bitmap]
# 设置某一位【这里的 val 只能是 0 或 1 】
setbit key offset val
# 查看某一位的二进制值
getbit key offset
# 返回 key 的 val 中在 [start, end] 中间,第一次出现 bit 的位置 【start end指的是字节】
bitpos key bit [start] [end]
# 统计 [start, end] 中间出现了几个 1,【start end指的是字节】
bitcount key [start end]
# 对两个 key 的 val 进行按位 与[and] 或[or] 非[not] 异或[xor],并将结果保存在 destkey
bitop operation destkey key [key ...]
Redis 中按照字节存储内容,每个字节有8个字符,其中字符之间也有 offset
供我们做一些位操作
位图操作的应用场景
-
有一个用户系统,需要统计每个用户发生了登录行为的天数
一个用户的存储成本:每年 365 天,对应到 365 bit = 45 byte + 5bit
setbit sean 1 1
:用户 Sean 第 1 天登录了setbit sean 7 1
:用户 Sean 第 7 天登录了setbit sean 364 1
:用户 Sean 第 364 天登录了bitcount sean -2 -1
:统计用户年末的 8 + 5 = 13 天内登录了几次 -
京东 618 活动,凡是在 6月18日当天登录的客户都送礼物,需要根据平时的访问量来准备礼物数,统计活跃用户数。
先将一亿用户,映射到 [1, 1亿] 的区间上,以日期做 key 存储那天的用户登录情况
setbit 20190101 1 1
:20190101 那天,用户 1 登录了setbit 20190102 1 1
:20190102 那天,用户 1 登录了setbit 20190102 2 1
:20190102 那天,用户 2 登录了bitop or dest 20190101 20190102
: 20190101 或 20190102 两天内的活跃用户数bitcount dest 0 -1
Redis 中,对于 String 类型的 encoding
有三种,分别是
- int 数值型
- embstr
- raw 太大的数值(44位以上)
查看其编码可用如下命令:object encoding key
set k1 hello
strlen k1 # 5
set k2 9
object encoding k2 # int
strlen k2 # 2
append k2 999
get k2 # 9999
object encoding k2 # raw 这里演示了一些操作会把 string 的 encoding 改变
incr k2 # 10000
object encoding k2 # int
strlen k2 # 5
上述命令中,后面 k2 在 incr 之后,Redis 将其重新标记为了 int 型,但是在 strlen 时,还是占5个字节。这里证实了 Redis 是 二进制安全 的。redis 存储数据就是一个字符一个字节,当 k2 接收到了 incr 命令时,先查看自己的 encoding 是 int,那么可以直接进行自增操作,如果 发现其是 raw,则先尝试将其转换为 int 再进行自增操作。
set k3 a
strlen k3 # 1
append k3 中
strlen k3 # 4
这里将 k3 追加了一个中文字符后,其长度变为了4个字节,可以看出 中
这个汉字在 Redis 中占用了三个字节。然而这并不是 redis 决定的。这完全取决于 当前客户端对 中文字符使用何种编码集,当不同客户端分别使用 GBK 编码集和 UTF-8 编码集时,设置的中文取出后就变成乱码了。所以必须统一 redis 客户端使用的编码集。
String 的底层结构
Redis 中的 String 是 简单动态字符串 Simple Dynamic String
,简称 SDS
。
SDS 的结构如下所示
struct sdshdr {
// 记录 buf 中已使用的字节数量
int len;
// 记录 buf 中剩余的字节数量
int free;
// 字节数组,保存字符串
char buf[];
}
redis 在保存一个字符串时,也沿用了 C 语言中字符串结尾是 \0
的这一惯例,目的是为了能直接使用 C 字符串函数库中的一部分函数。所以 SDS 实际占用空间 = len + free + 1 【字节】
SDS 的特点:
STRLEN
获取一个字符串的长度,时间复杂度为 O(1) 。- 【C语言中要遍历直到遇到
\0
,时间复杂度为 O(N)】
- 【C语言中要遍历直到遇到
- 杜绝缓冲区溢出,试图向一个 SDS append【sdscat命令】 之前,SDS API 会先判断 free 字段够不够,不够自动扩容。
- 【C语言中需要程序员关系是否有足够的内存空间用于 append】
- SDS 在进行空间分配时,会预留一部分空间 (free) -> 空间预分配
空间预分配:
- 如果 SDS 被修改过后的 len 值小于 1MB,将会分配
free = len
大小的预留空间; - 如果 SDS 被修改过后的 len 值大于 1MB,将会分配额外 1MB 的预留空间;
- 有效地减少扩容次数
惰性空间释放:
在执行 sdstrim 函数,移除 SDS 两端的指定字符串时,SDS 会将执行后的结果更新到 buf[] 中,并且更新 len 的值和 free 的值,并不会真正进行空间的释放。SDS 提供了相应 API 执行真正的空间释放。
SDS 与C 字符串对比
C 字符串 | SDS |
---|---|
获取字符串长度时间复杂度 O(N) | O(1) |
API 可能造成缓冲区溢出 | API 不会造成缓冲区溢出 |
修改字符串长度 N 次要进行 N 次内存分配 | 修改字符串长度 N 次至多进行 N 次内存分配 |
只能保存文本数据 | 可以保存二进制数据 |
可以使用全部 <string.h> 中的函数 | 可以使用部分 <string.h> 中的函数 |