题图:by Dimitris Vetsikas from dimitrisvetsikas1969
昨天的文章 中提到了 C10K 问题,结果好些程序员跑过来问,啥是 C10K,我写了这么多年程序,我怎么不知道呢?我说,那你听说过前腿儿猪肉吗?
今天简单说说 C10K 的问题。关于这个问题,Ruby 的作者松本行弘在《代码的未来》- 云计算时代的编程 一章中有详细的阐述,有兴趣的同事可以直接去读那本书,内容丰富,比我写的好。我这里写个简化版。
在做技术规划和架构设计的时候,我常常告诫技术人员,不要做过度设计,如果咱们只有1万用户,先别去操百万用户在线的心。淘宝那么大,也是从 Apache、PHP、MySql 发展起来的,没人能预见到淘宝会发展到这样一个规模,一旦发展起来,业务的爆发性增长会驱动技术的迅速发展,在业务规模还不及格的时候,不用为技术的未来担心。
这个思路在业务领域不会有太大的问题,因为需求的变化实在是太快了,需要时时去应对。但在底层技术的发展上,我们就有可能遭到「短视」的报复,比如:这个数据长度不会超过16位吧,这个程序不可能使用到2000年吧。于是就有了千年虫的问题,也有了 C10K 的问题。
C10K 就是 Client 10000 问题,即「在同时连接到服务器的客户端数量超过 10000 个的环境中,即便硬件性能足够, 依然无法正常提供服务」,简而言之,就是单机1万个并发连接问题。这个概念最早由 Dan Kegel 提出并发布于其个人站点( http://www.kegel.com/c10k.html )。
为什么会这样呢?因为计算机的上古时代,比如没有网络的 PC 时代,不会有程序员高瞻远瞩的预测到互联网时代的来临,也不会想到一台服务器会创建那么多的进程,即使在互联网初期,一台服务器能有100个在线用户已经是不得了的事情了。甚至,他们在设计 Unix 的 PID 的时候,采用了有符号的16位整数,这就导致一台计算机上能够创建出来的进程无法超过32767个。而计算机自己也得运行一些后台进程,这样应用软件能够创建的进程数就更少了。
当然,这个问题随着技术的发展很快就解决了,现在大部分的个人电脑操作系统可以创建64位的进程,由于数据类型所带来的进程数上限消失了,但是我们依然不能无限制的创建进程,因为随着并发连接数的上升会占用系统大量的内存,同样会造成系统的不可用。
操作系统里内存管理的主要作用是,进程请求内存的时候为其分配可用内存,进程释放后回收内存,并监控内存的使用状况。为了提高内存的使用率,现代操作系统需要程序能够共享内存,并且内存的限制对开发者透明,有些程序占用了内存空间,但不一定是一直使用的,这样可以把这部分内存数据序列化到磁盘上,需要的时候再加载到内存里,这样内存资源永远会给最需要的程序使用。于是程序员们发明了虚拟内存(Virtual Memory)。
虚拟内存技术支持程序访问比物理内存大得多的内存空间,也使得多个程序共享内存更加高效。物理内存由 RAM 芯片提供,虚拟内存则依靠透明的使用磁盘空间,使程序运行起来好像有了更大的内存空间。
但是问题依然存在,进程和线程的创建都需要消耗一定的内存,每创建一个栈空间,都会产生内存开销,当内存使用超过物理内存的时候,一部分数据就会持久化到磁盘上,随之而来的就是性能的大幅度下降。
这就像银行挤兑,人们把现金存入银行,收取一定的利息,平时只有少数人去银行取现,银行会拿人们存的钱去做更有价值的投资。但是,如果大部分人都去银行取现,银行是没有那么多现金的。取不到钱的用户,被门挡在外面的用户,一定会去拉横幅喊口号「最喜欢双截棍柔中带刚,不喜欢银行就上少林武当」云云,于是银行就处于不可用状态了。现在的 P2P 理财也是一个道理,投资者都去变现,无论是多么良性的资产,一样玩完。
为什么现在会有这么大的连接需求呢?因为业务驱动和技术发展嘛。除了普通的网页浏览和表单提交,即时通信和实时互动交流越来越成为主流需求,keep-alive 技术也能让浏览器产生长连接,实时在线的客户端越来越多,如果不能解决 C10K 问题,将导致服务商需要购买大量的服务器,而每一台服务器都不能做到物尽其用,即使你配置了更好的 CPU 和更大的内存。
当然,现在我们早已经突破了 C10K 这个瓶颈,具体的思路就是通过单个进程或线程服务于多个客户端请求,通过异步编程和事件触发机制替换轮训,IO 采用非阻塞的方式,减少不必要的性能损耗,等等。
底层的相关技术包括 epoll、kqueue、libevent 等,应用层面的解决方案包括 OpenResty、Golang、Node.js 等,比如 OpenResty 的介绍中是这么说的:
OpenResty 通过汇聚各种设计精良的 Nginx 模块,从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 C10K 乃至 C1000K 以上单机并发连接的高性能 Web 应用系统。
据说现在都去搞 C10M 了,你们怕不怕?
实际操作中,每个解决方案都不是那么容易实现的,很多技术领域油光水滑的东西,放到线上,往往会出现各种各样的问题和毛病。松本行弘先生介绍了一个「最弱连接」的概念:
如果往两端用力拉一条由很多环 (连接)组成的锁链,其中最脆弱的一个连接会先断掉。因此,锁链整体的强度取决于其中最脆弱的一环。
C10K 问题的情况也很相似。一台服务器同时应付超过一万个(或者更多)并发连接的情况,哪怕只有一个要素没有考虑到超过一万个客户端的情况,这个要素就会成为「最弱连接」,从而导致问题的发生。
每个做架构设计和技术实现的程序员,都应当考虑这个最弱连接问题。
你是最弱的一环吗?