【51CTO.com原创稿件】在互联网时代,高并发与高可用一样,已经变成系统的标配了,如果系统每秒查询率(QPS)没有上万,都不好意思跟人打招呼(虽然实际每天调用量不超过100)。尤其在双十一期间,电商们凭借着藐视全球的流量,热心地分享自己的技术架构,几乎千篇一律地用缓存+哈希(HASH),仿佛这就是高并发的核心技术了。当然,如果你信了,那就离坑不远了。
所谓知己知彼百战不殆,先来看看我们经常看到的高并发技术是什么。
资源静态化活动秒杀页面是标准的高并发场景,活动期间单个页面流量巨大无比,每秒QPS达到几十万上百万。系统核心解决方案就是静态化,靠机器和带宽支撑。如果没有部署CDN,所有流量都落到同一个IP,解决办法就是用Nginx的文件静态化,单机的承受能力主要取决于带宽和单机的性能。如果流量再多,那就采用LVS(或者F5)+集群了。就中后台而言,Nginx可以搞定大部分应用,核心还是使用了缓存技术。
现在各种缓存的工具如memcache,redis之类的KV数据库作为缓存已经非常成熟,而且基本上都可以集群化部署,操作起来也很简单,简直成为高并发的代言词。
读写分离和分库分表读写分离也是大家经常看到的高并发的架构,因为一般情况下读比写要多得多,所以数据库的主库写,从库们提供读操作,一下就把数据库的并发性能提高了。如果还不够,那么分库分表把,把数据分到各个数据库的各个机器上,进一步的减少单台机器的压力,从而达到高并发的目的。
如果是分库分表,有时候使用的就是哈希技术了,以某个字段哈希一下然后来分库分表,读写分离的读操作,基本也是通过哈希技术把读落到不同的机器上去减轻单机压力。但凡大数据处理,高并发系统,必言哈希,随机插入,时间复杂度O(1),随便查询,时间复杂度O(1),除了耗费点空间以外,几乎零缺点,在现在这个内存廉价的时代,哈希表变成了一个高并发系统的标配。
当电商促销活动时,几千万人同时访问网站还没有挂,让很多人觉得高并发应该就是这样子。这样的场景再扩展一点,就是凡是能提前提供数据的并发访问,就可以用缓存+哈希来搞定并发。而像12306网站这样,不能提前提供数据的,可以通过缓存+哈希来提高用户体验,然后通过异步方式来提供服务(被吐槽的验证码其实就是异步的排队方式)。
实际上是不是这样呢?显然没那么简单。让我们来看看一个高并发的系统真正需要考虑的元素。
搜索提示功能大家都非常熟悉,如果是google,baidu这种大型搜索系统,或者京东淘宝这种电商系统,搜索提示的调用量是搜索服务本身调用量的好几倍,因为你每输入一个键盘,就要调用一次搜索提示服务,这算得上是个标准的高并发系统吧?那么它是怎么实现的呢?
可能很多人脑子里立刻出现了缓存+哈希的系统,把搜索的搜索提示词存在redis集群中,每次来了请求直接redis集群中查找key,然后返回相应的value值就行了。虽然耗费点内存,但是空间换时间嘛,也能接受。然而事实上没有人会这么做。
这种搜索提示的功能一般用trie树来做,耗费的内存不多,查找速度为O(k),其中k为字符串的长度,虽然看上去没有哈希表的O(1)好,但是少了网络开销,节约了很多内存,并且实际查找时间还要不比缓存+哈希慢多少,一种合适当前场景的核心数据结构才是高并发系统的关键,缓存+哈希如果也看成一种数据结构,但这种数据结构并不适用于所有的高并发场景,所以高并发系统的设计,关键在合理的数据结构的设计,而不在架构的套用。
有了上面的数据结构,并且设计出了系统了,拿到线上一跑,效果还行,但感觉没达到极限,这时候可千万不能就直接上外部工具(比如缓存)提升性能,需要做的是不断的代码性能的优化,简单的说,就是不断的重申你的代码,不断找出可以优化的性能点,然后进行优化,因为之前设计的时候就已经通过理论大概能算出来这个系统的并发量了,比如上面那个搜索提示,如果我们假定平均每个搜索词6个字符,检索一次大约需要查询6次,需要2-3毫秒,这样的话,如果8核的机器,多线程编程方式,一秒钟最多能接受3200次请求(1000ms/2.5ms*8),如果没达到这个量级,那么肯定是代码有问题。
这个阶段可能需要借助一些个工具了,Golang自带的go tool pprof就能很好的进行性能优化。
或者把各个模块的时间打印出来,压测一遍,看看哪个模块耗时,然后再去仔细检查那个模块的代码,进行算法和数据结构的优化。
这个过程是一个长期的过程,也是《重构:改善代码的既有设计》中提到的,一个优秀的系统需要不断的进行代码级别的优化和重构,所以高并发系统的实现,就是不断的优化代码的性能,不断逼近设计时的理论值。
如果以上两个都完成了,并发量也基本达到理论值了,但是还有提升的需求,这时候再来考虑外部的通用方法,比如加一个LRU缓存,把热词的查询时间变成O(1),进一步提高性能。在没把系统的性能压榨完全之前,不要使用外部的通用方法,因为使用了以后就没有太多进一步优化空间了。
此外还需要再考虑运维的技术,如常规的加负载均衡,部署成集群,通过运维和部署的方法提高服务的并发性。
其实代码才是高可用的关键,代码的健壮性决定了高可用。除此之外,还需要看重数据结构的设计和代码的调优能力。很多人对数据结构嗤之以鼻,觉得对于现有的开发来说,数据结构没那么重要,但对于后端开发来说,数据结构是很重要的技能。
找准合适的数据结构,不断的优化代码,这样来提升系统性能才是可控的,才有不断优化的空间,更好的高并发,如果一开始就上外部的缓存技术,很可能如果性能达不到要求,就没有优化空间了,因为要修改外部的系统还很困难。
【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】