一、IO模型
Redis是个单线程程序
Redis单线程为什么这么快?
Redis的数据都存在内存中,所有的运算都是内存级别的运算
Redis是单线程,为什么能处理那么多的并发连接?
Redis是非阻塞IO,采用了多路复用技术。最简单的世界
IO基本原理指令队列
Redis为每个客户端套接字都关联一个指令队列,客户端的指令在队列中采用先到先服务的顺序处理。
响应队列
Redis为每个客户端套接字都关联一个响应队列。Redis服务器通过响应队列来将指令返回给客户端。在select事件轮询函数中,若响应队列为空当前客户端的描述符可从write_fds移出来,有数据后再放进去,避免select频繁返回空内容,引起CPU消耗飙升。
二、通信协议
RESP
RESP是Redis序列化协议(Redis Serialization Protocol)的简写。它是一种直观的文本协议,优势在于实现过程异常简单,解析性能极好。
三、持久化
Redis的数据全部在内存里,需要持久化机制来防止因突然宕机而导致的数据丢失,持久化时进行的IO操作十分耗费资源,Redis主节点需要承受客户端请求的压力,因此持久化应当主要在从节点进行。
Redis持久化机制有两种,第一种是快照,第二种是AOF日志。
RDB快照
- 快照是一次全量备份
- 快照是内存数据的二进制序列化形式,在存储上非常紧凑
- 内存快照要求Redis必须进行文件IO,但文件IO操作不能使用多路复用API,会严重拖累服务器性能。
- Redis需要一边持久化一边响应请求。为了防止即将持久化的内容被改动,需要使用操作系统的多进程COW(copy on write)机制来实现快照持久化。Redis持久化时会调用fork函数产生一个子进程
子进程做数据持久化,不会修改现有的内存数据结构,它只对数据结构进行遍历读取,然后序列化写到磁盘中
父进程不断服务客户端请求,数据段是有很多操作系统的页面组合而成,在修改数据时会用COW机制将需要修改的页面复制一份分离出来,然后在这个副本上修改,子进程的相应页面没有变化
AOF日志
- AOF日志是连续的增量备份
- AOF日志存储的是Redis服务器的顺序指令序列,只记录对内存修改的指令记录
- AOF日志是先执行指令再记录日志。Redis在收到修改指令后,进行参数校验,逻辑处理,通过后才可存储。后记录日志是为了防止过多无效错误的指令增大日志文件大小。
- AOF日志记录的是内存数据修改的指令记录文本,长期运行后日志会变得无比庞大,需定期进行AOF重写,给AOF日志瘦身
Redis 提供了bgrewriteaof指令进行AOF日志瘦身。原理是开辟一个子进程对内存遍历,转换成一系列Redis操作指令,序列化到新的AOF日志中。序列化后再将操作期间发生的增量AOF追加到新AOF日志中,追加完成后立即用新日志替代旧日志 - AOF日志是以文件形式存在,程序对日志进行写操作时,实际上是先将内容写到内核为文件描述符分配的内存缓存,然后内核异步将脏数据刷回磁盘。为了防止宕机,可使用linux的glibc提供的fsync(int fd) 函数将指定文件强制从缓存刷回磁盘。
fsync函数是磁盘IO操作,速度很慢,在生产环境中通常是配置Redis每隔1s执行一次fsync操作
混合持久化
重启Redis恢复数据时,使用rdb会丢失大量数据,使用aof重放会花费很长时间。Redis4.0提供了混合持久化。重启时,先加载rdb内容,再重放增量AOF日志(自持久化开始到持久化结束的增量),重启效率大幅提升。
四、管道
请求交互流程图
read和write操作均是对内核中的缓冲区的操作,几乎不耗时。一次简单的请求真正耗时的时间开销是一个网络路由来回的时间。
管道操作的本质是调整读写次数,减少网络来回的次数来节省IO时间。
例如:写->读->写->读,会花费两个网络来回;写>写->读->读,连续的操作只花费了一个网络来回。
五、事务
Redis的事务可保证连续多个操作的原子性,但Redis事务比较简单,不具备真正的原子性,遇到错误指令后会继续执行剩下指令。
MULTI【开启事务】
EXEC【执行事务】
DISCARD【抛弃事务】
WATCH key [key …]【对其添加CAS乐观锁,WATCH的键受到监控,以检测其变化。如果在EXEC命令之前修改了至少一个监视键,则整个事务将中止,并且EXEC返回Null答复以通知该事务失败】
127.0.0.1:6379> mset a 1 b 1 c 1
OK
127.0.0.1:6379> watch b
OK
127.0.0.1:6379> incrby b 10
(integer) 11
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> incr b
QUEUED
127.0.0.1:6379> incr c
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> mget a b c
1) "1"
2) "11"
3) "1"
六、PubSub
支持消息多播的消息队列。缺点是PubSub不能持久化,Redis停机重启数据将丢失,因此推荐使用Redis5.0提供的Stream数据结构或更专业的消息中间件,如RabbitMQ、RocketMQ、ActiveMQ、Kafka等。
七、 小对象压缩
如果Redis管理的几何数据结构很小,它会使用紧凑存储形式压缩存储。当集合对象的元素不断增加,或者key或value值过大,则会从小对象结构变成标准结构
OBJECT ENCODING key【查看当前对象的存储结构】
redis> hset myhash foo 1000
OK
redis> object encoding myhash
"ziplist"
//redis.conf部分默认配置
hash-max-ziplist-entries 512 //hash的元素个数超过512个则由ziplist升级为hash结构
hash-max-ziplist-value 64 //hash的value长度超过64则由ziplist升级为hash结构
zset-max-ziplist-entries 128 //zset的元素个数超过512个则由ziplist升级为zset结构
zset-max-ziplist-value 64 //zset的value长度超过64则由ziplist升级为zset结构
set-max-intset-entries 512 //set的元素个数超过512个则由intset升级为set结构
内存回收机制
Redis并不总是将空闲内存立即返回给操作系统。操作系统是以页为单位回收内存的,一页上有许多的key,只有一页上所有的key被删除了才会回收此页。Redis虽然无法保证立即回收已删除key的内存,但会重新使用未回收的空闲内存。
如果有疑问,欢迎评论~
如果成功解决了你的问题,点个赞再走吖~