来源:http://blog.csdn.net/ghj1976/article/details/27996095javascript
做者:蝈蝈俊html
咱们先看两个用Go作消息推送的案例实际处理能力。java
16台机器,标配:24个硬件线程。64GB内存
Linux Kernel 2.6.32 x86_64
单机80万并发链接,load 0.2~0.4,CPU 总使用率 7%~10%,内存占用20GB (res)
眼下接入的产品约1280万在线用户
2分钟一次GC。停顿2秒 (1.0.3 的 GC 不给力,直接升级到 tip,再次吃螃蟹)
15亿个心跳包/天,占大多数。node
(团队人数:4)
单机并发tcp链接数峰值118w
内存占用23G(Res)
Load 0.7左右
心跳包 4k/s
gc时间2-3.x spython
为何可以支撑这么高并发的请求呢?咱们先从C10K问题提及:2001年左右的时候,有一个叫Dan Kegel的人在网上提出:现在的硬件应该可以让一台机器支持10000个并发的client。linux
而后他讨论了用不一样的方式实现大规模并发服务的技术。golang
http://www.kegel.com/c10k.html (英文版)算法
http://www.oschina.net/translate/c10k (中文翻译版)
http://www.cnblogs.com/fll/archive/2008/05/17/1201540.htmlapache
固然, 现在C10K 已经不是问题了, 不论什么一个普通的程序猿, 都能利用手边的语言和库, 轻松地写出 C10K 的server. 这既得益于软件的进步, 也得益于硬件性能的提升。现在应该扩展讨论的是应该是C10M问题了。编程
參考资料:
千万级并发实现的秘密:内核不是解决方式,而是问题所在。
http://www.csdn.net/article/2013-05-16/2815317-The-Secret-to-10M-Concurrent-Connections
不论线程仍是进程,都不可能一个链接建立一个。对应的成本太大。多进程和多线程都有资源耗费比較大的问题。因此在高并发量的server端使用并很少。
解决方式是一个线程或者进程处理多个链接,更详细的现在比較主流的是:Coroutine模型 和 非堵塞/异步IO(callback),在分析这两个以前,咱们先看看多进程和多线程的状况。
这样的模型在linux如下的服务程序普遍採用。比方大名鼎鼎的apache。
下图说明了Apache的生命周期(prefork模式)。主进程负责监听和管理链接。而详细的业务处理都会交给子进程来处理。
这样的架构的最大的优势是隔离性,子进程万一crash并不会影响到父进程。缺点就是对系统的负担太重,想像一下假设有上万的链接。会需要多少进程来处理。
因此这样的模型比較合适那种不需要太多并发量的server程序。
另外,进程间的通信效率也是一个瓶颈之中的一个。大部分会採用share memory等技术来减低通信开销。
apache的处理能力。如下有几篇文章:
2008年时的数据:http://www.blogjava.net/daniel-tu/archive/2008/12/29/248883.html
http://wenku.baidu.com/view/c527582a453610661ed9f40f.html
Apache的问题在于server的性能会随着链接数的增多而变差
关键点:性能和可扩展性并不是一回事。当人们谈论规模时,他们每每是在谈论性能,但是规模和性能是不一样的,比方Apache。
持续几秒的短时间链接,比方快速事务。假设每秒处理1000个事务,仅仅有约1000个并发链接到server。
事务延长到10秒,要维持每秒1000个事务,必须打开1万个并发链接。这样的状况下:虽然你不顾DoS攻击。Apache也会性能陡降;同一时候大量的下载操做也会使Apache崩溃。
假设每秒处理的链接从5千添加到1万,你会怎么作?比方说。你升级硬件而且提升处理器速度到原来的2倍。
发生了什么?你获得两倍的性能。但你没有获得两倍的处理规模。每秒处理的链接可能仅仅达到了6000。你继续提快速度,状况也没有改善。
甚至16倍的性能时,仍然不能处理1万个并发链接。因此说性能和可扩展性是不同的。
问题在于Apache会建立一个CGI进程,而后关闭,这个步骤并无扩展。
为何呢?内核使用的O(N^2)算法使server没法处理1万个并发链接。
内核中的两个基本问题:
链接数=线程数/进程数。当一个数据包进来。内核会遍历其所有进程以决定由哪一个进程来处理这个数据包。
链接数=选择数/轮询次数(单线程)。
相同的可扩展性问题,每个包都要走一遭列表上所有的socket。
解决方法:改进内核使其在常数时间内查找。
使线程切换时间与线程数量无关。
使用一个新的可扩展epoll()/IOCompletionPort常数时间去作socket查询。
參考:http://www.csdn.net/article/2013-05-16/2815317-The-Secret-to-10M-Concurrent-Connections
这样的模型在windows如下比較常见。它使用一个线程来处理一个client。他的优势是编程简单,最重要的是你会有一个清晰连续顺序的work flow。
简单意味着不easy出错。
这样的模型的问题就是太多的线程会减低软件的运行效率。
普通的线程,需要消耗1M的堆栈
http://www.cnblogs.com/PurpleTide/archive/2010/11/12/1875763.html
多进程和多线程的优缺点...
http://blog.163.com/ymguan@yeah/blog/static/140072872201147832740/
咱们知道。操做系统的最小调度单元是“线程”,要运行不论什么一段代码,都必须落实到“线程”上。惋惜线程过重,资源占用过高,频繁建立销毁会带来比較严重的性能问题,因而又诞生出线程池之类的常见使用模式。也是类似的缘由。“堵塞”一个线程每每不是一个好主意。因为线程虽然暂停了。但是它所占用的资源还在。线程的暂停和继续对于调度器都会带来压力,而且线程越多,调度时的开销便越大。这当中的平衡很是难把握。
针对这个问题,有两类架构解决它:基于callback和coroutine的架构。
这样的架构的特色是使用非堵塞的IO,这样server就可以持续运转,而不需要等待,可以使用很是少的线程,即便仅仅有一个也可以。需要按期的任务可以採取定时器来触发。把这样的架构发挥到极致的就是node.js,一个用javascript来写server端程序的框架。
在node.js中,所有的io都是non-block的,可以设置回调。
举个样例来讲明一下。
传统的写法:
var file = open(‘my.txt’); var data = file.read(); //block sleep(1); print(data); //block
node.js的写法:
fs.open(‘my.txt’,function(err,data){ setTimeout(1000,function(){ console.log(data); } }); //non-block
这样的架构的优势是performance会比較好,缺点是编程复杂。把曾经连续的流程切成了很是多片断。
另外也不能充分发挥多核的能力。
coroutine本质上是一种轻量级的thread,它的开销会比使用thread少很是多。多个coroutine可以依照次序在一个thread里面运行。一个coroutine假设处于block状态,可以交出运行权。让其它的coroutine继续运行。
非堵塞I/O模型协程(Coroutines)使得开发人员可以採用堵塞式的开发风格,却可以实现非堵塞I/O的效果隐式事件调度,
简单来讲:协程十分轻量,可以在一个进程中运行有数以十万计的协程。依然保持高性能。
进程、线程、协程的关系和差异:
堆和栈的差异请參看:http://www.cnblogs.com/ghj1976/p/3623037.html
协程和线程的差异是:协程避免了无心义的调度。由此可以提升性能,但也所以,程序猿必须本身承担调度的责任。
运行协程仅仅需要极少的栈内存(大概是4~5KB),默认状况下。线程栈的大小为1MB。
goroutine就是一段代码。一个函数入口。以及在堆上为其分配的一个堆栈。因此它很是便宜,咱们可以很是轻松的建立上万个goroutine。但它们并不是被操做系统所调度运行。
Google go语言对coroutine使用了语言级别的支持。使用keywordgo来启动一个coroutine(从这个keyword可以看出Go语言对coroutine的重视),结合chan(类似于message queue的概念)来实现coroutine的通信,实现了Go的理念 ”Do not communicate by sharing memory; instead, share memory by communicating.”。
http://my.oschina.net/Obahua/blog/144549
goroutine 的一个主要特性就是它们的消耗;建立它们的初始内存成本很是低廉(与需要 1 至 8MB 内存的传统 POSIX 线程造成鲜明对照)以及依据需要动态增加和缩减占用的资源。这使得 goroutine 会从 4096 字节的初始栈内存占用開始按需增加或缩减内存占用,而无需操心资源的耗尽。
为了实现这个目标。连接器(5l、6l 和 8l)会在每个函数前插入一个序文。这个序文会在函数被调用以前检查推断当前的资源是否知足调用该函数的需求(备注 1)。假设不知足,则调用 runtime.morestack 来分配新的栈页面(备注 2),从函数的调用者那里拷贝函数的參数。而后将控制权返回给调用者。此时,已经可以安全地调用该函数了。当函数运行完成,事情并无就此结束。函数的返回參数又被拷贝至调用者的栈结构中,而后释放没用的栈空间。
经过这个过程,有效地实现了栈内存的无限使用。假设你并不是不断地在两个栈之间往返,通俗地讲叫栈切割,则代价是十分低廉的。
简单来讲:Go语言经过系统的线程来多路派遣这些函数的运行,使得每个用gokeyword运行的函数可以运行成为一个单位协程。当一个协程堵塞的时候。调度器就会本身主动把其它协程安排到另外的线程中去运行,从而实现了程序无等待并行化运行。而且调度的开销很是小,一颗CPU调度的规模不下于每秒百万次。这使得咱们可以建立大量的goroutine,从而可以很是轻松地编写高并发程序,达到咱们想要的目的。
从性能角度来讲,callback的典型node.js和golang的性能測试结果,二者几乎相同,參考如下測试数据:
http://www.cnblogs.com/QLeelulu/archive/2012/08/12/2635261.html
只是从代码可读性角度来讲。callback确实有点不太好。
參考资料:
风格之争:Coroutine模型 vs 非堵塞/异步IO(callback)
http://blog.csdn.net/kjfcpua/article/details/15809703
Goroutine(协程)为什么能处理大并发? http://www.cnblogs.com/ghj1976/p/3642513.html
python Eventlet http://www.360doc.com/content/14/0522/00/8504707_379786818.shtml
为何我以为goroutine和channel是把别的平台上类库的功能内置在语言里 http://blog.zhaojie.me/2013/04/why-channel-and-goroutine-in-golang-are-buildin-libraries-for-other-platforms.html
Go-简洁的并发 http://www.yankay.com/go-clear-concurreny/
GOROUTINE性能測试 http://www.kankanews.com/ICkengine/archives/115285.shtml
Golang特性介绍 http://mryufeng.iteye.com/blog/576968/
并发编程 http://book.2cto.com/201301/14436.html