libco是微信后盾大规模应用的c/c++协程库,2013年至今稳固运行在微信后盾的数万台机器上。libco在2013年的时候作为腾讯六大开源我的项目首次开源,咱们最近做了一次较大的更新,同步更新在https://github.com/tencent/libco(点击浏览原文间接拜访)上。libco反对后盾麻利的同步格调编程模式,同时提供零碎的高并发能力。
libco反对的个性
❏ 反对CGI框架,轻松构建web服务(New);
❏ 反对gethostbyname、mysqlclient、ssl等罕用第三库(New);
❏ 可选的共享栈模式,单机轻松接入千万连贯(New);
❏ 欠缺简洁的协程编程接口:
▪ 类pthread接口设计,通过co_create、co_resume等简略清晰接口即可实现协程的创立与复原;
▪ 类__thread的协程公有变量、协程间通信的协程信号量co_signal (New);
▪ 非语言级别的lambda实现,联合协程原地编写并执行后盾异步工作 (New);
▪ 基于epoll/kqueue实现的小而轻的网络框架,基于工夫轮盘实现的高性能定时器;
libco产生的背景
晚期微信后盾因为业务需要复杂多变、产品要求疾速迭代等需要,大部分模块都采纳了半同步半异步模型。接入层为异步模型,业务逻辑层则是同步的多过程或多线程模型,业务逻辑的并发能力只有几十到几百。随着微信业务的增长,零碎规模变得越来越宏大,每个模块很容易受到后端服务/网络抖动的影响。
异步化革新的抉择
为了晋升微信后盾的并发能力,个别的做法是把现网的所有服务改成异步模型。这种做法工程量微小,从框架到业务逻辑代码均须要做一次彻底的革新,耗时耗力而且危险微小。于是咱们开始思考应用协程。
❏ 但应用协程会面临以下挑战:
\1. 业界协程在c/c++环境下没有大规模利用的教训;
\2. 如何管制协程调度;
\3. 如何解决同步格调的API调用,如Socket、mysqlclient等;
\4. 如何解决已有全局变量、线程公有变量的应用;
最终咱们通过libco解决了上述的所有问题,实现了对业务逻辑非侵入的异步化革新。咱们应用libco对微信后台上百个模块进行了协程异步化革新,革新过程中业务逻辑代码根本无批改。至今,微信后盾绝大部分服务都已是多过程或多线程协程模型,并发能力相比之前有了质的晋升,而libco也成为了微信后盾框架的基石。
libco框架
同步格调API的解决
对于同步格调的API,次要是同步的网络调用,libco的首要任务是打消这些期待对资源的占用,进步零碎的并发性能。一个惯例的网络后盾服务,咱们可能会经验connect、write、read等步骤,实现一次残缺的网络交互。当同步的调用这些API的时候,整个线程会因为期待网络交互而挂起。
尽管同步编程格调的并发性能并不好,然而它具备代码逻辑清晰、易于编写的长处,并可反对业务疾速迭代麻利开发。为了持续放弃同步编程的长处,并且不需批改线上已有的业务逻辑代码,libco翻新地接管了网络调用接口(Hook),把协程的让出与复原作为异步网络IO中的一次事件注册与回调。当业务解决遇到同步网络申请的时候,libco层会把本次网络申请注册为异步事件,本协程让出CPU占用,CPU交给其它协程执行。libco会在网络事件产生或者超时的时候,主动的复原协程执行。
大部分同步格调的API咱们都通过Hook的办法来接管了,libco会在失当的机会调度协程复原执行。
千万级协程反对
libco默认是每一个协程独享一个运行栈,在协程创立的时候,从堆内存调配一个固定大小的内存作为该协程的运行栈。如果咱们用一个协程解决前端的一个接入连贯,那对于一个海量接入服务来说,咱们的服务的并发下限就很容易受限于内存。为此,libco也提供了stackless的协程共享栈模式,能够设置若干个协程共享同一个运行栈。同一个共享栈下的协程间切换的时候,须要把以后的运行栈内容拷贝到协程的公有内存中。为了缩小这种内存拷贝次数,共享栈的内存拷贝只产生在不同协程间的切换。当共享栈的占用者始终没有扭转的时候,则不须要拷贝运行栈。
libco协程的共享协程栈模式使得单机很容易接入千万连贯,只需创立足够多的协程即可。咱们通过libco共享栈模式创立1千万的协程(E5-2670 v3 @ 2.30GHz * 2, 128G内存),每10万个协程共享的应用128k内存,整个稳固echo服务的时候总内存耗费大略为66G。
协程公有变量
多过程程序革新为多线程程序时候,咱们能够用__thread来对全局变量进行疾速批改,而在协程环境下,咱们发明了协程变量ROUTINE_VAR,极大简化了协程的革新工作量。
因为协程本质上是线程内串行执行的,所以当咱们定义了一个线程公有变量的时候,可能会有重入的问题。比方咱们定义了一个__thread的线程公有变量,本来是心愿每一个执行逻辑独享这个变量的。但当咱们的执行环境迁徙到协程了之后,同一个线程公有变量,可能会有多个协程会操作它,这就导致了变量冲入的问题。为此,咱们在做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的异步化。
协程信号量
在多线程环境下,咱们会有线程间同步的需要,比方一个线程的执行须要期待另一个线程的信号,对于这种需要,咱们通常是应用pthread_signal 来解决的。在libco中,咱们定义了协程信号量co_signal用于解决协程间的并发需要,一个协程能够通过co_cond_signal与co_cond_broadcast来决定告诉一个期待的协程或者唤醒所有期待协程。
总结
libco是一个高效的c/c++协程库,提供了欠缺的协程编程接口、罕用的Socket族函数Hook等,使得业务可用同步编程模型疾速迭代开发。随着几年来的稳固运行,libco作为微信后盾框架的基石施展了无足轻重的作用。
本文原创作者微信团队转自微信后盾团队如有侵权请分割咱们删除
OpenIMgithub开源地址:
https://github.com/OpenIMSDK/...
OpenIM官网 :https://www.rentsoft.cn
OpenIM官方论坛:https://forum.rentsoft.cn/
更多技术文章:
开源OpenIM:高性能、可伸缩、易扩大的即时通讯架构
https://forum.rentsoft.cn/thr...
【OpenIM原创】简略轻松入门 一文解说WebRTC实现1对1音视频通信原理
https://forum.rentsoft.cn/thr...
【OpenIM原创】开源OpenIM:轻量、高效、实时、牢靠、低成本的音讯模型
https://forum.rentsoft.cn/thr...