一 全局命令:
1 查看所有键
keys *
2 键总数
dbsize
dbsize计算键数时不会遍历所有键,而是直接获取redis内置的键总数变量,所以时间复杂度是O(1),而keys命令会遍历所有键,时间复杂度为O(n),当redis保存了大量键时,线上环境禁止使用。
3 检查键是否存在
exists key
存在返回1,否则0
4 删除键
del key [key....]
del是通用的命令,可以删除任何数据结构类型,成功删除键会返回删除成功的键的个数,否则返回0。
del删除不存在的键会返回0。
del支持删除多个键(例del mylist1 mylist2 mylist3)。
5 键过期
expire keys seconds
redis支持对键添加过期时间,当过期后,会自动删除键
ttl key
ttl命令可以查看键的剩余过期时间,它有三中返回类型:
- 大于0的整数,表示键的剩余过期时间;
- -1表示未设置过期时间;
- -2表示键不存在。
6 返回键的数据结构类型
type key
若键不存在返回none
二 数据结构和内部编码
type命令返回的是当前键的数据结果类型,它是redis对外的数据结构,分别有string、hash、list、set、zset。
- 每种数据结构都有自己底层的内部编码实现,但redis会在合适的场景选择合适的内部编码。
- 每种数据结构都有两种内部编码实现(例list有linkedlist和ziplist实现)。
object encoding key可以查看键的内部编码信息。
redis这样设计的好处:
- 可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令;
- 多种内部编码可以在不同场景下发挥各自的优势。
三 单线程架构
redis使用单线程架构和I/O多路复用模型来实现高性能的内存数据库服务。
1 单线程模型
redis客户端与服务端的模型可以简化为下图,每次客户端都经历了发送命令、执行命令、返回结果三个过程。
第2步是比较重要的,redis是单线程执行命令的,所以一条命令到达服务端后不会立刻执行,而是所有命令都进入一个队列中,然后逐个被执行。当多个客户端执行命令时,这些命令的执行顺序是不确定的。
2 为什么单线程redis依然很快?
为什么Redis使用单线程回达到每秒万级别的处理能力呢?
- 纯内存访问,redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是redis达到每秒万级别访问的重要基础;
- 非阻塞IO,Redis使用epoll()作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll()中的连接、读写、关闭都转换为时间,不在网络I/O上浪费过多的时间;
- 单线程避免了线程切换和竞争产生的消耗(单线程简化数据结构和算法的实现)。
但是单线程有一个问题,它对每个命令的执行时间是有要求的。因为单个命令时间执行时间过长,会造成其他命令的阻塞,这样对redis这种高性能的服务来说是致命的。
3 字符串
redis的键都是字符串。字符串的值可以是字符串、数字和二进制,但是值最大不能超过512MB。
常用命令:
(1)设置值
set key value [ex seconds] [px milliseconds] [nx|xx]
- ex seconds:为键设置秒级过期时间;
- px milliseconds:为键设置毫秒级过期时间;
- nx:键必须不存在,才可以设置成功,用于添加;
- xx:与nx相反,键必须存在,才可以设置成功,用于更新。
另外redis还提供了setex、setnx两种命令。
setex key seconds value //相当于set key value ex seconds
setnx key value
setnx的应用场景:当有多个客户端执行setnx命令时,只有一个客户端能成功,setnx可以做分布式锁的一种实现方案。
(2) 获取值
get key
(3) 批量设置值
mset key value [key value.....]
(4) 批量获取值
mget key [key....]
如果键不存在会返回Nil空。
批量操作命令可以提高开发效率。
(5) 计数
incr key //对key进行自增操作
- 值不是整数,返回错误;
- 值是整数,返回自增的结果;
- 键不存在,按照值为0自增,返回结果1。
redis除了自增,还有自减decr,incrby(自增指定数字),decrby(自减指定数字)、incrbyfloat(自增浮点数)。
decr key
incrby key increment
decrby key decrement
incrbyfloat key increment
不常用命令
(1) 追加值
append key value
向字符串末尾追加值
(2)字符串长度
strlen key
中文在redis中,每个字符占3个字节。
(3)设置并返回原值
getset key value
(4) 设置指定位置的字符
setrange ket offeset value
(5) 获取部分字符串
getrange key start end
start表示起始偏移量,end表示结束偏移量。
3 字符串的内部编码
- int:8个字节的长整型;
- embstr:小于等于39字节的字符串;
- raw:大于39字节的字符串。
redis会根据值的类型和长度选择合适的编码。
4 使用场景
- 缓存功能:由于redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用;
- 计数:很多网页点击量及视频播放次数都是使用redis作为计数工具;
- 共享Session:使用分布式web服务器保存Session信息时可能出现不一致情况,使用redis保存session是一种解决方案;
- 限速:例如限制用户每分钟获取验证码的频率。
4 Hash哈希
哈希类型是指键值类型本身又是一个键值对结构。如下图:
(1)设置值
hset key field value
(2) 获取值
hget key field
当键或者field不存在返回Nil。
(3) 删除field
hdel key field [field....] //删除一个到多个field
(4)计算field的个数
hlen key
(5) 批量获取或设置值
hmget key filed [field....]
hmset key field value [field value....]
(6) 判断field是否存在
hexists key field
存在返回1,否则返回0
(7)获取所有field
hkeys key
(8) 获取所有值
hvals key
(9)获取所有的field-value
hgetall key
(10)hincrby hincrbyfloat
hincrby key field
hincrbyfloat key field
自增命令
(11)计算value的字符串长度(Redis3.2以上支持)
hstrlen key field
内部编码
- ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entrlies配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节),redis会使用ziplist作为内部实现;ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更优秀;
- hashtable(哈希表):当哈希类型无法满足ziplist的条件时,redis会使用hashtable作为哈希的实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
使用场景
- 缓存用户信息
哈希类型是稀疏的,关系数据库是完全结构化的,例如哈希类型每个键都有不同的field,关系数据库添加新的列,所有行都要为其设置值;
关系数据库可以做复杂关系查询,redis实现困难,且维护成本高。
缓存用户信息的方式:
- 使用原生字符串:优点:简单直观 缺点:键占用过多,内存占用量大,用户信息内聚性比较差;
- 序列化字符串类型:优点:简化编程,合理使用序列化可以提高内存使用率 缺点:但序列化和反序列化有开销;
- 哈希类型:优点:简单直观,合理使用可以减少内存空间使用 缺点:要控制ziplist和hashtable的内部编码转换,hashtable会消耗更多内存。
5 列表
列表类型是用来存储多个有序的字符串
一个列表最多可以存储-1个元素。在redis中可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下表的元素。
列表操作
1从右边添加元素操作
rpush key value [value....]
2 从左边插入元素
lpush key value [value.....]
3 向某个元素前插入或者后插入元素
linsert key before|after pivot value
若元素不存在,插入失败返回-1
查找操作
1 获取指定范围内的元素列表
lrange key start end
索引下标有两个特点:从左到右,下表为0到n-1,从右到左,下表为-1到-N。end选项包含自身。
2 获取指定下标的元素
lindex key index
3 获取列表长度
llen key
删除操作
1 从列表中左侧弹出元素
lpop key
2 从列表右侧弹出元素
rpop key
3 删除指定元素
lrem key count value
- count>0:从左到右最多删除count个元素;
- count<0:从右到左删除最多count绝对值个元素;
- count=0:删除所有。
4 按照索引范围修剪列表
ltrim key start end
截取start到end之间的元素
修改操作
1 修改 指定下标元素值
lset key index value
阻塞操作
1 阻塞时弹出
blpop key [key.....] timeout
brpop key [key....] timeout
- key指定弹出的键
- timeout:指定阻塞时间。
(1)key为空,timeout不为0时
阻塞timeout时间就会返回nil。
(2) key为空,timeout=0时
它会一直阻塞,知道键存在为止。
(3)key不空,timeout=0时会立刻返回数据
若brpop弹出多个键时,brpop会从左到右遍历键,只要有一个键能弹出就立刻返回;
若多个客户端同时对一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值。
内部编码
- ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置(默认64字节)时,redis会选用ziplist来作为列表的内部实现来减少内存的使用;
- linkedlist(链表):当列表类型无法满足ziplist的条件时,redis使用linkedlist作为列表的内部实现。
使用场景
- 消息队列:使用lpush+brpop命令组合即可实现阻塞队列。
- 文章列表
6 集合
集合用来存储不重复的字符串并且集合 是无序的。
集合内操作
1 添加元素
sadd key element [element....]
添加成功会返回添加成功的元素个数。
2 删除元素
srem key element [element....]
返回删除成功的元素数。
3 计算个数
scard key
它的时间复杂度为O(1), 它不会遍历集合,而是直接使用redis内部变量。
4 判断元素是否在集合内
sismember key element
成功返回1 失败返回0
5 随机从集合中返回指定个数元素
srandmember key [count]
count默认为1。
6 从集合中弹出元素
spop key
7 获取所有元素
smembers key
集合间操作
1 求多个集合的交集
sinter key [key....]
2 求多个集合的并集
sunion key [key.....]
3 求多个集合的差集
sdiff key [key...]
4 将交集、并集、差集的结果保存
sinterstore destination key [key.....]
sunionstore destination key [key.....]
sdiffstore destination key [key.....]
集合间的运算在元素较多的情况下比较耗时,redis提供上面三个命令将集合间交集、并集、差集的结果保存在destination key中。
内部编码:
- intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置时(默认512个),redis会选用intset作为内部实现,从而减少内存使用;
- hashtable(哈希表):当集合类型无法满足intset的条件时,redis会使用hashtable作为集合的内部实现。
使用场景:
集合类型比较典型的使用场景是标签。
7 有序集合
命令
集合内:
1 添加成员
zadd key score member [score member.....]
redis3.2后添加了nx、xx、ch、incr四个选项。
- nx,member不存在,才可以设置成功,用于添加;
- xx,member必须存在,才可以设置成功,用于更新;
- ch,返回此次操作后,有序集合元素和分数发生变化的个数;
- incr,对score做增加,相当于后面介绍的zincrby。
有序集合虽然相比集合进行了排序,但也产生了代价,zadd的时间复杂度为O(log(n)),sadd的时间复杂度为O(1)。
2 计算成员个数
zcard key
3 计算某个成员的分数
zscore key member
4 计算成员排名
zrank key member //从低到高返回排名
zrevrank key member //从高到低返回排名
5 删除成员
zrem key member
6 增加成员的分数
zincrby key increment member
7 返回指定排名范围的成员
zrange key start end [withscores] //从低到高返回指定范围的成员,withsocre表示返回分数
zrevrange key start end [withscores] //从高到低返回指定范围的成员,withsocre表示返回分数
8 返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count] //从低到高返回min到max分数范围的成员
zrevrangebyscore key min max [withscores] [limit offset count]//从高到低返回min到max分数范围的成员
9 返回指定分数范围的个数
zcount key min max
10 删除指定排名内的升序元素
zremrangebyrank key start end
11 删除指定分数范围的成员
zremrangebyscore key min max
集合间的操作
1 交集
zinterstore destination numkeys key [key....] [weights weight [weight...]] [aggregate sum | min | max]
- destination:交集计算结果保存到这个键;
- numkeys:需要做交集计算键的个数;
- key [key...]:需要做交集计算的键;
- weights weight [weight...]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的权重默认是1;
- aggregate sum|max|min:计算成员交集后,分值可以按照sum,min,max做汇总,默认值是sum。
2 并集
zunionstore destination numkeys key [key....] [weights weight [weight...]] [aggregate sum | min | max]
内部编码
- ziplist:当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节),redis会使用ziplist作为内部实现;
- skiplist:当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率下降。
使用场景:
- 添加用户点赞数
- 取消用户赞数
- 展示获取赞数最多的十个用户
- 展示用户信息以及用户分数
八 键管理
1 单个键管理
(1)键重命名
rename key newkey
renamenx key newKey //只有当newKey不存在时才会被重命名
使用重命名命令时,有两点需要注意:
- 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性,这点不可忽视;
- 如果rename和renamenx中的key和newKey是相同的,在redis3.2和之前返回结果不同。
(2)随机返回一个键
randomkey
、
(3)键过期
除了expire、ttl命令对键过期进行操作,还有expireat、pexpire、pexpireat、pttl、persist等一系列命令。
- expire key seconds:键在seconds秒后过期;
- expireat key timestamp:键在秒级时间戳timestamp后过期;
ttl命令和pttl命令都可以查询间的过期时间,但是pttl精度更高可以达到毫秒级别,有三种返回值:
- 大于等于0的整数:键剩余的过期时间(ttl是秒,pttl是毫秒)
- -1:表示键没有设置过期时间;
- -2:表示键不存在
若要让key在2019-11-1 00:00:00过期,可设时间戳为1572537600
除此之外,redis2.6版本后提供了毫秒级的过期方案。
- pexpire key milliseconds:键在milliseconds毫秒过期;
- pexpireat key milliseconds-timestamp:键在毫秒级时间戳timestamp后过期。
但是无论是使用过期时间还是时间戳,秒级还是毫秒级,在redis内部最终使用的都是pexpireat。
使用redis相关命令时需注意:
- 如果expire key的键不存在,返回结果为0;
- 如果过期时间为负值,键会立即被删除,犹如使用del命令;
- persisit命令可以将键的过期时间清除。
- 对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易在开发中被忽视。
- redis不支持二级数据结构(哈希、列表)内部元素的过期时间,例如不能对列表的一个元素做过期时间设置;
- setex命令作为set+expire的组合,不但是原子执行,同时减少了一次网络通讯的时间。
persist清楚键过期时间
set命令会清除键过期时间
(4)迁移键
redis支持把部分数据由一个redis迁移到另一个redis中,redis提供了move,dump+restore,migrate三组迁移的方法,他们的实现方式以及使用的场景不太相同。
- move key db:move命令用于在redis内部进行数据迁移,redis内部可以有多个数据库。这个命令是把指定的键从源数据库迁移到目标数据中,不建议在生产环境中使用;
- dump+restore:dump key; restore key ttl value;dump + store可以实现在不同的redis之间进行数据迁移的功能,整个迁移的过程分为两步:1)在原redis上,dump命令回将键值序列化,格式采用的是RDB格式;2) 在目标redis上,restore命令将上面序列化的值进行复原,其中ttl参数代表过期时间,如果ttl=0代表没有过期时间。
- migrate:migrate命令也是用于在Redis实例间进行数据迁移的,实际上migrate命令就是将dump、restore、del命令进行组合,从而简化了操作流程,migrate命令具有原子性,而且从redis3.0.6版本以后支持迁移多个键的功能,有效地提高了效率,migrate在水平扩容中起到重要作用。
>1 dump+restore
在原redis上执行dump
dump key
在目标redis上执行restore
restore key ttl value
>2 migrate
migrate host post key | "" destination-db timeout [copy] [replace] [keys key [key....]]
2 遍历键
(1)全量遍历键
keys pattern
- *:代表匹配任意字符;
- ?:代表匹配一个字符;
- []:代表匹配部分字符。
- \x:用来做转义。
当需要遍历所有键时(例如检测过期或闲置时间、寻找大对象等),keys是一个很有帮助的命令,例如想删除所有以video字符串揩油的键,可以执行如下操作:
redis-cli keys video* | xargs redis-cli del
当redis中键值比较多时,keys命令可能会导致redis阻塞。可以采用渐进式遍历避免阻塞。
(2) 渐进式遍历
redis2.8以后提供了一个新命令scan,它能有效的解决keys命令存在的问题。scan采用渐进式遍历方式解决keys阻塞问题,每次会遍历字典中的一部分键,直到键遍历完毕。
scan cursor [match pattern] [count number]
- cursor:是必须参数,cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束;
- match pattern:是可选参数,做模式匹配,这点和keys的模式匹配相似;
- count number:是可选参数,表明每次要遍历的键的个数,默认10
渐进式遍历能够有效解决keys阻塞问题,但是scan过程中如果有键的变化,那么遍历效果就会产生如下问题:新增的键可能没有遍历到,遍历重复的键。也就是说scan不能完整遍历出所有键。
3 数据库管理
(1)切换数据库
select dbIndex
redis默认配置中有16个数据库。多数据库的缺点:
- redis是单线程的,如果使用多个数据库,那么这些数据库仍然是使用一个CPU,彼此之间还是会受到影响的;
- 多数据库的使用方式,会让调试和运维不同业务的数据库变得困难,假如有一个慢查询存在,依然会影响其他数据库。这样会使得别的业务方定位问题困难;
- 部分redis客户端不支持这种方式,即使支持,开发难度也大。
(2)flushdb/flushall
flushdb/flushall用于清楚数据库,两者区别是flushdb用于清除当前数据库,flushlall会清除所有数据库。
- flushdb/flushall命令会将所有数据清除,一旦误操作后果不堪设想;
- 如果当前数据库键值比较多,flushdb/flushall存在阻塞redis的可能性。