9.3.2 任务模型
任务模型包括网络框架、任务队列、工作线程,UpdateServer最初的任务模型基于淘宝网实现的Tbnet框架(已开源,见http://code.taobao.org/p/tb-common-utils/src/trunk/tbnet/)。Tbnet封装得很好,使用比较方便,每秒收包个数最多可以达到接近10万,不过仍然无法完全发挥UpdateServer收发小数据包以及内存服务的特点。OceanBase后来采用优化过的任务模型Libeasy,小数据包处理能力得到进一步提升。
1.Tbnet
如图9-5所示,Tbnet队列模型本质上是一个生产者—消费者队列模型,有两个线程:网络读写线程以及超时检查线程,其中,网络读写线程执行事件循环,当服务器端有可读事件时,调用回调函数读取请求数据包,生成请求任务,并加入到任务队列中。工作线程从任务队列中获取任务,处理完成后触发可写事件,网络读写线程会将处理结果发送给客户端。超时检查线程用于将超时的请求移除。
图 9-5 Tbnet队列模型
Tbnet模型的问题在于多个工作线程从任务队列获取任务需要加锁互斥,这个过程将产生大量的上下文切换(context switch),测试发现,当UpdateServer每秒处理包的数量超过8万个时,UpdateServer每秒的上下文切换次数接近30万次,在测试环境中已经达到极限(测试环境配置:Linux内核2.6.18,CPU为2*Intel Nehalem E5520,共8核16线程)。
2.Libeasy
为了解决收发小数据包带来的上下文切换问题,OceanBase目前采用Libeasy任务模型。Libeasy采用多个线程收发包,增强了网络收发能力,每个线程收到网络包后立即处理,减少了上下文切换,如图9-6所示。
图 9-6 Libeasy任务模型
UpdateServer有多个网络读写线程,每个线程通过Linux epool监听一个套接字集合上的网络读写事件,每个套接字只能同时分配给一个线程。当网络读写线程收到网络包后,立即调用任务处理函数,如果任务处理时间很短,可以很快完成并回复客户端,不需要加锁,避免了上下文切换。UpdateServer中大部分任务为短任务,比如随机读取内存表,另外还有少量任务需要等待共享资源上的锁,可以将这些任务加入到长任务队列中,交给专门的长任务处理线程处理。
由于每个网络读写线程处理一部分预先分配的套接字,这就可能出现某些套接字上请求特别多而导致负载不均衡的情况。例如,有两个网络读写线程thread1和thread2,其中thread1处理套接字fd1、fd2,thread2处理套接字fd3、fd4,fd1和fd2上每秒1000次请求,fd3和fd4上每秒10次请求,两个线程之间的负载很不均衡。为了处理这种情况,Libeasy内部会自动在网络读写线程之间执行负载均衡操作,将套接字从负载较高的线程迁移到负载较低的线程。
