0. 手把手教你作中间件、高性能服务器、分布式存储技术交流群linux
手把手教你作中间件、高性能服务器、分布式存储等(redis、memcache、nginx、大容量redis pika、rocksdb、mongodb、wiredtiger存储引擎、高性能代理中间件),git地址以下:nginx
git地址:https://github.com/y123456yz/middleware_development_learningc++
1. 开发背景git
现有开源缓存代理中间件有twemproxy、codis等,其中twemproxy为单进程单线程模型,只支持memcache单机版和redis单机版,都不支持集群版功能。github
因为twemproxy没法利用多核特性,所以性能低下,短链接QPS大约为3W,长链接QPS大约为13W,同时某些场景时延抖动厉害。web
为了适应公有云平台上业务方的高并发需求,所以决定借助于twemproxy来作二次开发,把nginx的高性能、高可靠、高并发机制引入到twemproxy中,经过master+多worker进程来实现七层转发功能。redis
2 Twemproxymongodb
2.1 Twemproxy简介后端
Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII协议和更新的Redis协议。它所有用C写成,使用Apache 2.0 License受权。支持如下特性:centos
i)速度快
ii)轻量级
iii)维护持久的服务器链接
iiii)启用请求和响应的管道
iiiii)支持代理到多个后端缓存服务器
iiiii)同时支持多个服务器池
iiiiii)多个服务器自动分享数据
iiiiiii)可同时链接后端多个缓存集群
iiiiiiii)实现了完整的 memcached ascii 和 redis 协议.
iiiiiiiii)服务器池配置简单,经过一个 YAML 文件便可
iiiiiiiiii)一致性hash
iiiiiiiiii)详细的监控统计信息
iiiiiiiiiii)支持 Linux, *BSD, OS X and Solaris (SmartOS)
iiiiiiiiiiii)支持设置HashTag
iiiiiiiiiiiiiii)链接复用,内存复用,提升效率
2.2 memcache缓存集群拓扑结构
图1 twemproxy缓存集群拓扑图
如上图所示,实际应用中业务程序经过轮询不一样的twemproxy来提升qps,同时实现负载均衡。
说明:官方memcache没有集群版和持久化功能,集群版和持久化功能由咱们本身内部开发完成。
2.3 推特原生twemproxy瓶颈
现在twemproxy凭借其高性能的优点, 在不少互联网公司获得了普遍的应用,已经占据了其不可动摇的地位, 然而在实际的生产环境中, 存在如下缺陷,以下:
i)单进程单线程, 没法充分发挥服务器多核cpu的性能
ii)当twemproxy qps短链接达到8000后,消耗cpu超过70%,时延陡增。
iii)大流量下形成IO阻塞,没法处理更多请求,qps上不去,业务时延飙升
iiii)维护成本高,若是想要充分发挥服务器的全部资源包括cpu、 网络io等,就必须创建多个twemproxy实例,维护成本高
iiiii)扩容、升级不便
原生twemproxy进程呈现了下图现象:一我的干活,多我的围观。多核服务器只有一个cpu在工做,资源没有获得充分利用。
3. Nginx
nginx是俄罗斯软件工程师Igor Sysoev开发的免费开源web服务器软件,聚焦于高性能,高并发和低内存消耗问题,所以成为业界公认的高性能服务器,并逐渐成为业内主流的web服务器。主要特色有:
i)彻底借助epoll机制实现异步操做,避免阻塞。
ii)重复利用现有服务器的多核资源。
iii)充分利用CPU 亲和性(affinity),把每一个进程与固定CPU绑定在一块儿,给定的 CPU 上尽可能长时间地运行而不被迁移到其余处理器的倾向性,减小进程调度开销。
iiii)请求响应快
iiiii)支持模块化开发,扩展性好
iiiii)Master+多worker进程方式,确保worker进程可靠工做。当worker进程出错时,能够快速拉起新的worker子进程来提供服务。
iiiiii)内存池、链接池等细节设计保障低内存消耗。
iiiiii)热部署支持,master与worker进程分离设计模式,使其具备热部署功能。
iiiiiii)升级方便,升级过程不会对业务形成任何伤害。
Nginx多进程提供服务过程以下图所示:
4 Nginx master+worker多进程机制在twemproxy中的应用
4.1 为何选择nginx多进程机制作为参考?
Twemproxy和nginx都属于网络io密集型应用,都属于七层转发应用,时延要求较高,应用场景基本相同。
Nginx充分利用了多核cpu资源,性能好,时延低。
4.2 Master-worker多进程机制原理
Master-worker进程机制采用一个master进程来管理多个worker进程。每个worker进程都是繁忙的,它们在真正地提供服务,master进程则很“悠闲”,只负责监控管理worker进程, 包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常状况下),会自动从新启动新的worker进程。
worker进程负责处理客户端的网络请求,多个worker进程同时处理来自客户端的不一样请求,worker进程数可配置。
4.3 多进程关键性能问题点
master-worker多进程模式须要解决的问题主要有:
i)linux内核低版本(2.6如下版本), “惊群”问题
ii) linux内核低版本(2.6如下版本),负载均衡问题
iii)linux内核高版本(3.9以上版本)新特性如何利用
iii)如何确保进程见高可靠通讯
iiii)如何减小worker进程在不一样cpu切换的开销
iiiii)master进程如何汇总各个工做进程的监控数据
iiiiii)worker进程异常,如何快速恢复
4.3.1 linux内核低版本关键技术问题
因为linux低内核版本缺陷,所以存在”惊群”、负载不均问题,解决办法彻底依赖应用层代码保障。
4.3.1.1 如何解决“惊群”问题
当客户端发起链接后,因为全部的worker子进程都监听着同一个端口,内核协议栈在检测到客户端链接后,会激活全部休眠的worker子进程,最终只会有一个子进程成功创建新链接,其余子进程都会accept失败。
Accept失败的子进程是不该该被内核唤醒的,由于它们被唤醒的操做是多余的,占用本不该该被占用的系统资源,引发没必要要的进程上下文切换,增长了系统开销,同时也影响了客户端链接的时延。
“惊群”问题是多个子进程同时监听同一个端口引发的,所以解决的方法是同一时刻只让一个子进程监听服务器端口,这样新链接事件只会唤醒惟一正在监听端口的子进程。
所以“惊群”问题经过非阻塞的accept锁来实现进程互斥accept(),其原理是:在worker进程主循环中非阻塞trylock获取accept锁,若是trylock成功,则此进程把监听端口对应的fd经过epoll_ctl()加入到本进程自由的epoll事件集;若是trylock失败,则把监听fd从本进程对应的epoll事件集中清除。
Nginx实现了两套互斥锁:基于原子操做和信号量实现的互斥锁、基于文件锁封装的互斥锁。考虑到锁的平台可移植性和通用性,改造twemproxy选择时,选择文件锁实现。
若是获取accept锁成功的进程占用锁时间过长,那么其余空闲进程在这段时间内没法获取到锁,从而没法接受新的链接。最终形成客户端链接相应时间变长,qps低,同时引发负载严重不均衡。为了解决该问题,选择经过post事件队列方式来提升性能,trylock获取到accept锁成功的进程,其工做流程以下:
1.trylock获取accept锁成功
2.经过epoll_wait获取全部的事件信息,把监听到的全部accept事件信息加入accept_post列表,把已有链接触发的读写事件信息加入read_write_post列表。
3.执行accept_post列表中的全部事件
4.Unlock锁
5.执行read_write_post列表中的事件。
Worker进程主循环工做流程图以下:
从上图能够看出,worker进程借助epoll来实现网络异步收发,客户端链接twemproxy的时候,worker进程循环检测客户端的各类网络事件和后端memcached的网络事件,并进行相应的处理。
twemproxy各个进程总体网络i/o处理过程图以下:
4.3.1.2 如何解决“负载均衡“问题
在多个子进程争抢处理同一个新链接事件时,必定只有一个worker子进程最终会成功创建链接,随后,它会一直处理这个链接直到链接关闭。这样,若是有的子进程“运气”很好,它们抢着创建并处理了大部分链接,其余子进程就只能处理少许链接,这对多核cpu架构下的应用很不利。理想状况下,每一个子进程应该是平等的,每一个worker子进程应该大体平均的处理客户端链接请求。若是worker子进程负载不均衡,必然影响总体服务的性能。
nginx经过链接阈值机制来实现负载均衡,其原理以下:每一个进程都有各自的最大链接数阈值max_threshold和当前链接阈值数local_threshold,和当前链接数阈值,进程每接收一个新的链接,local_threshold增一,链接断开后,local_threashold减一。若是local_threshold超过max_threshold,则不去获取accept锁,把accept机会留给其余进程,同时把local_threshold减1,这样下次就有机会获取accept锁,接收客户端链接了。
在实际业务应用中,有的业务采用长链接和twemproxy创建链接,链接数最大可能就几百链接,若是设置max_threshold阈值过大,多个链接若是同时压到twemproxy,则很容易引发全部链接被同一个进程获取从而形成不均衡。
为了尽可能减小负载不均衡,在实际应用中,新增了epoll_wait超时时间配置选项,把该超时时间设短,这样减小空闲进程在epoll_wait上的等待事件,从而能够更快相应客户端链接,并有效避免负载不均衡。
4.3.2 Linux内核高版本TCP REUSEPORT特性如何利用
4.3.2.1 什么是reuseport?
reuseport是一种套接字复用机制,它容许你将多个套接字bind在同一个IP地址/端口对上,这样一来,就能够创建多个服务来接受到同一个端口的链接。
4.3.2.2 支持reuseport和不支持reuseport的区别
若是linux内核版本小于3.9,则不支持reuseport(注:部分centos发行版在低版本中已经打了reuseport patch,因此部分linux低版本发行版本也支持该特性)。
不支持该特性的内核,一个ip+port组合,只能被监听bind一次。这样在多核环境下,每每只能有一个线程(或者进程)是listener,也就是同一时刻只能由一个进程或者线程作accept处理,在高并发状况下,每每这就是性能瓶颈。其网络模型以下:
在Linux kernel 3.9带来了reuseport特性,它能够解决上面的问题,其网络模型以下:
reuseport是支持多个进程或者线程绑定到同一端口,提升服务器程序的吞吐性能,其优势体如今以下几个方面:
i)容许多个套接字 bind()/listen() 同一个TCP/UDP端口
ii)每个线程拥有本身的服务器套接字
iii)在服务器套接字上没有了锁的竞争,由于每一个进程一个服务器套接字
iiii)内核层面实现负载均衡
iiiii)安全层面,监听同一个端口的套接字只能位于同一个用户下面
4.3.3 Master进程和worker进程如何通讯?
因为master进程须要实时获取worker进程的工做状态,并实时汇总worker进程的各类统计信息,因此选择一种可靠的进程间通讯方式必不可少。
在twemproxy改造过程当中,直接参考nginx的信号量机制和channel机制(依靠socketpair)来实现父子进程见通讯。Master进程经过信号量机制来检测子进程是否异常,从而快速直接的反应出来;此外,借助socketpair,封装出channel接口来完成父子进程见异步通讯,master进程依靠该机制来统计子进程的各类统计信息并汇总,经过获取来自master的汇总信息来判断整个twemproxy中间件的稳定性、可靠性。
配置下发过程:主进程接收实时配置信息,而后经过channel机制发送给全部的worker进程,各个worker进程收到配置信息后应答给工做进程。流程以下:
获取监控信息流程和配置下发流程基本相同,master进程收到各个工做进程的应答后,由master进程作统一汇总,而后发送给客户端。
4.3.4 如何减小worker进程在不一样cpu切换的开销
CPU 亲和性(affinity) 就是进程要在某个给定的 CPU 上尽可能长时间地运行而不被迁移到其余处理器的倾向性。
Linux 内核进程调度器天生就具备被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程一般不会在处理器之间频繁迁移。这种状态正是咱们但愿的,由于进程迁移的频率小就意味着产生的负载小。具体参考sched_setaffinity函数。
4.3.5 worker进程异常如何减小对业务的影响?
在实际线上环境中,常常出现这样的状况:某个多线程服务跑几个月后,由于未知缘由进程挂了,最终形成整个服务都会不可用。
这时候,master+多worker的多进程模型就体现了它的优点,若是代码有隐藏的而且不容易触发的bug,某个时候若是某个请求触发了这个bug,则处理这个请求的worker进程会段错误退出。可是其余worker进程不会收到任何的影响,也就是说若是一个改造后的twemproxy起了20个worker进程,某个时候一个隐藏bug被某个请求触发,则只有处理该请求的进程段错误异常,其余19个进程不会受到任何影响,该隐藏bug触发后影响面仅为5%。若是是多线程模型,则影响面会是100%。
若是某个worker进程挂了,master父进程会感知到这个信号,而后从新拉起一个worker进程,实现瞬间无感知”拉起”恢复。如下为模拟触发异常段错误流程:
如上图所示,杀掉31420 worker进程后,master进程会立马在拉起一个31451工做进程,实现了快速恢复。
多进程异常,自动”拉起”功能源码,能够参考以下demo:
https://github.com/y123456yz/reading-code-of-nginx-1.9.2/blob/master/nginx-1.9.2/src/demo.c
5 网络优化
5.1 网卡多队列
在实际上线后,发现软中断太高,几乎大部分都集中在一个或者几个CPU上,严重影响客户端链接和数据转发,qps上不去,时延抖动厉害。
RSS(Receive Side Scaling)是网卡的硬件特性,实现了多队列,能够将不一样的流分发到不一样的CPU上。支持RSS的网卡,经过多队列技术,每一个队列对应一个中断号,经过对每一个中断的绑定,能够实现网卡中断在cpu多核上的分配,最终达到负载均衡的做用。
5.2 可怕的40ms
原生twemproxy在线上跑得过程当中,发现时延波动很大,抓包发现其中部分数据包应答出现了40ms左右的时延,拉高了总体时延抓包以下(借助tcprstat工具):
解决办法以下:在recv系统调用后,调用一次setsockopt函数,设置TCP_QUICKACK。代码修改以下:
6 Twemproxy改造先后性能对比 (时延、qps对比)
6.1 线上真实流量时延对比
6.1.1 改造前线上twemproxy集群时延
线上集群彻底采用开源twemproxy作代理,架构以下:
未改造前线上twemproxy+memcache集群,qps=5000~6000,长链接,客户端时延分布以下图所示:
在twemproxy机器上使用tcprstat监控到的网卡时延以下:
从上面两个图能够看出,采用原生twemproxy,时延高,同时抖动厉害。
6.1.2 参照nginx改造后的twemproxy时延
线上集群一个twemproxy采用官方原生twemproxy,另外一个为改造后的twemproxy,其中改造后的twemproxy配置worker进程数为1,保持和原生开源twemproxy进程数一致,架构以下:
替换线上集群两个代理中的一个后(影响50%流量),长链接,qps=5000~6000,客户端埋点监控时延分布以下:
替换两个proxy中的一个后,使用tcprstat在代理集群上面查看两个代理的时延分布以下:
原生twemproxy节点机器上的时延分布:
另外一个改造后的twemproxy节点机器上的时延分布:
总结:替换线上两个proxy中的一个后,客户端时间下降了一倍,若是线上集群两个代理都替换为改造后的twemproxy,客户端监控时延预计会再下降一倍,整体时延下降3倍左右。
此外,从监控能够看出,改造后的twemproxy时延更低,更加稳定,无任何波动。
6.2 参考nginx多进程改造后的twemproxy线下压测结果(开启reuseport功能)
监听同一个端口,数据长度100字节,压测结果以下:
linux内核版本:linux-3.10
物理机机型: M10(48 cpu)
多进程监听同一个端口,数据长度150字节,压测结果以下:
linux内核版本:linux-3.10
物理机机型: TS60 (24 cpu)
7 总结
7.1 多进程、多线程机制选择
选择参照nginx多进程机制,而不选择多线程实现缘由主要有:
1) 多进程机制无锁操做,实现更容易
2) 多进程的代理,整个worker进程无任何锁操做,性能更好
3) 若是是多线程方式,若是代码出现bug段错误,则整个进程挂掉,整个服务不可用。而若是是多进程方式,由于bug触发某个worker进程段错误异常,其余工做进程不会受到如何影响,20个worker进程,若是触发异常,同一时刻只有有1/20的流量受到影响。而若是是多线程模式,则100%的流量会受到影响。
4) worker进程异常退出后,master进程立马感知拉起一个新进程提供服务,可靠性更高。
5) 配置热加载、程序热升级功能实现更加容易
7.2 参照nginx改造后的twemproxy特性
支持nginx几乎全部的优秀特性,同时也根据本身实际状况新增长了自有特性:
1) master+多worker进程机制
2) 适配全部linux内核版本,内核低版本惊群问题避免支持
3) quic_ack支持
4) reuser_port适配支持
5) worker进程异常,master进程自动拉起功能支持
6) 90%、95%、98%、100%平均时延统计功能支持
7) memcache单机版、集群版支持
8) redis单机版、集群版支持
9) 二进制协议、文本协议同时支持
10) redis、memcache集群在线扩容、缩容、数据迁移支持,扩缩容、数据迁移过程对业务无任何影响。
11) 多租户支持,一个代理能够接多个memcache、redis集群,并支持混部。
12) mget、gets、sets等批量处理命令优化处理
13) 慢响应日志记录功能支持
14) 内存参数实时修改支持
15) 详细的集群监控统计功能
16) CPU亲缘性自添加
17)内存配置动态实时修改
7.3后期计划
添加以下功能:
i) 配置文件热加载支持。
ii) 代码热升级功能支持。
7.4 长远规划展望
抽象出一款相似nginx的高性能代理软件,nginx支持http协议,咱们的支持tcp协议代理,覆盖nginx全部功能,包括前面提到的全部功能,同时支持模块化开发。这样,不少的tcp协议代理就无需关心网络架构底层实现,只须要根据须要开发对应的协议解析模块,和本身关心的统计、审计等功能功能,下降开发成本。现有开源的中间件,很大一部分都是tcp的,有本身的私有tcp协议,把这个抽象出来,开发成本会更低
对nginx有兴趣的能够源码分析参考:
https://github.com/y123456yz/reading-code-of-nginx-1.9.2
内核网卡时延分析工具:
https://github.com/y123456yz/tcprstat
twemproxy源码分析:
https://github.com/y123456yz/Reading-and-comprehense-twemproxy0.4.1
内核协议栈延迟确认机制:
https://github.com/y123456yz/Reading-and-comprehense-linux-Kernel-network-protocol-stack
手把手教你作中间件、分布式存储、高性能服务端开发等开发:
https://github.com/y123456yz/middleware_development_learning
对linux c/c++ nginx redis memcache twemproxy mongodb 中间件 存储引擎 分布式 高并发 高性能服务端等技术敢兴趣的同窗能够加群:
QQ交流群1(针对在校生)-(群号:568892619 )
QQ交流群2(针对已工做)-(581089275)