流水不争先,争的是滔滔不绝

开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]

即时通讯软件开发 云聊IM 1279℃

前言

微信于2013年开源的ibco库,是微信后台大规模使用的c/c++协程库,2013年至今稳定运行在微信后台的数万台机器上。libco在2013年的时候作为腾讯六大开源项目首次开源,ibco支持后台敏捷的同步风格编程模式,同时提供系统的高并发能力。

libco主要特性:

无需侵入业务逻辑,把多进程、多线程服务改造成协程服务,并发能力得到百倍提升;

支持CGI框架,轻松构建web服务(New);

支持gethostbyname、mysqlclient、ssl等常用第三库(New);

可选的共享栈模式,单机轻松接入千万连接(New)。

libco完善简洁的协程编程接口:

类pthread接口设计,通过co_create、co_resume等简单清晰接口即可完成协程的创建与恢复;

类__thread的协程私有变量、协程间通信的协程信号量co_signal (New);

非语言级别的lambda实现,结合协程原地编写并执行后台异步任务 (New);

基于epoll/kqueue实现的小而轻的网络框架,基于时间轮盘实现的高性能定时器。

libco产生的背景

早期微信后台因为业务需求复杂多变、产品要求快速迭代等需求,大部分模块都采用了半同步半异步模型。接入层为异步模型,业务逻辑层则是同步的多进程或多线程模型,业务逻辑的并发能力只有几十到几百。随着微信业务的增长,系统规模变得越来越庞大,每个模块很容易受到后端服务/网络抖动的影响。

异步化改造的选择

为了提升微信后台的并发能力,一般的做法是把现网的所有服务改成异步模型。这种做法工程量巨大,从框架到业务逻辑代码均需要做一次彻底的改造,耗时耗力而且风险巨大。于是我们开始考虑使用协程。

但使用协程会面临以下挑战:

业界协程在c/c++环境下没有大规模应用的经验;

如何控制协程调度;

如何处理同步风格的API调用,如Socket、mysqlclient等;

如何处理已有全局变量、线程私有变量的使用。

最终我们通过libco解决了上述的所有问题,实现了对业务逻辑非侵入的异步化改造。我们使用libco对微信后台上百个模块进行了协程异步化改造,改造过程中业务逻辑代码基本无修改。至今,微信后台绝大部分服务都已是多进程或多线程协程模型,并发能力相比之前有了质的提升,而libco也成为了微信后台框架的基石。

libco框架技术架构图

libco在框架分为三层,分别是接口层、系统函数Hook层以及事件驱动层。

libco协程的共享协程栈模式使得单机很容易接入千万连接,只需创建足够多的协程即可。我们通过libco共享栈模式创建1千万的协程(E5-2670 v3 @ 2.30GHz * 2, 128G内存),每10万个协程共享的使用128k内存,整个稳定echo服务的时候总内存消耗大概为66G。

libco协程级私有变量

多进程程序改造为多线程程序时候,我们可以用__thread来对全局变量进行快速修改,而在协程环境下,我们创造了协程变量ROUTINE_VAR,极大简化了协程的改造工作量。

因为协程实质上是线程内串行执行的,所以当我们定义了一个线程私有变量的时候,可能会有重入的问题。比如我们定义了一个__thread的线程私有变量,原本是希望每一个执行逻辑独享这个变量的。但当我们的执行环境迁移到协程了之后,同一个线程私有变量,可能会有多个协程会操作它,这就导致了变量冲入的问题。为此,我们在做libco异步化改造的时候,把大部分的线程私有变量改成了协程级私有变量。协程私有变量具有这样的特性:当代码运行在多线程非协程环境下时,该变量是线程私有的;当代码运行在协程环境的时候,此变量是协程私有的。底层的协程私有变量会自动完成运行环境的判断并正确返回所需的值。

协程私有变量对于现有环境同步到异步化改造起了举足轻重的作用,同时我们定义了一个非常简单方便的方法定义协程私有变量,简单到只需一行声明代码即可。

libco的gethostbyname Hook方法介绍

对于现网服务,有可能需要通过系统的gethostbyname API接口去查询DNS获取真实地址。我们在协程化改造的时候,发现我们hook的socket族函数对gethostbyname不适用,当一个协程调用了gethostbyname时会同步等待结果,这就导致了同线程内的其它协程被延时执行。我们对glibc的gethostbyname源码进行了研究,发现hook不生效主要是由于glibc内部是定义了__poll方法来等待事件,而不是通用的poll方法;同时glibc还定义了一个线程私有变量,不同协程的切换可能会重入导致数据不准确。最终gethostbyname协程异步化是通过Hook __poll方法以及定义协程私有变量解决的。

gethostbyname是glibc提供的同步查询dns接口,业界还有很多优秀的gethostbyname的异步化解决方案,但是这些实现都需要引入一个第三方库并且要求底层提供异步回调通知机制。libco通过hook方法,在不修改glibc源码的前提下实现了的gethostbyname的异步化。
libco定义了协程信号量的概念

在多线程环境下,我们会有线程间同步的需求,比如一个线程的执行需要等待另一个线程的信号,对于这种需求,我们通常是使用pthread_signal 来解决的。在libco中,我们定义了协程信号量co_signal用于处理协程间的并发需求,一个协程可以通过co_cond_signal与co_cond_broadcast来决定通知一个等待的协程或者唤醒所有等待协程。

libco技术小结

libco是一个高效的c/c++协程库,提供了完善的协程编程接口、常用的Socket族函数Hook等,使得业务可用同步编程模型快速迭代开发。随着几年来的稳定运行,libco作为微信后台框架的基石发挥了举足轻重的作用。

ggithub源码:https://github.com/Tencent/libco

版权声明:部分文章、图片等内容为用户发布或互联网整理而来,仅供学习参考。如有侵犯您的版权,请联系我们,将立刻删除。
点击这里给我发消息