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