做者:施洪宝 顺风车运营研发团队
一. 压缩列表
压缩列表是Redis的关键数据结构之一。目前已经有大量的相关资料,下面几个连接都已经对Ziplist进行了详细的介绍。html
http://origin.redisbook.com/c...
https://segmentfault.com/a/11...
本文只对其进行整体上的介绍,更多详细内容能够参考上面3个连接以及Redis源码。redis
Ziplist总体结构图以下:编程
上图中,每一个域的具体功能为:segmentfault
2.节点结构
Ziplist的节点结构以下:api
(1) pre_entry_length数组
pre_entry_length字段记录了上一个节点的长度,经过这个值,咱们能够很容易的从当前节点跳转到上一节点。例如:服务器
根据编码方式的不一样,pre_entry_length可能占用一个字节,也可能占用5个字节。具体处理规则以下:网络
若是前一个节点长度小于254,便使用1个字节记录上一个节点长度。数据结构
若是前一个字节长度大于254,pre_entry_length便占用5个字节,第一个字节写入254,后面4个字节记录上一个节点的具体长度。多线程
(2) enconding以及length
encoding 和 length 决定了content 保存的数据类型及长度,encodingz占用2个bit,encoding以及length的总长度多是1个,2个或者5个字节。
00,01,10表示content的内容为字符数组
11表示content的内容为整形
具体的规则以下:
(3) content
content为所存储的数据,其类型和长度由encoding 和 length 决定。
(4) Example
3.基本操做
数据结构的基本操做有:增长节点、删除节点、查找节点等。因为Ziplist在内存上是连续存储的,故而在特定位置插入或者删除操做的复杂度较高。Ziplist定义了下面的几种操做:
值得一提的是,在特定位置插入或者删除时,程序须要进行一种称之为连锁更新的操做以维持Ziplist结构的性质。以上操做的具体代码实现,能够参考Redis源码,此处再也不赘述。
二. Server
这部分主要是从服务端,分析Redis响应客户请求的原理。
1.基础知识
Linux下有“一切皆文件”的思想。咱们能够将socket、pipe、硬件等资源都看做文件,利用操做文件的方式对其进行操做。具体到网络通讯中,服务端与客户端的数据交换能够看做是对文件的读写操做。
例如:服务器端首先监听端口,具体实现就是新建一个监听文件描述符,当这个监听文件可读时,就是有新的客户端请求链接。
服务端在接受一个新的链接请求时,也会新建一个文件描述符,这个描述符即表明这个网络链接。经过对这个文件的读写,便可实现与客户端的通讯。
正常状况下,一个服务端一般要同时服务多个客户,服务端处理客户请求的主逻辑以下:
while(1){ //等待网络事件发生 //根据每一个事件,调用相关的回调函数。 }
注:客户端向服务端发送数据时,网卡首先接收到数据,以后向操做系统提出中断请求,操做系统将通知服务进程处理这些数据。
2.IO多路复用
《UNIX网络编程》中提到,IO模型能够分为5种:同步阻塞、同步非阻塞、IO复用、信号驱动以及异步非阻塞。阻塞、非阻塞是针对调用方而言的。同步、异步是针对被调用方而言的。本节,咱们重点介绍IO多路复用。
IO复用:顾名思义,就是多个IO复用一个端口。常见的IO多路复用的方案有:select, poll, kqueue, epoll等。
(1) Select简介
select 的函数原型以下:
int select(int maxfdAdd1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); //maxfdAdd1, 所要监听的最大文件描述符加1 //readset, writeset, exceptset, 须要监听读、写、错误的文件描述符 fd_set 多是由数组,或者是由二进制位构成的数组。这个取决于操做系统的具体实现。 //timeout, 超时时间 struct timeval{ long tv_sec; //秒 long tv_usec; //微妙 };
函数返回值为发生事件的FD总数。
select的过程能够简述以下:应用程序首先调用select函数,经过入口参数将所要监听的文件描述符传递给操做系统,操做系统负责具体监听这些文件描述符,当有事件发生时,通知进程处理。
(2) Epoll简介
对于每一个文件描述符,epoll能够设置2种事件触发方式,水平触发与边沿触发。
水平触发:顾名思义,就是当FD有事件时,会一直通知进程。例如:当文件可读时,操做系统告诉进程,文件可读,进程处理以后,若是因为时间缘由,没法读取所有数据。此时,操做系统会仍然告诉进程FD可读。
边沿触发:所谓边沿,就是指事物状态发生改变。边沿触发,就是仅当FD状态发生改变时,通知进程。若是操做系统通知进程以后,进程并无所有读取数据,操做系统仅会在FD状态发生改变时(例如:新数据到来),才会通知进程去处理。
//函数原型 int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, strcut epoll_event *event); int epoll_wait(int epfd, struct epoll_event *event, int maxEvent, int timeout); //epfd自己会占用一个文件描述符,不用时应将其关闭。 typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; //简单的使用用例,限于篇幅,本部分只列出其中的关键语句。 int epfd = epoll_create(size); //调用epoll_ctl(epfd, op, fd, *events), 增长或者减小所要监听的Fd while(1){ int num = epoll_wait(epfd, *events, maxEvent, timeout); //events 记录全部发生事件的FD for(int i = 0; i < num; ++i){ //根据每一个FD,调用对应的事件处理函数 } }
Epoll和Select相比,效率更高,这主要因为如下几个缘由:
Select每次调用,都须要将全部文件描述符从用户态复制到内核态,Epoll能够经过epoll_ctl,每一个文件描述符只须要复制一次。
对select而言,操做系统须要遍历全部文件,从而找出发生事件的文件描述符。操做系统为每一个epoll维护了一个双向链表,当某个文件可读或者可写时,经过回调事先设定的回调函数,将文件描述符写入这个双向链表。操做系统每次只须要查看这个链表,便可知道是否有事件发生。
Select返回时,须要程序遍历全部监听的文件描述符,从而找出发生事件的文件。Epoll能够直接获得发生事件的文件描述符(epoll_wait函数,结果会被写入events)。
Redis 是做为服务端向客户提供服务的。网络编程的基本模型有单进程单线程、单进程多线程、多进程单线程以及多进程多线程。Redis在提供服务时,是基于单进程单线程的模型,经过IO复用技术,实现高并发。Redis源码4.0.9版,已经实现了select, kqueue, epoll以适应不一样的需求(redis-4.0.9/src/ae_select.c, redis-4.0.9/src/ae_kqueue.c, redis-4.0.9/src/ae_epoll.c)。
(1) 关键数据结构
aeEventLoop为Reids最重要的数据结构之一,表明服务进程的事件处理循环。
/* State of an event based program */ typedef struct aeEventLoop { int maxfd; /* highest file descriptor currently registered */ int setsize; /* max number of file descriptors tracked */ long long timeEventNextId; time_t lastTime; /* Used to detect system clock skew */ aeFileEvent *events; /* Registered events */ aeFiredEvent *fired; /* Fired events */ aeTimeEvent *timeEventHead; int stop; void *apidata; /* This is used for polling API specific data */ aeBeforeSleepProc *beforesleep; aeBeforeSleepProc *aftersleep; } aeEventLoop;
aeFileEvent封装通常的文件事件
typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */ aeFileProc *rfileProc; aeFileProc *wfileProc; void *clientData; } aeFileEvent;
aeTimeEvent用来封装定时器事件
/* Time event structure */ typedef struct aeTimeEvent { long long id; /* time event identifier. */ long when_sec; /* seconds */ long when_ms; /* milliseconds */ aeTimeProc *timeProc; aeEventFinalizerProc *finalizerProc; void *clientData; struct aeTimeEvent *next; } aeTimeEvent;
记录已经发生事件的文件描述符集合
/* A fired event */ typedef struct aeFiredEvent { int fd; int mask; } aeFiredEvent;
(2)Reis服务端简要流程Reis服务启动的入口为Server.c中的main函数,在main函数中,会进行一些数据初始化、配置初始化、设置回调函数等工做。以后Redis会进入事件主循环(aeMain函数),等待事件发生,处理事件。