1.Redis的基本全局命令
Redis有多种数据结构,但它们都是键值对的,对于与键来说有一些通用的命令
1.1 KEYS
返回所有满足样式(pattern)的key
假定当前具有以下value值:hllo,hello,hallo,hxllo,heeeello
h?llo匹配hello,hallo和hxllo
h*llo匹配hllo,hello,hallo,hxllo,heeeello
h[ae]llo匹配hallo,hello但不匹配hxllo
h[^a]llo匹配hello,hxllo但不匹配hallo
h[a-b]匹配hallo
匹配规则:
? | 匹配任意一个字符 |
* | 匹配0~任意个字符 |
[ae] | 只能匹配字符a和字符e |
[^e] | 匹配不是e的一个字符 |
[a-e] | 匹配a,b,c,d,e这五种字符 |
语法:KEYS pattern
时间复杂度:O(N)
返回值:匹配pattern的所有key
示例:
注意:在实际生产环境中一般是禁止使用KEYS命令,因为Redis是单线程的服务器,如果key的数量很多 ,KEYS操作可能会消耗很长的时间,造成服务器产生阻塞,服务器无法给其他客户端提供服务。同时Redis作为缓存,挡在MySQL前,替MySQL负重前行,Redis倍KEYS命令阻塞,其他请求在Redis服务器超时,就会直接查MySQL,很容易把MySQL整挂了
1.2 EXISTS
判断某个key是否存在
语法:EXISTS key [key ...](多个key使用空格分隔)
时间复杂度:O(1)
返回值:key存在的个数
示例:
exists hello,exists hallo和exists hello hallo之间的区别:
Redis是一个客户端服务器结构的程序,客户端和服务器之间通过网络来进行通信,分开写,产生更多次网络通信(分装和复用),效率非常低,成本高(与直接操作内存相比)
1.3 DEL
删除制定的key
语法:DEL key [key ...]
时间复杂度:O(1)
返回值:删除掉的key的个数
示例:
1.4 EXPIRE
为指定的key添加秒级的过期时间(Time To Live,简称TTL)
语法:EXPIRE key seconds
时间复杂度:O(1)
返回值:1表示设置成功,0表示设置失败
示例:
1.5 TTL
获取指定key的过期时间,秒级
语法:TTL key
时间复杂度:O(1)
返回值:剩余过期时间。-1表示没有设置过期时间,-2表示key不存在
示例:
EXPIRE和TTL命令都有对应支持毫秒级单位的版本:PEXPIRE 和PTTL
经典面试题:Redis的key过期策略
1.定期删除
2.惰性删除
Redis的key过期策略采用惰性删除和定期删除相结合。惰性删除指key已经过期,但是暂时没有删除它,即key仍然存在,紧接着后续又一次访问正好用到了该key,于是Redis服务器触发删除key操作的命令,同时返回nil。这种惰性删除也要结合定期删除一起,定期删除的时间有明确的要求,这是因为Redis是单线程的程序,主要任务是处理每个命令的任务,扫描过期key等。如果扫描过期key消耗时间太多,导致正常处理其他请求命令阻塞(类似于KEYS *),所以定期删除一般都是每次只扫描一部分。
虽然Redis采用了这两种删除策略结合,但仍然有很多残留的key,没有及时删除,Redis对key过期还提供了一系列的内存淘汰策略(后续讲解)
1.6 TYPE
返回key对应的数据类型
语法:TYPE key
时间复杂度:O(1)
返回值:none,string,list,set,zset,hash和stream等
示例:
2.Redis中的数据结构和内部编码
实际上Redis针对每种数据结构都有自己的底层内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码
数据结构 | 内部编码(具体实现) |
string | raw(最基本的字符串,相当于Java中的Byte数组) int (redis存储键值对时,当value是整数时,redis可能会采用int) embstr(针对短字符串进行的特殊优化) |
hash | hashtable(redis内部哈希表的实现) ziplist(压缩列表,当hash中元素比较少的时候,可能会优化为ziplist) |
list | linkedlist(链表) ziplist quicklist(从Redis3.2开始,引入,结合了前两者的优点,是链表,每个节点又是一个ziplist,把空间和效率折衷顾虑到了) |
set | hashtable inset(集合中存放的都是整数) |
zset | skiplist(跳表,也是链表,不同于普通的链表,每个节点有多个指针域,通过巧妙地搭配实现查询元素的时间复杂度为O(logN)) ziplist |
可以看到,每种数据结构至少有两种以上的内部编码实现,那么我们如何去查询到这些数据结构的编码形式?通过object encoding命令查询内部编码
示例:
Redis这样设计的好处:
1.可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部代码,无需改动外部数据结构和命令,例如quicklist,结合ziplist和linkedlist两者的优点,为列表提供了一种新的内部编码实现,而对用户是无感知的
2.多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist可以节省内存,但在列表元素较多的情况下,性能会下降,这是Redis会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知
3.Redis的单线程架构
3.1单线程模型
Redis使用单线程架构来实现高性能的内存数据库服务,redis只使用一个主线程处理所有命令请求,并不是说redis服务器进程只有一个线程,其实也有多个线程,只不过在处理网络IO
上图三个客户端同时请求Redis服务,会不会产生线程不安全的情况呢?
不会,因为Redis是单线程的程序,多个请求在队列中排队,等待服务器一个一个取出命令并执行,从微观上讲Redis服务器串行/顺序执行命令的
经典面试题:Redis单线程为什么这么快
通常来讲单线程处理能力比多线程要差,但Redis单线程执行速度快,可以归结于以下几点:
1.Redis纯内存访问,Redis将所有数据存在的内存中,而读内存的速度要比读硬盘的速度要快得多
2.Redis核心功能比数据库的要简单
3.Redis采用单线程避免了多线程之间线程的切换和竞争所产生的开销,单线程也可以简化数据结构和算法的实现,让模型更加简单
4.Redis采用epoll作为I/O多路复用技术的实现,再加上Redis自身的时间处理模型将epoll中的连接读取,关闭都转换为时间,不在网络I/O上浪费过多的时间
Redis使用I/O多路复用模型
针对TCP来说,服务器这边每次要服务一个客户端,都需要给这个客户端安排一个Socket,一个服务器多个客户端,同时就有多个Socket 。这些Socket并不是无时无刻都在传输数据,很多情况下,每个客户端服务器之间的通信没有那么频繁,此时很多Socket都是静默状态的,上面没有要传输的数据,只有少数Socket活跃,于是采用一个线程来管理。
Java中使用NIO实现IO的多路复用(标准库中提供了一组类,底层封装了epoll)
虽然单线程给Redis带来了很多好处,但是也有一个致命的问题:对于单个命令的执行时间都是有要求的,如果某个命令执行时间过长,会导致其他命令全部处于阻塞队列中,迟迟得不到响应,造成客户端的阻塞,对于Redis、这种高性能的服务是非常严重的,所以Redis是面向快速执行场景的数据库