Redis详解:原理和机制

一、性能
1 性能测试
测试环境:RHEL 6.3 / HP Gen8 Server/ 2 * Intel Xeon 2.00GHz(6 core) / 64G DDR3 memory / 300G RAID-1 SATA / 1 master(writ AOF), 1 slave(write AOF & RDB)
数据准备:预加载两千万条数据,占用10G内存 。
测试工具:自带的redis-benchmark,默认只是基于一个很小的数据集进行测试,调整命令行参数如下,就可以开100条线程(默认50),SET 1千万次(key在0-1千万间随机),key长21字节,value长256字节的数据 。
1
1.SET:4.5万,
2.GET:6万 ,
3.INCR:6万,
4.真实混合场景: 2.5万SET & 3万GET
单条客户端线程时6千TPS,50与100条客户端线程差别不大,200条时会略多 。
Get/Set操作,经过了LAN,延时也只有1毫秒左右,可以反复放心调用,不用像调用REST接口和访问数据库那样,每多一次外部访问都心痛 。
资源监控:
1.CPU: 占了一个处理器的100%,总CPU是4%(因为总共有2CPU*6核*超线程 = 24个处理器),可见单线程下单处理器的能力是瓶颈 。AOF rewrite时另一个处理器占用50-70% 。
2.网卡:15-20 MB/s receive, 3Mb/s send(no slave) or 15-20 MB/s send (with slave)。当把value长度加到4K时,receive 99MB/s,已经到达千兆网卡的瓶颈,TPS降到2万 。
3.硬盘:15MB/s(AOF Append), 100MB/s(AOF rewrite/AOF load,普通硬盘的瓶颈),
2 为什么快
主要是以下几点点
一)、纯内存操作
数据存放在内存中,内存的响应时间大约是 100纳秒 ,这是Redis每秒万亿级别访问的重要基础 。
二)、单线程操作,避免了频繁的上下文切换
虽然是采用单线程,但是单线程避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU;虽然作者认为CPU不是瓶颈,内存与网络带宽才是 。但实际测试时并非如此,见上 。
三)、采用了非阻塞I/O多路复用机制
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作 。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程 。加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了事件,不在I/O上浪费过多的时间 。
四)、纯ANSI C编写 。
不依赖第三方类库,没有像memcached那样使用libevent,因为libevent迎合通用性而造成代码庞大,所以作者用libevent中两个文件修改实现了自己的epoll event loop 。微软的兼容windows补丁也因为同样原因被拒了 。
快,原因之一是Redis多样的数据结构,每种结构只做自己爱做的事,当然比数据库只有Table,MongogoDB只有JSON一种结构快了 。
二、I/O复用模型和Reactor 设计模式
Redis内部实现采用epoll+自己实现的简单的事件框架 。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性, 绝不在io上浪费一点时间:
1、I/O 多路复用的封装
I/O 多路复用其实是在单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流 。

Redis详解:原理和机制

文章插图
因为 Redis 需要在多个平台上运行,同时为了最大化执行的效率与性能,所以会根据编译平台的不同选择不同的 I/O 多路复用函数作为子模块,提供给上层统一的接口 。
redis的多路复用, 提供了select, epoll, evport, kqueue几种选择,在编译的时候来选择一种 。
select是POSIX提供的, 一般的操作系统都有支撑;
epoll 是linux系统内核提供支持的;
evport是Solaris系统内核提供支持的;
kqueue是mac 系统提供支持的;
# ifdefHAVE_EVPORT
# include"ae_evport.c"
# else
# ifdefHAVE_EPOLL
# include"ae_epoll.c"
# else
# ifdefHAVE_KQUEUE
# include"ae_kqueue.c"
# else
# include"ae_select.c"
# endif
# endif
# endif
为了将所有 IO 复用统一,Redis 为所有 IO 复用统一了类型名 aeApiState,对于 epoll 而言,类型成员就是调用 epoll_wait所需要的参数
接下来就是一些对epoll接口的封装了:包括创建 epoll(epoll_create),注册事件(epoll_ctl),删除事件(epoll_ctl),阻塞监听(epoll_wait)等
创建 epoll 就是简单的为 aeApiState 申请内存空间,然后将返回的指针保存在事件驱动循环中


推荐阅读