社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
早在十八年前的1999年,千兆网卡还是一个新玩意儿,想当年有吉比特带宽却只能支持10K客户端,还是个值得研究的问题,毕竟Nginx在2009年才出来,在这之前大家还在内核折腾过HTTP服务器,服务器领域还在讨论如何解决C10K问题,C10K中文翻译在这里。读这个文章,感觉进入了繁忙服务器工厂的车间,成千上万错综复杂的电缆交织在一起,甚至还有古老的惊群(thundering herd)问题,惊群像远古狼人一样就算是在21世纪还是偶然能听到它的传说。现在大家讨论的都是如何支持C10M,也就是千万级并发的问题。
并发,无疑是服务器领域永远无法逃避的话题,是服务器软件工程师的基本能力。Go的撒手锏之一无疑就是并发处理,如果要从Go众多优秀的特性中挑一个,那就是并发和工程化,如果只能选一个的话,那就是并发的支持。大规模软件,或者云计算,很大一部分都是服务器编程,服务器要处理的几个基本问题:并发、集群、容灾、兼容、运维,这些问题都可以因为Go的并发特性得到改善,按照《人月神话》的观点,并发无疑是服务器领域的固有复杂度(Essential Complexity)之一。Go之所以能迅速占领云计算的市场,Go的并发机制是至关重要的。
借用《人月神话》中关于固有复杂度(Essential Complexity)的概念,能比较清晰的说明并发问题。就算没有读过这本书,也肯定听过软件开发“没有银弹”,要保持软件的“概念完整性”,Brooks作为硬件和软件的双重专家和出色的教育家始终活跃在计算机舞台上,在计算机技术的诸多领域中都作出了巨大的贡献,在1964年(33岁)领导了IBM System/360和IBM OS/360的研发,于1993年(62岁)获得冯诺依曼奖,并于1999年(68岁)获得图灵奖,在2010年(79岁)获得虚拟现实(VR)的奖项IEEE Virtual Reality Career Award (2010)。
在软件领域,很少能有像《人月神话》一样具有深远影响力和畅销不衰的著作。Brooks博士为人们管理复杂项目提供了具有洞察力的见解,既有很多发人深省的观点,又有大量软件工程的实践。本书内容来自Brooks博士在IBM公司System/360家族和OS/360中的项目管理经验,该项目堪称软件开发项目管理的典范。该书英文原版一经面世,即引起业内人士的强烈反响,后又译为德、法、日、俄、中、韩等多种文字,全球销售数百万册。确立了其在行业内的经典地位。
Brooks是我最崇拜的人,有理论有实践,懂硬件懂软件,致力于大规模软件(当初还没有云计算)系统,足够(长达十年甚至二十年)的预见性,孜孜不倦奋斗不止,强烈推荐软件工程师读《人月神话》。
短暂的广告回来,继续讨论并发(Concurrency)的问题,要理解并发的问题就必须从了解并发问题本身,以及并发处理模型开始。2012年我在当时中国最大的CDN公司蓝汛设计和开发流媒体服务器时,学习了以高并发闻名的NGINX的并发处理机制EDSM(Event-Driven State Machine Architecture),自己也照着这套机制实现了一个流媒体服务器,和HTTP的Request-Response模型不同,流媒体的协议比如RTMP非常复杂中间状态非常多,特别是在做到集群Edge时和上游服务器的交互会导致系统的状态机翻倍,当时请教了公司的北美研发中心的架构师Michael,Michael推荐我用一个叫做ST(StateThreads)的技术解决这个问题,ST实际上使用setjmp和longjmp实现了用户态线程或者叫协程,协程和goroutine是类似的都是在用户空间的轻量级线程,当时我本没有懂为什么要用一个完全不懂的协程的东西,后来我花时间了解了ST后豁然开朗,原来服务器的并发处理有几种典型的并发模型,流媒体服务器中超级复杂的状态机,也广泛存在于各种服务器领域中,属于这个复杂协议服务器领域不可Remove的一种固有复杂度(Essential Complexity)。
我翻译了ST(StateThreads)总结的并发处理模型高性能、高并发、高扩展性和可读性的网络服务器架构:State Threads for Internet Applications,这篇文章也是理解Go并发处理的关键,本质上ST就是C语言的协程库(腾讯微信也开源过一个libco协程库),而goroutine是Go语言级别的实现,本质上他们解决的领域问题是一样的,当然goroutine会更广泛一些,ST只是一个网络库。我们一起看看并发的本质目标,一起看图说话吧,先从并发相关的性能和伸缩性问题说起:
1000
个客户端在观看500Kbps
码率的视频时,意味着每个客户端每秒需要500Kb的数据,那么服务器需要每秒吐出500*1000Kb=500Mb
的数据才能正常提供服务,如果服务器因为性能问题CPU跑满了都无法达到500Mbps的吞吐率,客户端必定就会开始卡顿。并发的模型包括几种,总结Existing Architectures如下表:
Arch | Load Scalability | System Scalability | Robust | Complexity | Example |
---|---|---|---|---|---|
Multi-Process | Poor | Good | Great | Simple | Apache1.x |
Multi-Threaded | Good | Poor | Poor | Complex | Tomcat, FMS/AMS |
Event-Driven State Machine |
Great | Great | Good | Very Complex |
Nginx, CRTMPD |
StateThreads | Great | Great | Good | Simple | SRS, Go |
我将Go也放在了ST这种模型中,虽然它是
多线程+协程
,和SRS不同是多进程+协程
(SRS本身是单进程+协程
可以扩展为多进程+协程
)。
从并发模型看Go的goroutine,Go有ST的优势,没有ST的劣势,这就是Go的并发模型厉害的地方了。当然Go的多线程是有一定开销的,并没有纯粹多进程单线程那么高的负载伸缩性,在活跃的连接过多时,可能会激活多个物理线程,导致性能降低。也就是Go的性能会比ST或EDSM要差,而这些性能用来交换了系统的维护性,个人认为很值得。除了goroutine,另外非常关键的就是chan。Go的并发实际上并非只有goroutine,而是goroutine+chan,chan用来在多个goroutine之间同步。实际上在这两个机制上,还有标准库中的context,这三板斧是Go的并发的撒手锏。
Share Memory By Communicating
。Go Concurrency Patterns: Timing out, moving on
和Go Concurrency Patterns: Context
。由于Go是多线程的,关于多线程或协程同步,除了chan也提供了Mutex,其实这两个都是可以用的,而且有时候比较适合用chan而不是用Mutex,有时候适合用Mutex不适合用chan,参考Mutex or Channel
。
Channel | Mutex |
---|---|
passing ownership of data,<br />distributing units of work,<br /> communicating async results | caches,<br />state |
特别提醒:不要惧怕使用Mutex,不要什么都用chan,千里马可以一日千里却不能抓老鼠,HelloKitty跑不了多快抓老鼠却比千里马强。
实际上goroutine的管理,在真正高可用的程序中是非常必要的,我们一般会需要支持几种gorotine的控制方式:
而goroutine的管理,最开始只有chan和sync,需要自己手动实现goroutine的生命周期管理,参考Go Concurrency Patterns: Timing out, moving on
和Go Concurrency Patterns: Context
,这些都是goroutine的并发范式。
直接使用原始的组件管理goroutine太繁琐了,后来在一些大型项目中出现了context这些库,并且Go1.7之后变成了标准库的一部分。具体参考GOLANG使用Context管理关联goroutine以及GOLANG使用Context实现传值、超时和取消。
Context也有问题:
Read(Context, []byte)
函数。或者提供两套接口,一种带Contex,一种不带Context。这个问题还蛮困扰人的,一般在应用程序中,推荐第一个参数是Context。备注:关于对Context的批评,可以参考Context should go away for Go 2,作者觉得在标准库中加context作为第一个参数不能理解,比如
Read(ctx context.Context
等。
由于简书限制了文章字数,只好分成不同章节:
欢迎加入SRS流媒体服务器钉钉群:
或者用微信扫二维码加公众号,然后在公众号中点加微信群:
推荐加钉钉群哦,有各种实时消息,有直播和回看,还有机器人等你撩~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!