redis单线程原理
redis单线程问题
单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
1. 为什么说redis能够快速执行
(1) 绝大部分请求是纯粹的内存操作(非常快速)
(2) 采用单线程,避免了不必要的上下文切换和竞争条件
(3) 非阻塞IO - IO多路复用
2. redis的内部实现
内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间 这3个条件不是相互独立的,特别是第一条,如果请求都是耗时的,采用单线程吞吐量及性能可想而知了。应该说redis为特殊的场景选择了合适的技术方案。
3. Redis关于线程安全问题
redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。
4. IO多路复用
参考:https://www.zhihu.com/question/32163005
要弄清问题先要知道问题的出现原因
原因:
由于进程的执行过程是线性的(也就是顺序执行),当我们调用低速系统I/O(read,write,accept等等),进程可能阻塞,此时进程就阻塞在这个调用上,不能执行其他操作.阻塞很正常.
接下来考虑这么一个问题:一个服务器进程和一个客户端进程通信,服务器端read(sockfd1,bud,bufsize),此时客户端进程没有发送数据,那么read(阻塞调用)将阻塞,直到客户端调用write(sockfd,but,size)发来数据.在一个客户和服务器通信时这没什么问题;
当多个客户与服务器通信时当多个客户与服务器通信时,若服务器阻塞于其中一个客户sockfd1,当另一个客户的数据到达套接字sockfd2时,服务器不能处理,仍然阻塞在read(sockfd1,…)上;此时问题就出现了,不能及时处理另一个客户的服务,咋么办?
I/O多路复用来解决!
I/O多路复用:
继续上面的问题,有多个客户连接,sockfd1,sockfd2,sockfd3…sockfdn同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后就调用read读取收到消息的sockfd,然后又循环回select阻塞;这样就不会因为阻塞在其中一个上而不能处理另一个客户的消息
“I/O多路复用”的英文是“I/O multiplexing”,可以百度一下multiplexing,就能得到这个图:
Q:
那这样子,在读取socket1的数据时,如果其它socket有数据来,那么也要等到socket1读取完了才能继续读取其它socket的数据吧。那不是也阻塞住了吗?而且读取到的数据也要开启线程处理吧,那这和多线程IO有什么区别呢?
A:
1.CPU本来就是线性的不论什么都需要顺序处理并行只能是多核CPU
2.io多路复用本来就是用来解决对多个I/O监听时,一个I/O阻塞影响其他I/O的问题,跟多线程没关系.
3.跟多线程相比较,线程切换需要切换到内核进行线程切换,需要消耗时间和资源.而I/O多路复用不需要切换线/进程,效率相对较高,特别是对高并发的应用nginx就是用I/O多路复用,故而性能极佳.但多线程编程逻辑和处理上比I/O多路复用简单.而I/O多路复用处理起来较为复杂.
5. 使用Redis有哪些好处?
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
6. Redis相比memcached有哪些优势?
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据
(4)Redis支持数据的备份,即master-slave模式的数据备份。
(5) 使用底层模型不同,它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
(6)value大小:redis最大可以达到1GB,而memcache只有1MB
7. Redis常见性能问题和解决方案:
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件;(Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照;AOF文件过大会影响Master重启的恢复速度)
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…;这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
8. Redis的回收策略
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。
使用策略规则:
1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
9. 五种I/O模型介绍
IO 多路复用是5种I/O模型中的第3种,对各种模型讲个故事,描述下区别:
故事情节为:老李去买火车票,三天后买到一张退票。参演人员(老李,黄牛,售票员,快递员),往返车站耗费1小时。
1.阻塞I/O模型
老李去火车站买票,排队三天买到一张退票。
耗费:在车站吃喝拉撒睡 3天,其他事一件没干。
2.非阻塞I/O模型
老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。
耗费:往返车站6次,路上6小时,其他时间做了好多事。
3.I/O复用模型
1.select/poll
老李去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次
2.epoll
老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话
4.信号驱动I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话
5.异步I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。
耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话
1同2的区别是:自己轮询
2同3的区别是:委托黄牛
3同4的区别是:电话代替黄牛
4同5的区别是:电话通知是自取还是送票上门
Redis为何那么快-----底层原理浅析
Redis的快速很多人都知道是因为基于内存,但这只是一方面,其实redis在底层是一套很完善的多路复用事件处理机制来保证执行的高效的
线程模型
redis内部使用文件事件处理器file event handler,它包含如下几个部分
- 多个socket
- IO多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器,命令请求处理器,命令回复处理器)
之所以说redis是单线程其实是指这个文件事件处理器是单线程的,它采用多路复用的方式监听系统上多个socket,将socket上产生的事件压入队列中,由文件事件分派器从队列中取出一个socket根据事件类型发给相应的事件处理器
整个处理过程如图:
处理过程可以分为以下几个步骤:
- 客户端向redis发起一个socket请求,向redis的server socket请求连接,这里命名为socket01
- server socket产生一个AE_READABLE事件,IO多路复用程序监听到事件后将这个socket01压入队列
- 文件事件分派器从队列中取出socket01,交给连接应答处理器
- 连接应答处理器会将socket01的AE_READABLE事件与命令请求处理器相关联
- 假设客户端执行set操作,这时命令请求处理器会从socket01读取key value,在内存中完成key value的设置
- 在内存中完成设置后,会将socket01的AE_WRITEABLE事件与命令回复处理器相关联,然后压入队列中
- 事件分派器拿到socket01后,交给命令回复处理器,由命令回复处理器向socket01写入本次操作的结果,比如OK,之后解除关联
以上就是一个命令在redis中执行的过程
总结一下效率高的原因
- 内存操作
- IO多路复用机制,减少了阻塞
- 单线程避免了线程切换的开销和竞争问题
- 最最根本的redis是用C语言写的,本来就直接跟操作系统交互,命令执行快得飞起
Redis内部原理简介
知道了Redis的各种数据结构,对象结构,那么Redis是如何保存数据的,又是如何操作数据的呢,Redis里面的命令是怎么实现的呢?这一系列问题值得我们思考
一.Redis维护多个数据库
Redis内部维护一个db数组,每个db都是一个数据库,默认情况下Redis会创建16个数据库。我们可以通过select命令来切换数据库,如select1切换到数据库号为1的数据库。select实现是通过修改客户端的db指针,通过指针指向不同的数据库来实现数据库的切换操作的。
需要注意的是,为了不造成操作数据库号错误,最好执行命令之前,手动select一下数据库。
二.数据库键空间
Redis是一个键值对数据库服务器,Redis通过字典保存了数据库中的所有键值对,我们将这个字典称为键空间。键空间的每个键都是一个字符串对象,键空间的值也就是数据库的值,可以是字符串对象,列表对象,哈希表对象,集合对象,有序集合对象中的任何一种。
1.添加新键
每次添加一个新键就是将一个新键值对添加到键空间里面,其中键为字符串对象,值为任意一种类型的Redis对象。
2.删除键
删除键就是在键空间里删除键所对应的键值对对象。
3.更新键
更新键就是对键空间里面键所对应的值对象进行更新。
4.查找键
查找键就是在键空间中取出键所对应的值对象。
每次在键空间读取一个键之后,服务器会更新键的LRU时间,用于计算键的闲置时间。如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行后续操作。如果有客户端使用watch命令监视了某个键,那么服务器在对被监视的键进行修改之后,会将这个键标记为dirty,从而让事务注意到这个键被修改过。服务器每次修改一个键之后,都会对键计数器的值+1,这个计数器用来触发服务器的持久化操作。如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。
三.设置键的生存时间和过期时间
我们知道expire命令或者pexpire命令可以对一个键设置生存时间,经过指定的时间之后,服务器会自动删除生存时间为0的键。那么Redis是如何实现删除过期键的操作的呢?
Redis有四个命令可以设置键的过期时间,包括expire,pexpire,expireat,pexpireat,不过这四个命令最后都会转化成pexpireat命令来实现。
Redis使用一个过期字典记录所有带过期时间的键,字典的键指向键空间中的某个键对象,字典的值是一个longlong类型的整数,这个证书保存了键空间所指向的数据库键的过期时间。通过过期字典,程序可以检查一个给定键是否过期,检查给定键是否存在于过期字典,如果存在,取得键的过期时间,检查当前时间戳是否大于键的过期时间,如果是的话,键已经过期,否则键未过期。
四.过期键的删除策略
如果一个键过期了,那么它什么时候被删除呢?通常我们可以用三种删除策略删除过期键
1.定时删除:在设置键过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,删除键
2.惰性删除:放任键过期不管,但是每次动键空间获取键时,都会检查键是否过期,如果过期,则删除。
3.定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。
这几种方式各有利有弊,首先定时删除对内存最友好,当一个键过期时,一定会删除这个键,释放内存。不过定时删除对CPU最不友好,在过期键比较多的情况下,删除过期键这一行为可能会占用相当一部分CPU时间。此外,创建定时器需要用到Redis服务器中的时间时间,而当前时间时间的实现方式-无序链表查找一个事件的时间复杂度为O(N),不能高效地处理大量时间事件。
惰性删除策略对CPU是最友好的,但是对内存最不友好。如果一个键已经过期,这个键又保留在数据库中,那么内存就会一直占用不释放。
定期删除算是前两种策略的一种整合和折中,定期策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率减少删除操作对CPU时间的影响。定期删除过期键可以有效地减少因为过期键带来的内存浪费。
Redis过期键的删除使用惰性删除和定期删除两种策略配合使用。惰性策略比较好理解,所有读写数据库的命令执行之前都会对输入键进行检查,如果键过期,那么从数据库中删除键。定期删除策略的实现由Redis的serverCron函数来执行,这个函数每100ms执行一次,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,删除其中的过期键。
五.复制功能对过期键的处理
Redis复制主要包括RDB复制和AOF复制,在RDB复制中,每次执行SAVE或BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。载入RDB文件时,服务器也会对保存的键进行检查,如果键已过期,则不会载入。当使用AOF持久化模式运行时,当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加一条删除命令,记录键已被删除。