前言:redis系类博客都是以redis5.0版本为基础!!!
目录
Redis常见命令
在正式介绍5种数据结构之前,了解⼀下Redis的⼀些全局命令、数据结构和内部编码、单线程 命令处理机制是⼗分必要的,它们能为后⾯内容的学习打下⼀个良好的基础. 主要体现在两个⽅⾯: 1)Redis的命令有上百个,如果纯靠死记硬背⽐较困难,但是如果理解Redis的⼀些机制,会发现这 些命令有很强的通⽤性。
2)Redis不是万⾦油,有些数据结构和命令必须在特定场景下使⽤,⼀旦使⽤不当可能对Redis本⾝ 或者应⽤本⾝造成致命伤害。
基本全局命令
Redis 有5种数据结构,但它们都是键值对种的值,对于键来说有⼀些通⽤的命令。
KEYS
返回所有满⾜样式(pattern)的key。⽀持如下统配样式。
• h?llo 匹配 hello ,hallo,hqllo......
• h*llo 匹配 hllo 和 heeeello......
• h[ ae]llo 匹配 hello 和 hallo
• h[ ^e]llo 匹配 hallo, hbllo......但不匹配 hello
• h [a-b]llo 匹配 hallo 和 hbllo
语法:KEYS pattern
命令有效版本:1.0.0之后 时间复杂度:O(N) 返回值:匹配pattern的所有key。
示例:
redis> MSET firstname Jack lastname Stuntman age 35
"OK"
redis> KEYS *name*
"firstname"
"lastname"
redis> KEYS a??
- "age"
redis> KEYS *
"age"
"firstname"
"lastname"
EXISTS
判断某个 key 是否存在。
语法: EXISTS key [key ...]
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:key 存在的个数。
示例:
redis> SET key1 "Hello"
"OK"
redis> EXISTS key1
(integer) 1
redis> EXISTS nosuchkey
(integer) 0
redis> SET key2 "World"
"OK"
redis> EXISTS key1 key2 nosuchkey
(integer) 2
DEL
删除指定的 key。
语法:DEL key [key ...]
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:删除掉的 key 的个数。
示例:
redis> SET key1 "Hello"
"OK"
redis> SET key2 "World"
"OK"
redis> DEL key1 key2 key3
(integer) 2
EXPIRE
为指定的 key 添加秒级的过期时间(Time To Live TTL)
语法:EXPIRE key seconds
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:1 表⽰设置成功。0 表⽰设置失败。
示例:
redis> SET mykey "Hello"
"OK"
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10
TTL
获取指定 key 的过期时间,秒级。
语法:TTL key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:剩余过期时间。-1 表⽰没有关联过期时间,-2 表⽰ key 不存在。
示例:
redis> SET mykey "Hello"
"OK"
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10
EXPIRE和TTL命令都有对应的⽀持毫秒为单位的版本:PEXPIRE和PTTL,详细⽤法就不再
介绍了。
TYPE
返回key对应的数据类型。
语法:TYPE key
命令有效版本:1.0.0之后
时间复杂度:O(1)
返回值: none , string , list , set , zset , hash and stream .。
示例:
redis> SET key1 "value"
"OK"
redis> LPUSH key2 "value"
(integer) 1
redis> SADD key3 "value"
(integer) 1
redis> TYPE key1
"string"
redis> TYPE key2
"list"
redis> TYPE key3
"set"
数据结构和内部编码
type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列
表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构。
可以看到每种数据结构都有⾄少两种以上的内部编码实现,例如 list 数据结构包含了 linkedlist 和
ziplist 两种内部编码。同时有些内部编码,例如 ziplist,可以作为多种数据结构的内部实现,可以通过 object encoding 命令查询内部编码:
27.0.0.1:6379> set hello world
OK
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"quicklist"
可以看到 hello 对应值的内部编码是 embstr,键 mylist 对应值的内部编码是 ziplist。
Redis 这样设计有两个好处:
1)可以改进内部编码,⽽对外的数据结构和命令没有任何影响,这样⼀旦开发出更优秀的内部编码,⽆需改动外部数据结构和命令,例如 Redis 3.2 提供了 quicklist,结合了 ziplist 和 linkedlist 两者的优势,为列表类型提供了⼀种更为优秀的内部编码实现,⽽对⽤⼾来说基本⽆感知。
2)多种内部编码实现可以在不同场景下发挥各⾃的优势,例如 ziplist ⽐较节省内存,但是在列表元素⽐较多的情况下,性能会下降,这时候 Redis 会根据配置选项将列表类型的内部实现转换为
linkedlist,整个过程⽤⼾同样⽆感知。
单线程架构
Redis 使⽤了单线程架构来实现⾼性能的内存数据库服务,⾸先通过多个客⼾端命令调⽤的例
⼦说明 Redis 单线程命令处理机制,接着分析 Redis 单线程模型为什么性能如此之⾼,最终给出为什么理解单线程模型是使⽤和运维 Redis 的关键。
现在开启了三个 redis-cli 客⼾端同时执⾏命令。
客⼾端 1 设置⼀个字符串键值对:
127.0.0.1:6379> set hello world
客⼾端 2 对 counter 做⾃增操作:
127.0.0.1:6379> incr counter
客⼾端 3 对 counter 做⾃增操作:
127.0.0.1:6379> incr counter
我们已经知道从客⼾端发送的命令经历了:发送命令、执⾏命令、返回结果三个阶段,其中我们
重点关注第 2 步。我们所谓的 Redis 是采⽤单线程模型执⾏命令的是指:虽然三个客⼾端看起来是同时要求 Redis 去执⾏命令的,但微观⻆度,这些命令还是采⽤线性⽅式去执⾏的,只是原则上命令的执⾏顺序是不确定的,但⼀定不会有两条命令被同步执⾏,可以想象 Redis内部只有⼀个服务窗⼝,多个客⼾端按照它们达到的先后顺序被排队在窗⼝前,依次接受 Redis 的服务,所以两条 incr 命令⽆论执⾏顺序,结果⼀定是 2,不会发⽣并发问题,这个就是 Redis 的单线程执⾏模型。
Redis 的单线程模型
2. 为什么单线程还能这么快
通常来讲,单线程处理能⼒要⽐多线程差,例如有 10 000 公⽄货物,每辆⻋的运载能⼒是每次
200 公⽄,那么要 50 次才能完成;但是如果有 50 辆⻋,只要安排合理,只需要依次就可以完成任务。那么为什么 Redis 使⽤单线程模型会达到每秒万级别的处理能⼒呢?可以将其归结为三点:
a. 纯内存访问。Redis 将所有数据放在内存中,内存的响应时⻓⼤约为 100 纳秒,这是 Redis 达
到每秒万级别访问的重要基础。
b. ⾮阻塞 IO。Redis 使⽤ epoll 作为 I/O 多路复⽤技术的实现,再加上 Redis ⾃⾝的事件处理模型
将 epoll 中的连接、读写、关闭都转换为事件,不在⽹络 I/O 上浪费过多的时间。
c. 单线程避免了线程切换和竞态产⽣的消耗。单线程可以简化数据结构和算法的实现,让程序模
型更简单;其次多线程避免了在线程竞争同⼀份共享数据时带来的切换和等待消耗。
虽然单线程给 Redis 带来很多好处,但还是有⼀个致命的问题:对于单个命令的执⾏时间都是有
要求的。如果某个命令执⾏过⻓,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客⼾端的阻塞,对于 Redis 这种⾼性能的服务来说是⾮常严重的,所以 Redis 是⾯向快速执⾏场景的数据库。