全局命令
-
查看所有的键(遍历所有key,慢查询之一)
keys *
-
键总数(redis内部维护的计数器,并不会扫描全库)
dbsize
-
检查键是否存在
exists key
-
删除键
del key1 key2...
-
过期设置
expire key seconds
-
获取数据类型
type key
数据结构与内部编码
-
string:
- int
- embstr
- raw
-
hash:
- hashtable
- ziplist
-
list:
- quicklist
-
set:
- hashtable
- intset
-
zset:
- skiplist
- ziplist
单线程与I/O多路复用模型
客户端到服务端模型
-
发送命令
-
执行命令
- 进入命令队列
-
返回结果
为什么快
- 纯内存访问(主要)
- 非阻塞io:依赖linux内核中的多路复用IO接口
epoll
,自编写一套处理模型(不依赖于其他事件模型)将epoll中的连接、读写、关闭都转换为事件 - 单线程避免了线程切换以及线程竞争的开销,单线程也简化了数据结构与算法的实现,坏处是对于慢查询非常敏感,一个慢查询将阻塞之后所有的命令
数据结构的概览
字符串
命令
命令 | 时间复杂度 |
---|---|
set key value | O(1) |
get key | O(1) |
del key [key ...] | O(k) |
mset key value [key value ...] | O(k) |
mget key [key ...] | O(k) |
incr key | O(1) |
decr key | O(1) |
decr key | O(1) |
incrby key increment | O(1) |
decrby key decrment | O(1) |
incrbyfloat key incrment | O(1) |
append key value | O(1) |
strlen key | O(1) |
setrange key offset value | O(1) |
getrange key start end | O(n)(n=strlen key) |
内部编码
- int:整形
- embstr:小于等于44字节
- raw:大于44字节
使用场景
- 缓存
- 计数
- 共享变量(session etc.)
- 限流
- setnx分布式锁
哈希
命令
命令 | 时间复杂度 |
---|---|
hset key field value | O(1) |
hget key field | O(1) |
hget key [field ...] | O(k)(k=sum(fileds)) |
hlen key | O(1) |
hgetall key(遍历key,可能会阻塞,可以用hscan替换) | O(1) |
hmget key filed [filed ...] | O(k) |
hmset key filed value [field value] | O(k) |
hexists key field | O(1) |
hkeys key | O(n) |
hvals key | O(n) |
hsentx key field value | O(1) |
hincrby key field incrment | O(1) |
hincrbyfloat key field incrment | O(1) |
hstrlen key field | O(1) |
使用场景
- 对象类型(k/v)数据
列表
命令
- 添加
命令 | 时间复杂度 |
---|---|
rpush key value [value ...] | O(k) |
lpush ... | O(k) |
linsert key before | after pivot value |
- 查找
命令 | 时间复杂度 |
---|---|
lrange key start end(可能阻塞) | O(start+(end-start)) |
lindex key index | O(index) |
llen key(ziplist 超过65534需要遍历,否则和linkedlist一样用变量维护) | O(1) |
- 删除
命令 | 时间复杂度 |
---|---|
lpop key | O(1) |
rpop key | O(1) |
lrem count value | O(n)(列表长度) |
ltrim key start end | O(n)(裁剪元素总数) |
- 修改
命令 | 时间复杂度 |
---|---|
lset key index value | O(n)(索引偏移量) |
- 阻塞操作
命令 | 时间复杂度 |
---|---|
blpop brpop | O(1) |
内部编码
-
ziplist:
- 元素个数小于list-max-ziplist-entries配置
- 列表中每个元素的值都小于list-max-ziplist-value配置
-
linkedlist:
- 无法满足ziplist条件
-
quicklist(当前):
- 结合了ziplist和linkedlist
消息队列
- 消息队列(单次消费,无ack)
- lpush+lpop=Stack(栈)filo
- lpush+rpop=Queue(队列)fifo
- lpsh+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
集合
命令
命令 | 时间复杂度 |
---|---|
sadd key element [element ...] | O(k) |
srem key element [element ...] | O(k) |
scard key | O(1) |
sismember key element | O(1) |
srandmember [count] | O(count) |
spop key | O(1) |
smember key(遍历,可以换为sscan) | O(n)(元素总数) |
sinter key [key ...] | O(m*k)(k是多个集合中元素最少的个数,m是键的个数) |
sunion key [key ...] | O(k)(k是多个集合的元素个数) |
sdiff key [key ...] | O(k)(k是多个集合的元素个数) |
内部编码
-
intset:
- 元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
-
hashtable:
- 无法满足intset
使用场景
- 取共同好友等交集行为
有序集合
命令
命令 | 时间复杂度 |
---|---|
zadd key element scoer [element score ...] | O(k*log(n))(k:添加成员个数、n:当前有序集合个数) |
zcard key | O(1) |
zscore key member | O(1) |
zrank | zrevrank key member |
zrem key member [member ...] | O(k*log(n))(k:添加成员个数、n:当前有序集合个数) |
zincrby key incrment member | O(log(n))(n:当前有序集合个数) |
zrange | zrevrange key start end [withscore](返回成员分数) |
zrangebyscore | zrevrangebyscore key start end [withscore](返回成员分数) |
zcount | O(log(n))(n:当前有序集合个数) |
zremrangebyrank key start end | O(k+log(n))(k:删除成员个数、n:当前有序集合个数) |
zremrangebyscore key min max | O(k+log(n))(k:删除成员个数、n:当前有序集合个数) |
zinterstore dest numkeys key [key ...] | O(kn)+O(mlog(m))(n:成员最小的有序集合成员个数、k:有序集合个数、m:结果集成员个数) |
ziunionstore dest numkeys key [key ...] | O(n)+O(m*log(m))(n:所有有序集合成员个数、m:结果集成员个数) |
内部编码
- ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用
- skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降
流
键管理
-
重命名:
rename key new key
- 如果在rename之前,键java已经存在,那么它的值也将被覆盖
- 为了防止被强行rename,Redis提供了renamenx命令
- 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性
- 如果rename和renamenx中的key和newkey如果是相同的,在Redis3.2和之前版本返回结果略有不同
-
随机返回一个键
randomkey
-
键过期
-
expire key second
:键在seconds秒后过期 -
expiret key timestamp
:键在秒级时间戳timestamp后过期 -
pexpire key milliseconds
:键在milliseconds毫秒后过期 -
pexpireat key milliseconds-timestamp
:键在毫秒级时间戳timestamp后过期 -
无论是使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最终使用的都是pexpireat
注意:
- 如果
expire key
的键不存在,返回结果为0 - 如果过期时间为负值,键会立即被删除,犹如使用
del
命令一样 persist key
命令可以将键的过期时间清除- 对于字符串类型键,执行
set
命令会去掉过期时间,这个问题很容易在开发中被忽视 - Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置
setex
命令作为set
+expire
的组合,不但是原子执行,同时减少了一次网络通讯的时间
-
-
迁移键
migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key ...]
-
遍历键
-
全量遍历键
keys pattern
注意:
- 如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞
-
渐进式遍历
scan cursor [match pattern] [count number]
注意:
- Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan
- 如果在scan的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重复的键等情况
-
重点回顾
- Redis提供5种数据结构,每种数据结构都有多种内部编码实现。
- 纯内存存储、IO多路复用技术、单线程架构是造就Redis高性能的三个因素。
- 由于Redis的单线程架构,所以需要每个命令能被快速执行完,否则会存在阻塞Redis的可能,理解Redis单线程命令处理机制是开发和运维Redis的核心之一。
- 批量操作(例如mget、mset、hmset等)能够有效提高命令执行的效率,但要注意每次批量操作的个数和字节数。
- 了解每个命令的时间复杂度在开发中至关重要,例如在使用keys、hgetall、smembers、zrange等时间复杂度较高的命令时,需要考虑数据规模对于Redis的影响。
- persist命令可以删除任意类型键的过期时间,但是set命令也会删除字符串类型键的过期时间,这在开发时容易被忽视。
- move、dump+restore、migrate是Redis发展过程中三种迁移键的方式,其中move命令基本废弃,migrate命令用原子性的方式实现了dump+restore,并且支持批量操作,是Redis Cluster实现水平扩容的重要工具。
- scan命令可以解决keys命令可能带来的阻塞问题,同时Redis还提供了hscan、sscan、zscan渐进式地遍历hash、set、zset。
来源:oschina
链接:https://my.oschina.net/u/4557072/blog/4331217