目录
前言
Redis 提供了 5 种数据结构,理解每种数据结构的特点对于 Redis 开发运维⾮常重要,同时掌握每
种数据结构的常见命令,会在使用Redis 的时候做到游刃有余。在正式介绍 5 种数据结构之前,了解⼀下 Redis 的⼀些全局命令。单线程命令处理机制是⼗分必要的,它们能为后⾯内容的学习打下⼀个良好的基础。
注意:
- Redis 的命令有上百个,如果纯靠死记硬背⽐较困难,但是如果理解 Redis 的⼀些机制,会发现这些命令有很强的通⽤性。
- Redis 不是万⾦油,有些数据结构和命令必须在特定场景下使⽤,⼀旦使⽤不当可能对 Redis 本⾝或者应⽤本⾝造成致命伤害。
两个核心命令
GET和SET
这两个命令是Redis中最核心的两个命令,因为Redis是按照键值对的方式存储数据的;GET命令是根据KEY来取对应的value的,而SET是把一对key-value存储进去;
说明:
- 使用SET存储一对KV值时,在命令行上敲的是字符串,但是对于value可能是不同的数据结构;
- KV值可以加上单引号,双引号,也可以不加;
示例:
说明:
- GET命令直接输入key就可以得到value;
- 如果当前key不存在,回返回 nil;
- Redis中的命令不区分大小写;
全局命令
Redis支持很多种数据结构,包括:字符串、哈希表、列表、集合、有序集合等;对于key固定的就是字符串,但是value实际上会有很多种类型,操作不同的数据结构就会有不同的命令;因此我们在学习Redis中的数据结构之前,先了解一些全局命令:就是能够搭配任意一个数据结构来使用的命令;
KEYS
**功能:**用来查询当前服务器上匹配的key;通过一些特殊符号(通配符)来描述key的模样,匹配上述模样的key就可以被查询出来;
语法:
KEYS pattern
- h?llo 匹配 hello , hallo 和 hxllo
- h*llo 匹配 hllo 和 heeeello
- h[ae]llo 匹配 hello 和 hallo 但不匹配 hillo
- h[^e]llo 匹配 hallo , hbllo , ... 但不匹配 hello
- h[a-b]llo 匹配 hallo 和 hbllo
时间复杂度:O(N)
返回值:匹配pattern的所有key
示例:
keys * 一个大"杀器",查询Redis中所有的key;
注意:由于keys命令的时间复杂度是O(N),所有在真实的生产环境上,一般都会禁止使用keys*;因为生产环境上的key可能会非常的多,而redis是一个单线程的服务器,执行keys*的时间就会非常的长,就可能会使Redis的服务器被阻塞了,无法给客户端提供服务了;由于Redis经常会用于做缓存,挡在MySQL前面,而万一Redis被keys*阻塞了,此时其他查询Redis的操作就会超时,这些请求就会直接查询数据库;最终可能导致MySQL挂掉,整个服务器最后基本就瘫痪了。
EXISTS
功能:判定某个key是否存在
语法:
EXISTS key [key ...]
时间复杂度:O(1)
返回值:key存在的个数
说明:
- 这个命令针对查询多个key来说是非常有用的;
- Redis组织这些key是按照哈希表的方式来组织的,所以时间复杂度是O(1)
示例:
上面两种执行命令的区别?
Redis只一个客户端服务器结构的程序,客户端和服务器之间通过网络来进行通信;分开写会产生多轮次的网络通信,而一起写只会有一次的网络通信,效率比较高,虽然结果是一样的;
DEL
功能:删除指定的key
语法:
DEL key [key ...]
时间复杂度:O(1)
返回值:删除掉的key的个数
示例:
说明:我们要联系Redis的定位用途,他是作为缓存来存放热点数据的,全量的数据是存放在MySQL中的;删除一个key一般来说问题不大;但是如果把所有的数据或者一大半数据删除了,这种影响会非常的大;因为这样容易把所有的请求打给MySQL,然后MySQL就会被搞挂;相比之下MySQL的全量数据哪怕误删一个数据影响都是很大的;
EXPIRE
功能:为指定的key添加秒级别的过期时间(Time To Live TTL)
语法:
EXPIRE key seconds
时间复杂度:O(1)
返回值:1 表⽰设置成功。0 表⽰设置失败。
示例:
说明:
这个命令 有很多的时间限制的业务场景的,例如:手机验证码(该验证码5分钟内有效)、外卖优惠券(该优惠券在指定的时间内有效)等等;
TTL
功能:获取指定 key 的过期时间,秒级
语法:
TTL key
时间复杂度:O(1)
返回值:剩余过期时间。-1 表⽰没有关联过期时间,-2 表⽰ key 不存在。
示例:
EXPIRE 和 TTL 命令都有对应的⽀持毫秒为单位的版本:PEXPIRE 和 PTTL,详细⽤法就不再
介绍了。
Redis的key的过期策略是怎么实现的?(经典面试题)
Redis整体策略是定期删除和惰性删除
- 定期删除:每次抽取一部分,进行验证过期时间;保证抽取检查的过程足够快;
- 惰性删除:假设这个key已经到过期的时间了,但是暂时还没有处理它,key还存在;紧接着后面又一次访问,正号用到了这个key,于是这次访问就会让Redis触发机制删除key的操作,同时在返回一个nil;
虽然有了上述两种策略结合整体效果一般,仍然可能会有很多过期的key被残留没有被即使删除;Redis为了对上述进行了补充,还提供了一系列的内存淘汰策略,我会在后面的文章中提到;
TYPE
功能:返回key对应的数据类型
语法:
TYPE key
时间复杂度:O(1)
返回值:none , string , list , set , zset , hash and stream .
示例:
本篇文章只是抛砖引⽟,给出⼏个通⽤的命令,为 5 种数据结构的使⽤做⼀个热⾝,后续章节将对
键管理做⼀个更为详细的介绍。
数据结构的内部编码
type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列
表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构
Redis的5中数据类型
实际上 Redis 针对每种数据结构都有⾃⼰的底层内部编码实现,⽽且是多种实现,这样 Redis 会
在合适的场景选择合适的内部编码
Redis数据结构和内部编码
可以看到每种数据结构都有⾄少两种以上的内部编码实现,例如 list 数据结构包含了 linkedlist 和
ziplist 两种内部编码。同时有些内部编码,例如 ziplist,可以作为多种数据结构的内部实现,可以通过 object encoding 命令查询内部编码:
可以看到 hello 对应值的内部编码是 embstr,键 mylist 对应值的内部编码是 ziplist。
Redis 这样设计有两个好处:
- 可以改进内部编码,⽽对外的数据结构和命令没有任何影响,这样⼀旦开发出更优秀的内部编码,⽆需改动外部数据结构和命令,例如 Redis 3.2 提供了 quicklist,结合了 ziplist 和 linkedlist 两者的优势,为列表类型提供了⼀种更为优秀的内部编码实现,⽽对⽤⼾来说基本⽆感知。
- 多种内部编码实现可以在不同场景下发挥各⾃的优势,例如 ziplist ⽐较节省内存,但是在列表元素⽐较多的情况下,性能会下降,这时候 Redis 会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程⽤⼾同样⽆感知。
单线程架构
Redis只是用一个线程来处理所有的命令请求,不是说一个Redis服务器进程内部真的只有一个线程,其实也有多个线程,多个线程是在 处理网络IO;
当有很多的客户端同时并发的对Redis服务器发起了请求;此时的服务器并不会因此而产生线程安全问题,因为Redis服务器实际上是采用单线程模型的,保证当前收到的多个请求是串行执行的;
多个请求同时到达Redis服务器时,也是要在队列中排队;再等待Redis服务器一个一个的取出里面的命令再执行;
Redis能够使用单线程模型很好的工作,原因主要在于Redis的核心业务逻辑都是短平快的,不太消耗CPU资源;
因此它的单线程模型也是它的缺点:使用Redis必须特别小心,某个操作占用时间太长,就会阻塞其他命令的执行;如果某个命令执⾏过⻓,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客⼾端的阻塞,对于 Redis 这种⾼性能的服务来说是⾮常严重的,所以 Redis 是⾯向快速执⾏场景的数据库。
为什么单线程还能这么块?(经典面试题)
- 就如上面所说Redis核心功能,比其他数据库的核心功能更简单
- 纯内存访问。Redis 将所有数据放在内存中,内存的响应时⻓⼤约为 100 纳秒,这是 Redis 达到每秒万级别访问的重要基础。其他数据库是访问硬盘。
- 单线程避免了线程切换和竞态产⽣的消耗。单线程可以简化数据结构和算法的实现,让程序模型更简单;其次多线程避免了在线程竞争同⼀份共享数据时带来的切换和等待消耗。
- ⾮阻塞 IO。Redis 使⽤ epoll 作为 I/O 多路复⽤技术的实现,再加上 Redis ⾃⾝的事件处理模型将 epoll 中的连接、读写、关闭都转换为事件,不在⽹络 I/O 上浪费过多的时间;也就是再处理网络IO的时候使用了IO多路复用机制
今天对Redis常用命令、内部编码、单线程模型的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!!