基础命令
1. Strings
redis 对此描述为 binary-safe strings。可以看出, strings 类型在 redis 中是以 二进制 形式存储,这意味着 redis strings 可以用来存储任何类型的数据,甚至是一张图片。一个 string value 最大可以为 512M
redis 使用 sds(simple dynamic string) struct 来存储 strings 数据。
<code>struct
sdshdr
{unsigned
int
len;unsigned
int
free
;char
buf[]; }/<code>
初始化时,free 为 0, 当 append key value 时, redis 会对该存储空间扩容,扩容大小 = new_value.length * 2 ,但最大不能超过 1M (redis 中定义为 SDS_MAX_PREALLOC)。
这样, redis 就能十分方便的获取 string 长度(O(1)),减少内存扩容。
同样,这种策略会预分配内存,产生不必要的内存浪费,所以当我们经常 append huge strings 时,就会产生很多不必要的浪费。
redis 在 3.2 版本后,对此做了优化,会根据 string 的长度,选择不同的 SDS type
<code>static
inline
char
sdsReqType
(
size_t
string_size) {if
(string_size1
5
)return
SDS_TYPE_5;if
(string_size1
8
)return
SDS_TYPE_8;if
(string_size1
16
)return
SDS_TYPE_16;if
(string_size1l
l32
)return
SDS_TYPE_32;return
SDS_TYPE_64;return
SDS_TYPE_32; }/<code>
strings 指令:
inrc / decr / incrby / decrby :原子递增/递减
可以用来统计访问量 / 点赞量 / 转发量 等各种熟路
<code>127.0
.0
.1
:6379>
set
counter
18
OK
127.0
.0
.1
:6379>
INCR
counter
(integer)
19
127.0
.0
.1
:6379>
DECR
counter
(integer)
18
127.0
.0
.1
:6379>
INCRBY
counter
18
(integer)
36
127.0
.0
.1
:6379>
DECRBY
counter
18
(integer)
18
127.0
.0
.1
:6379>
get
counter
"18"
/<code>
get / set / getset / append : 获取 / 设置 / 设置同时返回值 / 追加
mget / mset : 批量获取 / 设置
<code>127.0
.0
.1
:6379>
MSET
k1
v1
k2
v2
k3
v3
OK
127.0
.0
.1
:6379>
MGET
k1
k2
k3
1
)
"v1"
2
)
"v2"
3
)
"v3"
/<code>
setex : 设置 value 和过期时间
<code>127.0
.0
.1
:6379
>SETEX
exp1
10expv1
OK
# 获取过期时间, 还有 7 秒 127.0
.0
.1
:6379
>ttl
exp1
(integer
) 7 127.0
.0
.1
:6379
>get
exp1
"expv1
" # 此时查询时已过期 127.0
.0
.1
:6379
>get
exp1
(nil
)/<code>
setnx / strlen : set not exists,即没有时,才生效 / 获取 value 长度
<code># 无nx
这个key
时, 设置成功 127.0
.0
.1
:6379
>SETNX
nx
nxv
(integer
) 1 127.0
.0
.1
:6379
>get
nx
"nxv
" # 有nx
这个key
时, 设置失败 127.0
.0
.1
:6379
>SETNX
nx
nxvvvvvv
(integer
) 0 127.0
.0
.1
:6379
>get
nx
"nxv
" # 获取key
为nx
的value
长度 127.0
.0
.1
:6379
>STRLEN
nx
(integer
) 3 127.0
.0
.1
:6379
>get
nx
"nxv
"/<code>
更多 strings 相关命令参见 这里
2. Lists
redis 的 list 是 linkedList , 是根据插入顺寻排列的 string 元素的集合, 一个 list 最大长度为 232 -1 。
list 节点 / 迭代器 / list 定义如下:
<code>typedef
struct
listNode
{struct
listNode
*prev
;struct
listNode
*next
;void
*value; } listNode; typedef
struct
listIter
{ listNode *next;int
direction; } listIter; typedef
struct
list
{ listNode *head; listNode *tail;void
*(*dup)(void
*ptr);void
(*free
)(void
*ptr);int
(*match)(void
*ptr,void
*key);unsigned
long
len; }list
;/<code>
由此可见, redis 中的 list 是一个双向链表。同时会保存头尾节点和长度,所以获取他们的时间复杂度都为 O(1)
所以, LPUSH,RPUSH, RPOP, LPOP ,RPOPLPUSH , LLEN 等操作都十分高效
Lists 指令:
LPUSH / RPUSH : 从头部 / 尾部插入元素
<code>127.0
.0
.1
:6379>
LPUSH
list
l1
l2
(integer)
2
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"l2"
2
)
"l1"
127.0
.0
.1
:6379>
RPUSH
list
r1
r2
(integer)
4
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"l2"
2
)
"l1"
3
)
"r1"
4
)
"r2"
127.0
.0
.1
:6379>
del
list
/<code>
LRANGE : 从列表中拿出一个 range 。
- 注意这个 range 是 闭合 的,比如你执行 lrange list 0 1 ,即 [0, 1] 而非 [0, 1) 。
- 负数表示倒数, 如 [0, -1] ,表示从 0 到最后一个数,即该 list 的全部元素
<code>127.0
.0
.1
:6379>
RPUSH
list
v1
v2
v3
(integer)
3
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"v1"
2
)
"v2"
3
)
"v3"
127.0
.0
.1
:6379>
del
list
/<code>
我们结合 LPUSH + LRANGE ,可以简单的实现 timeline 功能
LTRIM:从头部截取指定长度 slice 并存入该 key 中
<code>127.0
.0
.1
:6379>
lpush
list
v1
v2
v3
(integer)
3
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"v3"
2
)
"v2"
3
)
"v1"
127.0
.0
.1
:6379>
LTRIM
list
0
1
OK
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"v3"
2
)
"v2"
127.0
.0
.1
:6379>
del
list
/<code>
LPUSH + LTRIM 的组合,可以实现一个简单的定长 list ,该 list 维护指定长度的最新元素
LINSERT : 在指定元素前/后插入原色
<code>127.0
.0
.1
:6379>
lpush
list
val
(integer)
1
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"val"
127.0
.0
.1
:6379>
linsert
list
before
val
val-before
(integer)
2
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"val-before"
2
)
"val"
127.0
.0
.1
:6379>
LINSERT
list
after
val
val-after
(integer)
3
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"val-before"
2
)
"val"
3
)
"val-after"
127.0
.0
.1
:6379>
LINSERT
list
before
noexist
noexist
(integer)
-1
127.0
.0
.1
:6379>
lrange
list
0
-1
1
)
"val-before"
2
)
"val"
3
)
"val-after"
127.0
.0
.1
:6379>
del
list
/<code>
LPOP / RPOP
拿到并删除 头部 / 尾部 元素
<code>127.0
.0
.1
:6379>
rpush
list
head
middle
tail
(integer)
3
127.0
.0
.1
:6379>
lrange
list
0
-1
1
)
"head"
2
)
"middle"
3
)
"tail"
127.0
.0
.1
:6379>
LPOP
list
"head"
127.0
.0
.1
:6379>
RPOP
list
"tail"
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"middle"
127.0
.0
.1
:6379>
del
list
/<code>
LREM key count element : 删除元素
- count > 0 时, 从头到尾 删除与 element 相等的元素, 删除 count 个
- count < 0 时, 从尾到头 删除与 element 相等的元素, 删除 count 个
- count = 0 时, 删除所有与 element 相等的元素
<code>127.0
.0
.1
:6379>
RPUSH
list
1
2
1
2
1
2
(integer)
6
127.0
.0
.1
:6379>
LREM
list
1
2
(integer)
1
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"1"
2
)
"1"
3
)
"2"
4
)
"1"
5
)
"2"
127.0
.0
.1
:6379>
LREM
list
-1
2
(integer)
1
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"1"
2
)
"1"
3
)
"2"
4
)
"1"
127.0
.0
.1
:6379>
LREM
list
0
1
(integer)
3
127.0
.0
.1
:6379>
LRANGE
list
0
-1
1
)
"2"
/<code>
RPOPLPUSH source dest
取出 source 尾节点放到 dest 的头节点
- 如果 source 不存在,返回 nil , 不执行任何操作
- 如果 dest 不存在,创建 dest,并执行操作
- 如果 source tail 元素 和 dest head 元素一致,依旧会 push
- 如果 source 和 dest 是同一个 list ,这个 list 就变成了一个 circular list
<code>127.0
.0
.1
:6379>
LRANGE
source
0
-1
1
)
"s"
2
)
"s"
127.0
.0
.1
:6379>
RPOPLPUSH
noexist
source
(nil)
127.0
.0
.1
:6379>
LRANGE
source
0
-1
1
)
"s"
2
)
"s"
127.0
.0
.1
:6379>
keys
*
1
)
"source"
127.0
.0
.1
:6379>
RPOPLPUSH
source
dest
"s"
127.0
.0
.1
:6379>
LRANGE
dest
0
-1
1
)
"s"
127.0
.0
.1
:6379>
RPOPLPUSH
source
dest
"s"
127.0
.0
.1
:6379>
LRANGE
dest
0
-1
1
)
"s"
2
)
"s"
/<code>
BLPOP key1 key2 key3... timeout
LPOP 的阻塞版本,即 key1 , key2, key3 ... 任一监听的 key 中都没有值时,会阻塞到 timeout ,timeout 为 0 表示一直阻塞
- key1 -> key2 -> key3 按序查找,直到找到值
- 多个 client 监听同一个 key 时,按监听时间的早晚,依次返回给 client ,client 接收到后,下次监听需要重新排队
- multi 操作一次塞入多个值时,会先执行完 multi 操作,然后返回并删除头节点
BRPOP / BRPOPLPUSH
BRPOP 功能参考 BLPOP, 即阻塞版本的 RPOP,获取并删除尾节点
BRPOPLPUSH 即阻塞版本的 RPOPLPUSH ,从 source 获取尾节点并放到 dest 的头部
更多 lists 的操作命令参考 这里
3. Sets
sets 底层主要基于 hash 表和 inset 数据结构实现。
无序且去重的 set 集合,最大可存储 232 -1 个元素。
SADD key ele1 ele2 ele3 ...
SCARD / SMEMBERS key
SISMEMBER key ele
添加 / 获取 set 元素数量 / 获取 set 所有元素 / 确定一个元素是否在集合中
SDIFF key1 key2 key3 ...
SDIFFSTORE dest key1 key2
- sdiff :获取 key1 与 key2, key3 的 差集,即 key2 不存在与 key2, key3 中的元素
- sdiffstore:与 LRPOPLPUSH 类似,将 sdiff 的结果放到 dest set 中,注意,如果 dest 中有元素,则元素会被清空
SINTER key key1 key
SINTERSTORE dest key key1 key2
与 sdiff /sdiffstore 类似,这组命令是取 交集
SUNION key1 key2 key3 ...
SUNIONSTORE dest key1 key2 key3 ...
同样,这里取并集
SMOVE source dest ele
SREM key ele1 ele2
- 移动 source 中的 ele 到 dest 集合中
- 将 key 中元素 ele1 ele2 删除
SRANDMEMBER / SPOP
- srandmember key count : 从 set 中随机取出元素0 ≤ count ≤ set length :随机返回 count 个元素, 无放回随机count > set length : 返回全部 set 元素count 为负数时, 随机返回 count 个元素,此时为 有放回随机 , 这也意味着, count 可以为一个绝对值大于 set length 的负数。
- spop key count : spop 的 count 不能为负数,其他行为与 srandmember 一致,spop 会同时删除取出的元素
SSCAN key cursor [MATCH pattern] count
是一个基于游标的迭代器。它会返回游标以及查询到的元素。
该命令保证
- 从完整遍历开始到结束,所有存在于 key 中的元素都会被返回
- 开始前被移除且直到结束都未加入的元素,不会被返回
- 同一个元素可能被迭代多次
- 一个元素在迭代过程中被添加或删除,不保证一定返回或不返回
更多 Sets 相关命令参见 这里
4. Sorted sets
sorted sets 底层主要基于 跳跃表 实现,关于跳跃表,作者表示也不是很清楚,需要先去加强一波学习,这里不再展开,可参考文末的 reference 系列文章。
sorted sets 与 sets 十分类似,但是 sorted sets 会给每个 element 添加一个 score,用来给元素排序。sorted sets 不允许相同的元素,但运行相同分数的元素。当查询多个相同分数的元素时,返回结果为元素的字典排序
ZADD key [XX|NX] [CH] [INCR] score ele ...
添加一个或多个元素到 sorted set。
XX : 仅更新已存在的元素,不添加新元素
NX : 只添加新成员,不更新已存在的元素
CH : 返回发生变化的元素总数,不添加则返回添加的元素总数
INCR : 相当于 ZINCRBY ,为元素分数做递增操作,使用该选项时,一次只能更新一个元素
ZCARD key
ZCOUNT key min max
ZSCORE key ele
- zcard key 返回 key 所有元素数量
- zcount key min mzx 返回指定 [min, max] 分数中的所有元素数量
- 查询给定元素分数
ZLEXCOUNT key [min_ele [max_ele
查询 min ele 和 max ele 之间的元素个数,包含本身,是闭区间!
- 查询的元素前要加 [ ,如 zlexcount sset [min [max
- - (减号) , + (加号) 分别表示最小分数和最大分数元素, 如 zlexcount sset - + ,则返回 sset 这个 key 中的所有元素个数
ZINTERSTORE dest num key1 key2 [weights] [aggregate]
对 key1 key2 取交集,放入 dest 中,若 dest 已存在,则会清空 dest 后放入结果集
- num ,必须纳入交集计算的 key 的个数,这里 num 应该为 2
- weights : 在存入 dest 之前,会按照给定的 weights 与元素 相乘 ,默认为 1
- aggregate : 聚合方式, MAX , MIN , SUM ,默认 SUM
<code>127.0
.0
.1
:6379>
ZADD
a
1
1
1
2
1
3
(integer)
3
127.0
.0
.1
:6379>
ZADD
b
2
2
2
3
2
4
(integer)
3
127.0
.0
.1
:6379>
ZADD
c
3
3
3
4
3
5
(integer)
3
127.0
.0
.1
:6379>
ZINTERSTORE
dest
3
a
b
c
weights
10
1
1
aggregate
MAX
(integer)
1
127.0
.0
.1
:6379>
ZRANGE
dest
0
-1
withscores
1
)
"3"
2
)
"10"
/<code>
ZPOPMAX / ZPOPMIN key [count]
ZRANGE / ZREVRANGE key start stop [withscores]
ZRANGEBYLEX / ZREVRANGEBYLEX key [min [max [limit offset count]
ZRANGEBYSCORE
- 获取并删除给定 count 个元素,按照 score 的最大值 / 最小值排序
- 按照分数从小到大(zrevrange 从大到小) 排序,score 相同则按照字典序排列
- zrangebylex : 按字典排序,获取 [min, max] 中元素, 不要在 score 不一致的 key 中使用该指令,因为获取的结果会不准min , max 参考 zlexcount 说明, 使用 ( 获取开区间结果集limit offset count : 是否分页, offset , count 分别为起始位置和返回结果数量以 ASCII 字符集排序,所以对大小写敏感,对 utf-8 字符集不友好
ZRANK key element
ZREVRANK key element
从小到大(zrevrank 从大到小) 获取指定 ele 的 index
ZREM key ele1, ele2 ...
ZREMRANGEBYRANK key start stop ...
ZREMRANGEBYLEX key min max
ZREMRANGEBYSCORE key min max
- 删除指定元素
- 按照给定索引删除
- 按照给定元素区间,按照字典序删除
- 按照给定分数区间删除
更多 Sorted Sets 相关命令参见 这里
5. Hashes
hash 在 redis 底层是通过 dict 数据结构来实现的,即字典,java 中的 map ,都是类似的数据结构
<code>typedef
struct
dictEntry
{void
*key;union
{void
*val;uint64_t
u64;int64_t
s64;double
d; } v;struct
dictEntry
*next
; } dictEntry;typedef
struct
dictht
{ dictEntry **table;unsigned
long
size;unsigned
long
sizemask;unsigned
long
used; } dictht;typedef
struct
dict
{ dictType *type;void
*privdata; dictht ht[2
];long
rehashidx;unsigned
long
iterators; } dict;/<code>
当出现 hash 冲突时, redis 会通过一个链表来处理。
你还可以在 这里 看更详尽的解释
一个 hash 最多可包含 232 -1 个 k-v 对
- HSET key field1 v1 field2 v2 ... :新增/更新 kv
- HSETNX key field value :not exist,即不存在 field 时插入, key 不存在会新建
- HGET key field :获取 field 的 value , field 不存在时返回 nil
- HGETALL key :获取 key 中所有 kv
- HDEL key field1 field2:删除指定 field
- HEXISTS key field :field 是否存在
- HINCRBY key field step : 增加 field 对应 value 值,不存在则新建该 field, step 作为值
- HINCRBYFLOAT key field step :同上,增加或新增 float 类型
- HLEN key :获取 key 的 field 个数
- HKEYS key : 获取 key 中所有 field
- HVALS key : 获取 key 中所有 value
- HSTRLEN key field :获取 field 对应 value 的长度
- HMGET : 获取多个 field 值,field 不存在时,返回 nil hmget key field1 field1
- HMSET : 批量新增/更新 field,hmset key field1 v1 field2 v2
- HSCAN :
更多 Hash 相关命令参见 这里
6. 其他命令
bitmap, geo, hyperLogLog 参见 这里
更多命令参见 redis.cn or redis
pipeline
当我们需要多个操作时, 通常的办法是,client --> op1 --> redis --> client --> op2 --> redis ...
每次 op 操作,都需要先和 redis 建立连接,而后再操作。虽然连接建立耗时极短,但当网络不好时,仍然是一笔开销,而 pipeline 则可以很好的解决这个问题。
pipeline 和我们常说的管道基本一致,它能在 client 和 redis server 之间建立连接通道,而后每次操作都在该通道下进行,所有操作完成后,再关闭 pipeline
当我们通过 pipeline 操作数据时,需要注意操作的个数,因为 redis 需要耗费内存来组织需要返回的数据。所以我们需要控制 pipeline 内的 op 个数,分批处理。
pipeline 操作是非原子性的,如果要保证其原子性,可以通过 multi exec 来保证
事实上, pipiline 能处理的事情,都可以通过脚本来处理,脚本还能够处理有依赖关系的数据。lua>
PUBSUB
在 redis 中, 通过几个简单的命令就可以实现 pub/sub 。
我们在一个 terminal 中使用 subscribe 命令
<code>127.0
.0
.1
:6379>
SUBSCRIBE
chan1
chan2
Reading
messages...
(press
Ctrl-C
to
quit)
1
)
"subscribe"
2
)
"chan1"
3
)
(integer)
1
1
)
"subscribe"
2
)
"chan2"
3
)
(integer)
2
1
)
"message"
2
)
"chan1"
3
)
"From chan1"
/<code>
该命令会阻塞监听两个 channel: chan1 和 chan2 , 如果 channel 不存在,则会创建。
我们同样可以使用 psubscribe 命令来使用通配符监听 channel, 如 psubscribe chan*
我们还可以通过 unsubscribe 和 punsubscribe (支持通配符) 来取消订阅
我们在另一个 terminal 通过 publish chan1 value1 发送消息,订阅方就能收到消息
在 redis 的 server.h 中,定义了一个用来存储 pubsub 数据的 key=channel , value=client 的 dict
<code>dict
*pubsub_channels;/<code>
- 当有 client subscribe 时, redis 就会往这个 dict 中添加 的一堆键值对, 多个 client 注册同一个 channel 时, client 的存储就变成 dict 中的 list。
- 当有人 publish 值到 channel 时, redis 会先通过 channel 查询到对应的 client list ,然后依次发送给各个 client。
- 退订 channel 时, 只需要找到 channel 下的 client ,从 dict 中剔除即可
如果 publisher 在 publish 时没有 subscriber 监听,那么该条消息就会丢失。
Reference
- redis 官网
- redis 中文
- redis 设计与实现 老版
- redis 设计与实现 3.0 版
- 深入了解 redis 底层数据结构
- 图解 redis 五种数据结构底层实现