不同于
nginx
的精雕细琢,
redis
代码的风格趋向于简洁实用。简洁启事,下面所述不再列
举任何源码,不拼凑任何外来资料。去除末枝,下面直入
redis
主题,尽可能简洁地描述
redis
的设计思想。
整体模型:单进程单线程事件驱动模式。
Redis
在主处理流程中,采用了单进程接受各种
client
请求并返回结果,整体处理流程采用
事件驱动的方式进行。通过其
IO
复用的方式监听
aeEventLoop
事件,在事件处理的过程中,
这种模式对处理函数的要求:进行一次事件处理需要尽可能地快。因此会有以下几种处理
方法:
1.
对于非长时间处理事件,直接处理。如
set
命令;
2.
对于需要长时间处理的事件,分步处理,直至完成。如
rehash
过程,在单次事件处理过
程中,每次只移动一个桶。
3.
对于阻塞函数,如从磁盘读取
vm
数据等,采用异步的模式。从磁盘
load
数据,是一个较
为耗时的操作,如处理函数中直接读取数据,将会阻塞整体的单线程处理其他的
client
请
求。
多线程开发的复杂性是我们所清楚的。采用单进程单线程的模式,可以避免很多复杂性,
另外事务性操作,无须额外加锁,大大降低了开发难度。
IO
复用
Redis
的
IO
复 用 层 , 可 以 看 作 是 一 个
tiny libevent
, 其 实 现 只 有 四 个 文 件 ,
ae.c
,
ae_select.c
,
ae_kqueue.c
,
ae_epoll.c
。 一 目 了 然 ,
ae
是 事 件 监 听 总 的
interface
,
select
、
kqueque
、
epoll
是三种可选的
IO
复用方式。小而够用,简洁高效,充分
体现了
redis
的设计理念。
Redis Default
:
select
,
C10K max
。
事件监听
在单线程事件驱动模型中,往往会遇到两种任务,定时处理任务及事件触发任务,于是会
有如下问题:事件触发任务在无任务时,会处于阻塞状态;定时任务要求定时处理,于是
往往会有如下两种处理方式:
1.
如果定时任务时间间隔为
t
,一般设
IO
复用层的超时时间为
t/10
,这样可以保证定时任
务得到及时处理,在调用所有
cron
任务时,会做间隔时间判断。
2.
计算当前事件与最近的定时任务开始时间的间隔,设置
IO
复用超时时间为该事件。这样
定时任务也能得到及时处理。
注意:以上定时任务的处理时间,都为估约时间,非精确时间。前一种方式每轮计算量少
当容易引起空转,后一种计算量大,但减少了空转次数。
Redis
采用的是后一种方式。
这里也许有人会问:为什么不采用
sigaction
、
setitimer
等设置精确时钟,以后再也不用为
cron
事件做额外处理。原因如下:
定时时钟,往往会让进程陷入内核态,内核软中断往往会打破用户态一个函数的完整性,
因此会破坏相关的状态转移;另外加入软中断的程序,将可能为程序带来不可避免地复杂
性。
以曾经做过一个项目的一段挫折经历为例:
一个电话多路呼转的项目,采用的是单线程异步事件驱动模型,每次定时任务有个让空闲
通道“挂机”的任务。先前采用预制的驱动时钟,时钟信号为实时信号(信号宏值取
32-64
之
间的一个数),信号在
sigqueue
中排队,这样如果任务阻塞返回后,会瞬间抛出大量时钟
事件,造成空闲通道多次挂机失败。后改用
setitimer
设置系统时钟,软中断打断了用户态
相关文档
评论