Golang适合高并发场景的缘由分析

Golang适合高并发场景的缘由分析_编程语言_酷勤网

    典型的两个现实案例:

    咱们先看两个用Go作消息推送的案例实际处理能力。
    360消息推送的数据:

    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亿个心跳包/天,占大多数。
    京东云消息推送系统

    (团队人数:4)

    单机并发tcp链接数峰值118w

    内存占用23G(Res)

    Load 0.7左右

    心跳包 4k/s

    gc时间2-3.x s
    C10K问题

    为何能够支撑这么高并发的请求呢?咱们先从C10K问题提及:2001年左右的时候,有一个叫Dan Kegel的人在网上提出:如今的硬件应该可以让一台机器支持10000个并发的client。而后他讨论了用不一样的方式实现大规模并发服务的技术。

    http://www.kegel.com/c10k.html(英文版)

    http://www.oschina.net/translate/c10k(中文翻译版)

    http://www.cnblogs.com/fll/archive/2008/05/17/1201540.html

    固然, 如今C10K 已经不是问题了, 任何一个普通的程序员, 都能利用手边的语言和库, 轻松地写出 C10K 的服务器. 这既得益于软件的进步, 也得益于硬件性能的提升,如今应该扩展讨论的是应该是C10M问题了。

    参考资料:

    千万级并发实现的秘密:内核不是解决方案,而是问题所在!

    http://www.csdn.net/article/2013-05-16/2815317-The-Secret-to-10M-Concurrent-Connections
    Coroutine模型 和 非阻塞/异步IO(callback)

    不论线程仍是进程,都不可能一个链接建立一个,相应的成本太大,多进程和多线程都有资源耗费比较大的问题,因此在高并发量的服务器端使用并很少。解决方案是一个线程或者进程处理多个链接,更具体的如今比较主流的是:Coroutine模型 和 非阻塞/异步IO(callback),在分析这两个以前,咱们先看看多进程和多线程的状况。
    多进程

    这种模型在linux下面的服务程序普遍采用,好比大名鼎鼎的apache。

    下图说明了Apache的生命周期(prefork模式)。主进程负责监听和管理链接,而具体的业务处理都会交给子进程来处理。

    1234514831_ddvip_588

    这种架构的最大的好处是隔离性,子进程万一crash并不会影响到父进程。缺点就是对系统的负担太重,想像一下若是有上万的链接,会须要多少进程来处理。因此这种模型比较合适那种不须要太多并发量的服务器程序。另外,进程间的通信效率也是一个瓶颈之一,大部分会采用share memory等技术来减低通信开销。

    apache的处理能力,下面有几篇文章:

    2008年时的数据:http://www.blogjava.net/daniel-tu/archive/2008/12/29/248883.html

    http://wenku.baidu.com/view/c527582a453610661ed9f40f.html
    Apache的问题

    Apache的问题在于服务器的性能会随着链接数的增多而变差

    关键点:性能和可扩展性并非一回事。当人们谈论规模时,他们每每是在谈论性能,可是规模和性能是不一样的,好比Apache。

    持续几秒的短时间链接,好比快速事务,若是每秒处理1000个事务,只有约1000个并发链接到服务器。

    事务延长到10秒,要维持每秒1000个事务,必须打开1万个并发链接。这种状况下:尽管你不顾DoS攻击,Apache也会性能陡降;同时大量的下载操做也会使Apache崩溃。

    若是每秒处理的链接从5千增长到1万,你会怎么作?比方说,你升级硬件而且提升处理器速度到原来的2倍。发生了什么?你获得两倍的性能,但你没有获得两倍的处理规模。每秒处理的链接可能只达到了6000。你继续提升速度,状况也没有改善。甚至16倍的性能时,仍然不能处理1万个并发链接。因此说性能和可扩展性是不同的。

    问题在于Apache会建立一个CGI进程,而后关闭,这个步骤并无扩展。

    为何呢?内核使用的O(N^2)算法使服务器没法处理1万个并发链接。

    内核中的两个基本问题:

    链接数=线程数/进程数。当一个数据包进来,内核会遍历其全部进程以决定由哪一个进程来处理这个数据包。

    链接数=选择数/轮询次数(单线程)。一样的可扩展性问题,每一个包都要走一遭列表上全部的socket。

    解决方法:改进内核使其在常数时间内查找。

    使线程切换时间与线程数量无关。

    使用一个新的可扩展epoll()/IOCompletionPort常数时间去作socket查询。

    参考:http://www.csdn.net/article/2013-05-16/2815317-The-Secret-to-10M-Concurrent-Connections
    多线程

    这种模型在windows下面比较常见。它使用一个线程来处理一个client。他的好处是编程简单,最重要的是你会有一个清晰连续顺序的work flow。简单意味着不容易出错。

    这种模型的问题就是太多的线程会减低软件的运行效率。
    线程和进程的成本

    普通的线程,须要消耗1M的堆栈

    http://www.cnblogs.com/PurpleTide/archive/2010/11/12/1875763.html

    多进程和多线程的优缺点...

    http://blog.163.com/ymguan@yeah/blog/static/140072872201147832740/

    咱们知道,操做系统的最小调度单元是“线程”,要执行任何一段代码,都必须落实到“线程”上。惋惜线程过重,资源占用过高,频繁建立销毁会带来比较严重的性能问题,因而又诞生出线程池之类的常见使用模式。也是相似的缘由,“阻塞”一个线程每每不是一个好主意,由于线程虽然暂停了,可是它所占用的资源还在。线程的暂停和继续对于调度器都会带来压力,并且线程越多,调度时的开销便越大,这其中的平衡很难把握。

    针对这个问题,有两类架构解决它:基于callback和coroutine的架构。
    Callback- 非阻塞/异步IO

    这种架构的特色是使用非阻塞的IO,这样服务器就能够持续运转,而不须要等待,可使用不多的线程,即便只有一个也能够。须要按期的任务能够采起定时器来触发。把这种架构发挥到极致的就是node.js,一个用javascript来写服务器端程序的框架。在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-协程

    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使用了语言级别的支持,使用关键字go来启动一个coroutine(从这个关键字能够看出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语言经过系统的线程来多路派遣这些函数的执行,使得每一个用go关键字执行的函数能够运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其余协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。并且调度的开销很是小,一颗CPU调度的规模不下于每秒百万次,这使得咱们可以建立大量的goroutine,从而能够很轻松地编写高并发程序,达到咱们想要的目的。
    Coroutine模型 和 非阻塞/异步IO(callback)性能对比

    从性能角度来讲,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.htmljavascript

相关文章
相关标签/搜索