坑系列 —— 缓存 + 哈希 = 高并发?

2,705 阅读14分钟

今天继续坑系列,高可用已经讲过了,当前互联网时代,怎么少的了高并发呢?高并发高可用一样, 已经变成各个系统的标配了,如果你的系统QPS没有个大几千上万,都不好意思跟人打招呼,虽然可能每天的调用量不超过100。

高并发这个词,我个人感觉是从电商领域开始往外流传的,特别是电商领域双11那种藐视全球的流量,再把技术架构出来分享一把,现在搞得全互联网都在说高并发,而且你注意回忆一下所有你看到的高并发系统,往往都逃不开一个核心概念,那就是缓存+哈希,一切都是以这个概念和基础的,仿佛这就是高并发的核心技术了。`

我们看到的高并发技术

围绕这个核心技术,通常我们看到的各种高并发的架构系统,在博客,论坛,现场分享出来的高并发系统,都跑不出以下几个方面的东西。

资源静态化

就是那种单个页面流量巨大无比,每秒的QPS几十万上百万的系统,确实并发高的系统,核心解决方案就是静态化,靠机器和带宽去抗,假如没有CDN的话,所有流量都落到同一个IP下面的话,基本上也就是用Nginx的文件静态化了,单机的承受能力主要取决于带宽和单机的性能,要再多的话,那就LVS(或者F5)+集群了,这种的典型场景就是搞活动时候的首页,活动页面了,还有就是引流搜索引擎的着陆页了,一般都是现成的图片和文字静态化,当然,这种还有很多前端的技巧和技术了,这一点我不是很了解,就不得瑟了,就中后台来说,大部分情况下直接Nginx搞定了,核心还是使用了缓存技术

读写分离和分库分表

读写分离是大家看到的第二个高并发的架构了,也很常规,因为一般情况下读比写要多得多,所以数据库的主库写,从库们提供读操作,一下就把数据库的并发性能提高了。

如果还不够,那么分库分表把,把数据分到各个数据库的各个机器上,进一步的减少单台机器的压力,从而达到高并发的目的。

如果是分库分表,有时候使用的就是哈希技术了,以某个字段哈希一下然后来分库分表,读写分离的读操作,基本也是通过哈希技术把读落到不同的机器上去减轻单机压力。

万能的缓存

说到高并发,不得不说缓存了,现在各种缓存的工具也很多也很成熟,memcache,redis之类的KV数据库作为缓存已经非常成熟了,而且基本上都可以集群化部署,操作起来也很简单,简直变成了一个高并发的代言词了,核心就是缓存技术了,而memcacheredis只是用来实现这个缓存技术的工具而已。

无敌的哈希

但凡大数据处理,高并发系统,必言哈希,随机插入,时间复杂度O(1),随便查询,时间复杂度O(1),除了耗费点空间以外,几乎没什么缺点了,在现在这个内存廉价的时代,哈希表变成了一个高并发系统的标配。

正面的例子

我们来看个例子,看看一些个大家眼中标准的高并发系统的设计,这些设计大家应该都看过,无非就是上面的几个要点,最重要的就是缓存+哈希,这两个东西的组合好像无所不能。

活动秒杀页面

活动秒杀页面,这是个标准的高并发吧,到了搞活动的那个时刻,单页面的访问量是天量数据了,但这种系统有个特点逻辑简单,只要带宽和性能够,就一定能提供稳定的服务 服务能迅速的返回数据即可,没有什么计算逻辑,这种高并发系统的设计基本上就是在怎么压榨机器的IO性能了,如果有CDN绝对使用CDN,能在本机读取的绝不走网络获取,能读取到内存中绝不放在硬盘上,把系统的磁盘IO和网络IO都尽可能的压榨,使用缓存+哈希技术,只要设计合理,99%的情况能搞定。

活动页面的冲击感实在太强,想象一下几千万人同时访问网站还没有挂,让很多人觉得高并发应该就是这样子,这估计也是高并发这次经常在电商技术中出现的原因吧,因为搞个活动就可以搞出一个高并发事件。

这样的场景再扩展一点,就是凡是能提前提供数据的并发访问,就可以用缓存+哈希来搞定并发。

12306

接下来,我们再看看这个星球并发量最疯狂的网站,瞬间的访问量碾压其他网站的12306,这种场景下的高并发也有个特点,那就是虽然量大,但其实无法给每个用户提供服务

类似的其实还有商品的抢购系统,商品和车票一共就1000张,你100万的人抢,你系统做得再好,也无法给100万人提供服务,之前12306刚刚上线的时候很多人喷,说如果让某某公司来做肯定能做好,但大家很多只看到了表面,让某很厉害的公司来做,最多也只能做到大家访问的时候不会挂掉,你买不到车票还是买不到,而且现在的12306体验也已经做得很好了,也不卡了,但是还是很多人骂,为什么?还不是因为买不到票。

对于这样的系统,设计关注的就不仅仅是提高性能了,因为性能瓶颈已经摆在那了,就1000张票,做得更多的是分流和限流了,除了缓存+哈希来保证用户体验以外,出现了奇葩验证码,各个站点分时间点放票。然后通过排队系统来以一种异步的方式提供最终的服务。

我们给这样的场景再扩展一下,凡是不能提前提供数据的,可以通过缓存+哈希来提高用户体验,然后通过异步方式来提供服务。

高并发系统如何设计

如果把上面两个场景的情况合并一下,仿佛缓存+哈希变成万能的了,很多人眼中的高并发就是上面的场景的组合,认为缓存+哈希就可以解决高并发的问题,其他的场景中,加上缓存提高读写速度,在加上哈希提供分流技术,再通过一个异步提供最终服务,高并发就这么搞定了,但实际上是不是这样呢?显然没那么简单,那如何来设计一个高并发的系统呢?

合理的数据结构

举个例子来说吧,搜索提示功能大家都知道吧,就是下面这个图的东西。

如果是google,baidu这种大型搜索系统,或者京东淘宝这种电商系统,搜索提示的调用量是搜索服务本身调用量的几倍,因为你每输入一个键盘,就要调用一次搜索提示服务,这算得上是个标准的高并发系统吧?那么它是怎么实现的呢?

可能很多人脑子里立刻出现了缓存+哈希的系统,把搜索的搜索提示词存在redis集群中,每次来了请求直接redis集群中查找key,然后返回相应的value值就行了,完美解决,虽然耗费点内存,但是空间换时间嘛,也能接受,这么做行不行?恩,我觉得是可以的,但有人这么做吗?没有。

了解的人应该知道,没有人会这么来实现,这种搜索提示的功能一般用trie树来做,耗费的内存不多,查找速度为O(k),其中k为字符串的长度,虽然看上去没有哈希表的O(1)好,但是少了网络开销,节约了很多内存,并且实际查找时间还要不比缓存+哈希慢多少,一种合适当前场景的核心数据结构才是高并发系统的关键,缓存+哈希如果也看成一种数据结构,但这种数据结构并不适用于所有的高并发场景,所以

高并发系统的设计,关键在合理的数据结构的设计,而不在架构的套用

不断的代码性能优化

有了上面的数据结构,并且设计出了系统了,拿到线上一跑,效果还行,但感觉没达到极限,这时候可千万不能就直接上外部工具(比如缓存)提升性能,需要做的是不断的代码性能的优化,简单的说,就是不断的review你的代码,不断的找出可以优化的性能点,然后进行优化,因为之前设计的时候就已经通过理论大概能算出来这个系统的并发量了,比如上面那个搜索提示,如果我们假定平均每个搜索词6个字符,检索一次大约需要查询6次,需要2-3毫秒,这样的话,如果8核的机器,多线程编程方式,一秒钟最多能接受3200次请求(1000ms/2.5ms*8),如果没达到这个量级,那么肯定是代码哪里有问题。

这个阶段可能需要借助一些个工具了,JAVA有JAVA的性能优化工具,大家都有自己觉得好使的,我本身JAVA用得很少,也没啥可推荐的,如果是Golang的话,自带的go tool pprof就能很好的进行性能优化。

或者最简单的,就是把各个模块的时间打印出来,压测一遍,看看哪个模块耗时,然后再去仔细review那个模块的代码,进行算法和数据结构的优化,我个人比较推崇这个办法,虽然比较笨,但是比较实在,性能差就是差,比较直观能看出来,也能看出需要优化的点,而且比较贴近代码,少了外部工具的干扰,可能也比较装逼吧。

这个过程是一个长期的过程,也是《重构:改善代码的既有设计》中提到的,一个优秀的系统需要不断的进行代码级别的优化和重构,所以

高并发系统的实现,就是不断的优化你代码的性能,不断逼近你设计时的理论值

再考虑外部通用方法

以上两个都完成了,并发量也基本达到理论值了,但是还有提升的需求,这时候再来考虑外部的通用方法,比如加一个LRU缓存,把热词的查询时间变成O(1),进一步提高性能。

说起LRU,多说一句,这是个标准的缓存技术了,实现起来代码也不复杂,就是个哈希表+链表的数据结构,一个合格的开发人员,即便没有听说过,给定一个场景,应该也能自己设计出来,我见过很多简历都说自己有大型高并发系统的开发经验,能熟练运用各种缓存技术,也对缓存技术有深入的了解,但是一面试的时候我让他写个LRU,首先有50%的人没听说过,OK,没听过没关系,我描述一下,然后给一个场景,硬盘上有N条数据,并且有一个程序包,提供GET和SET方法,可以操作磁盘读写数据,但是速度太慢,请设计一个内存中的数据结构,也提供GET和SET方法,保存最近访问的前100条数据,这个数据结构就是一个LRU了,让面试者实现出来,如果觉得写代码麻烦,可以把数据结构设计出来描述一下就行了,就这样,还很多人不会,这怎么能说是对缓存技术有深入了解呢?就这样,怎么能说有过大型高并发系统的经验呢?这只是开源工具的使用经验罢了。

在没把系统的性能压榨完全之前,不要使用外部的通用方法,因为使用了以后就没有太多进一步优化空间了。

最后靠运维技术了

上面几种都已经弄完了,还需要提升性能,这时候再考虑运维的技术了,比如常规的加负载均衡,部署成集群之类的,通过运维和部署的方法提高服务的并发性。

高并发系统只是相对的,没有什么无上限的高并发,流量的洪流来了,再高的高并发一样挂,新浪微博的高并发应该做得很好吧?但是林心如发条微博说她和霍建华谈恋爱了,一样把微博搞挂了(非官方消息啊,我猜测的,呵呵,那天下午新浪微博正好挂了),呵呵,你说要是TFBOY明天过生日,微博是不是要连夜加几个redis集群啊?如果有微博的朋友,留个言溜溜呗:)

总结

罗里吧嗦说了这么多,其实我就想表达一个意思,不管是前面的高可用,还是今天的高并发

代码才是关键,架构都是锦上添花的东西,既然是锦上添花的,必然坑多,没有什么捷径。

代码的健壮性决定了高可用,这些印度人就能做到,而高性能,高并发需要的不仅仅是代码的健壮性,还有数据结构的设计和代码的调优能力了。

架构模式是大家总结出来的,和你的系统可能关系不是很大,学习太多的架构,脑袋会乱,还不如实打实的看几本书,然后对着架构多推敲练习,很多人对数据结构嗤之以鼻,觉得对于现有的开发来说,数据结构没那么重要了,但对于后端开发来说,数据结构是很重要的技能,虽然面试的时候不会让你去翻转一棵二叉树,但二叉树是什么,什么场景下用还是应该知道的吧?

找准合适的数据结构,不断的优化代码,这样来提升你的系统性能,这样的系统才是你可控的,才有不断优化的空间,更好的高并发,如果一开始就上外部的缓存技术,很可能如果性能达不到要求,就没有优化空间了,因为要修改外部的系统还是很困难的。

这几篇我觉得我都在瞎扯淡,虽然比较虚,但也是我工作这么些年淌坑无数以后的总结吧,后面的文章会写一些实际的了,我的搜索引擎部分还没写完呢。尽请期待。呵呵。。


如果你觉得不错,欢迎转发给更多人看到,也欢迎关注我的公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行