所谓高并发,指的是同一时间可以处理大量的WEB请求,这个指标用来衡量一个架构的体量和性能。这里的大量如何评估呢?1000算不算?10000算不算?
对于中小型的站点来说,可能并发100多就很不错了,但对于像淘宝这样的大型站点,单凭一个接口调用的量就有可能达到百万的并发。在双11这样的大型活动场景里,淘宝的并发请求数都能达到上亿次,这样的体量无论是在国内还是在国际都是排在前列的。而本章节要讲述的内容是如何设计一个可以承载巨量并发请求的架构。
要想设计一个高并发的架构,首先要搞清楚架构的分层,因为每一个层面都有可能造成影响高并发的瓶颈点。找到瓶颈点后,只要把瓶颈点解除,自然会提升整个架构的并发处理能力。我们先来看一个综合分层的架构图:
对于大型网站来说增加CDN这一层是非常有必要的,CDN(Content Delivery Network,内容分发网络),它属于网络范畴的一个技术,它依靠部署在各个区域的边缘服务器,实现负载均衡、内容分发调度等功能。它使得用户就近获取内容,降低网络堵塞,提供用户访问响应速度。
来举一个通俗点的例子:小明公司做了了一个针对全国用户的业务,服务器放在了北京,但是深圳用户在访问网站的时候非常卡顿,有时候甚至访问不到。经排查,造成该问题的原因是深圳用户所在网络到北京的机房延迟非常大。小明想到了一个办法,他在深圳的某机房假设了一台服务器,把北京服务器上的文件传输到深圳的服务器上,当深圳用户访问网站时,让该用户直接去访问深圳的服务器,而不是访问北京的服务器。同理,其他城市也效仿深圳假设了类似的服务器,这样全国各地的用户访问公司业务都很顺畅了。
例子中的解决方案其实就是CDN实现原理,当然,真正的CDN技术要复杂得多,要考虑很多问题,比如边缘服务器的分布、机房的网络、带宽、服务器的存储、智能DNS解析、边缘服务器到真实服务器之间的网络优化、静态和动态资源的区分、缓存的优化、压缩、SSL等等问题。关于这些细节技术我不做过多解释,但希望大家能通过我的描述了解CDN在架构中存在的意义。
CDN是处于整个架构体系中最前端的一层,它是直接面对用户的,CDN会把静态的请求(图片、js、css等)直接消化掉,然后把动态的请求往后传递。实际上,一个网站(比如,淘宝网)超过80%的请求都是静态的请求,那也就意味如果前端架设了CDN,即使并发1亿,也只有2000万到了后端的WEB上。那么你可能会问,CDN能支持8000万的并发吗?这个主要取决于CDN厂商的实力,如果他们搞10000个节点(即边缘服务器),每个节点上消化8000并发,如果搞10万个节点,每个节点只需要消
化800个并发而已。然而,一台普通的Nginx服务器(配置2核CPU4G内存)轻松处理5万个并发(前提是做过优化,并且处理的请求是静态请求、或者只是转发请求)。
这一层,其实就是一个反向代理(或者叫做分发器),它的主要作用是把用户的请求按照预设的算法发送给它后面的WEB服务器。该层在实现上大致分为两类:四层和七层(网络OSI七层模型),Nginx的负载均衡就属于七层,而LVS属于四层。从吞吐量上来分析,四层的负载均衡更有优势。
所以,要想实现高并发,负载均衡这一层必须要使用四层技术,其中LVS就是一款不错的开源负载均衡软件。LVS有三种实现模式:
在这种模式下,负载均衡器上有设置iptables nat表的规则,实现了把用户的请求数据包转发到后端的Real Server(即WEB
Server)上,而且还要把WEB Server的响应数据传递给用户,这样负载均衡器很容易成为一个瓶颈,当并发量很大时,一定会
影响整个架构的性能。
LVS的DR模式和NAT不一样,负载均衡器只需要分发用户的请求,而WEB Server的返回数据并不通过负载均衡器传递,数据直
接由WEB Server自己处理。这样就解决了NAT模式的瓶颈问题。但是,DR模式有一个要求:负载均衡器和WEB Server必须在同一个内部网络(要求在相同的广播域内),这是因为DR模式下,数据包的目的MAC地址被修改为了WEB Server的MAC地址。
LVS的IP Tunnel模式和DR模式类似,负载均衡器只需要分发用户的请求,而WEB Server的返回数据并不通过负载均衡器传递,数据直接由WEB Server自己处理。这样就解决了NAT模式的瓶颈问题。和DR模式不同的是,IP Tunnel模式不需要保证分发器和Real Server在同一个网络环境,因为这种模式下,它会把数据包的目的IP地址更改为Real Server的IP地址。这种模式,可以实现跨机房、跨地域的负载均衡。
对于以上三种模式来说,IP Tunnl模式更适合用在高并发的场景下。但有一点需要注意,这台作为负载均衡器的服务器无论是自身的网卡性能,还是它所处的网络环境里的网络设备都有很高的要求。
可能你会有疑问,这台负载均衡器终究只是一个入口,一台机器顶多支撑10万并发,对于1000万、2000万的并发怎么实现?答案是:叠加!一台10万,100台就是1000万,200台就是2000万……
还有个问题,如何让一个域名(如,www.google.com)访问这200台负载均衡器?请思考一下上一小节的CDN技术,它就可以让一个域名指向到成千上万的边缘服务器上。没错,智能DNS解析可以把全国甚至世界各地的请求智能地解析到最优的边缘服务器上。当然,DNS也可以不用智能,大不了直接添加几百条A记录呗,最终也会把用户的请求均衡地分发到这几百个节点上。
如果最前端使用了CDN,那么在WEB这一层处理的请求绝大多数为动态的请求。什么是动态的请求?除了静态的就是动态的,那什么是静态的?前面提到过的图片、js、css、html、音频、视频等等都属于静态资源,当然另外还有太多,大家可以参考第一篇文章《HTTP扫盲》的MIME Type。
再来说这个动态,你可以这样理解:凡是涉及到数据库存取操作的请求都属于动态请求。比如,一个网站需要注册用户才可以正常访问里面的内容,你注册的用户信息(用户名、密码、手机号、邮箱等)存入到了数据库里,每次你登录该网站,都需要到数据库里查询用户名和密码,来验证你输入的是否是正确的。
如果到了WEB这一层全都是动态的请求的话,那么并发量的多少主要取决于WEB层后端的DB层或者Cache层。也就是说要想提升WEB层服务器的并发性能,必须首先要提升DB层或者Cache层的并发性能。
我们不妨来一个假设:要求架构能支撑1000万并发(动态),假设单台WEB Server支撑1000并发,则需要1万台服务器。实际生产环境中,单台机器支撑1000并发已经非常厉害啦,至少在我的运维生涯里,单台WEB Server最大动态并发量并没有达到过这么大。
我提供一组数据,你自然就可以估算出并发量。在这我拿PHP的应用举例:一个PHP的网站,单个PHP-FPM进程耗费内存在2M-20M(假设耗费内存10M),1000个并发也就意味着同时有1000个PHP-FPM进程,耗费内存为1000*10M=10G,再加上留给系统1G内存,所以1000并发至少需要11G内存。
按照上面的估算,2000并发则需要21G内存,10000并发则需要101G内存,这个仅仅是理论值。实际上,并发量不仅跟内存有关系,跟CPU同样也有关系。如果服务器有4核CPU,则理论上仅仅支持4个进程同时占用CPU计算,也就是说仅能支持4个并发。当然,CPU计算那么快,进程会来回切换排队占用CPU,这样能够实现即使只有4核CPU,依然能够支持几百甚至上千的并发。但无论如何,CPU的核数越大,该服务器能支撑的并发也就越大。
对于高并发的架构,WEB Server必然会做负载均衡集群,单台WEB Server的配置通常会选择4核8G这样的配置(这个配置,最好是根据自己业务的特性选择合适的,毕竟现在大多企业都使用公有云或者私有云,服务器的配置可以定制),然后由这样的机器来组成一个大型集群,最终实现高并发的需求。
增加这一层的目的是为了减轻DB层的压力,Cache层有一个特点:数据的读写发生在内存里,跟磁盘并没有关系。正是因为这个特点,保证了数据的读写速度非常快。假如没有Cache层,并发1000万的动态请求意味着这1000万会直接透传到DB层(如MySQL),1000万的并发就会造成1000万对磁盘的读写操作。我想大家都明白,磁盘的读写速度远远低于内存的读写速度,要想支撑1000万并发读写是不现实的。
当然,Cache层主要针对读操作,而且它仅仅是缓存一部分DB层的热数据(频繁读取的那部分数据)。举一个下例子:有一次公司的某个业务临时做了一个推广活动,结果导致访问量暴涨10倍,原本的服务器架构并不能支撑这么大的量,结果可想而知。当时,我们的解决方案是:把查询量非常大的数据缓存到Memcached里面,然后在没有增加硬件的情况下顺利抗了过去。可见这一
个Cache层所起到的作用是多么关键。
可以作为Cache的角色通常是NoSQL,如Memcached、Redis等。在第3章《常见WEB集群架构》中我曾提到过Memcached,架构图如下:
作为Cache角色时,Redis和Memcached用法基本一致。其实,抛开这个Cache角色,NoSQL也可以独立作为DB层,这主要取决于业务逻辑是否支持拿NoSQL作为数据存储引擎,毕竟NoSQL的数据结构和关系型数据库比还是比较简单的,有些复杂场景无法实现。但为了实现高并发,我们可以尝试同时使用传统的关系型数据库和NoSQL数据库存储数据。
既然Memcached和Redis都可以作为Cache角色,那么到底用哪一个可以支撑更大的并发量呢?其实这两者各有千秋,不能盲目地下结论说哪个更快或者更好。得根据你的业务选择适合的服务。由于Redis属于单线程,故只能使用单核,而Memcached属于多线程的,从而可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以
上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。如果不考虑存储数据大小,肯定Memcached性能更好,毕竟它是多线程的。
另外你需要了解,Memcached的数据只能存内存,不能存到磁盘,而Redis支持把内存的数据镜像一份到磁盘上,而且还可以记录日志(通过这个日志来获取数据)。Memcached只能存简单的K-V格式的数据,而Redis支持更多的数据类型(如,list、hash)。
无论你用哪一种作为Cache,我们都需要为其做一个高可用负载均衡集群,这样才可以满足高并发的需求。
可以说DB层是整个架构体系中非常关键的一层,也是瓶颈所在。原因无他,只因它涉及到对磁盘的读写。所以,为了提升性能,对服务器磁盘要求很高,至少是15000r/m的SAS硬盘而且需要做RAID10,如果选择SSD盘更优。
最简单暴力提升并发数量的办法是服务器的堆积(即,做负载均衡集群),但DB层跟WEB层不一样,它涉及到数据存储到磁盘
里,服务器可以累加,但是磁盘在累加的同时,如何保证所有的服务器能读写同一份数据?这是一个很大的问题,所以单纯的服务器堆积只适合小规模的业务,对于并发上千万的业务并不适用。并发量大的站点,意味着数据量也是非常大的(如,TB级别),如果单个数据库上TB,那一定是一个灾难,无论是读写还是备份都将是极大的问题。
那如何解决这个问题呢?既然大了不行,那就将大的库切割成小的库即可。你可不要把这个切割单纯地想象成切割文件。我们可以从两个维度来实现切割。
大型网站为了应对日益复杂的业务场景,通过使用分而治之的手段将整个网站业务分成不同的产品线,如大型购物网站就会将首页、商铺、订单、买家、卖家、仓储、物流、售后服务等拆分成不同的产品项,这样数据库自然也拆分为了多个数据库,原来TB级的数据,变成了GB级。如果觉得还不够细化,我们可以继续把商铺进一步拆分,比如个人类的、企业类的、明星类的、普通类的等等。总之,你可以根据业务特性想到几百种拆分方法,最终一块大蛋糕变成了几十甚至几百块小蛋糕,吃起来就简单多了。
业务拆分是产品经理设计的,但是这个分库分表只能是DBA操刀。如果一个几千GB的大库读写很慢的话,但分成1000个几GB的小库后,读写速度一定是有质的飞跃。同理,表也是可以像库那样划分的。分库分表需要借助数据库中间件来完成。比如MySQL分库分表比较好的中间件MyCAT就不错。
有了以上两个拆分原则,无论多大的库,我们都可以划分为比较小的库,这样即使使用传统的架构依然可以轻松应付。最终的DB层架构就成了蜂窝状的一组一组的小单元,每一个单元独立做高可用以及负载均衡集群。
一个大型的网站,一定少不了消息队列这一层。在前面第3章《常见WEB集群架构》一文中就提到过它,它主要解决的问题是:解耦合、异步处理、流量削峰等。以下三个应用场景曾在第3章出现过,也许你现在看会有更深层次的体会。
用户上传图片到服务器,要求人脸识别系统识别该上传图片,传统的做法是:用户上传图片 → 服务接收到图片开始识别图片 → 系统判断图片是否合法 → 反馈给用户是否成功。这个要涉及两个系统:
而使用消息队列,流程会变成这样:
用户上传图片后,图片上传系统将图片信息写入消息队列,直接返回成功;而人脸识别系统则定时从消息队列中取数据,完成对新增图片的识别。
此时图片上传系统并不需要关心人脸识别系统是否对这些图片信息的处理、以及何时对这些图片信息进行处理。事实上,由于用户并不需要立即知道人脸识别结果,人脸识别系统可以选择不同的调度策略,按照闲时、忙时、正常时间,对队列中的图片信息进行处理。
用户到一个网站注册账号,系统需要发送注册邮件并验证短信。传统的处理流程如下:
这种方式下,需要最终发送短信验证后再返回给客户端。
另外一种方式就是异步处理,即注册邮件和短信同时发送,流程如下:
当用户填写完注册信息并成功写入消息队列后,就可以反回成功的信息给客户端,从而客户端不需要再等待系统发邮件和发短信,不仅客户端不用等,而且处理客户端请求的那个工作进程也不需要等(这个特性非常重要,它是实现高并发的一个重要手段),这个就是异步处理的优势。
很典型的应用就是购物网站秒杀活动,一般由于瞬时访问量过大,服务器接收过大,会导致流量暴增,相关系统无法处理请求甚至崩溃。而加入消息队列后,系统可以从消息队列中取数据,相当于消息队列做了一次缓冲。
该方法可以让请求先入消息队列,而不是由业务处理系统直接处理,极大地减少了业务处理系统的压力。另外队列长度可以做限制,比如队列长度为100,则该系统只能有100人可以秒杀到商品,排在100名后的用户无法秒杀到商品,而返回活动已结束或商品已售完的信息。
总之,消息队列的引入极大提升了整个架构的并发能力。从WEB层接收到动态的请求后,Cache层过滤掉一部分,然后请求逐一地发送到DB层,在这个过程中,查询时间很长的请求可以单独摘出来,把它搞到消息队列里,这样WEB层和DB层只处理那种快速有结果的查询,并发量自然很大。而消息队列会慢慢消化掉这些特殊的查询,或许你有疑问,这种查询慢的请求也很多怎么
办?不也同样影响到并发量吗?毕竟最终的查询到了DB层。不要忘记消息队列本身就有削峰的能力,如果有大量的这种查询,那么就让它们排好队列,慢慢消化,总之不让它们影响到DB层的正常查询。
可以提供消息队列的服务有那么多(RabitMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ、Beanstalk、Redis等等),到底选择哪一种?最好是让研发同事来定吧,只有研发团队最了解自己代码的逻辑架构,适合自己的才是最好的。事实上,无论你用哪一种消息队列服务,它都不会成为整个架构的瓶颈点。当然,你最好做一个分布式的集群,这样能够保证它的横向扩容或者缩容。
关于存储,目前的解决方案我归类为以下几种:
就是服务器自身的磁盘,对于像DB层这样关键的角色,我们通常会用高性能磁盘做RAID10。特点:方便维护、稳定、性能非常好、容量有限、扩容不方便。
主要有三类:NAS、SAN、DAS。
NAS:类似Linux系统做的NFS服务,它是建立在操作系统层面上的一种共享存储解决方案,它是一种商业产品。NAS比较适合小规模网站。特点:容量大、扩容不方便、吞吐量一般(受网络环境影响)、稳定性好、成本高。
SAN:也是一种商业的共享存储解决方案,支持普通网络或者光纤接入,比NAS更加底层。特点:容量大、扩容不方便、性能好(比NAS强很多)、稳定性好、成本高昂。
DAS:磁盘阵列,支持RAID,商业的存储。特点:容量大、扩容不方便、不支持共享、性能好、稳定性好。
随着云计算、大数据技术的日益流行,分布式共享存储技术越来越成熟,无论是商业的还是开源的都有不少优秀的解决方案。比如,开源的有HDFS、FastDFS、MFS、GlusterFS、Ceph、Swift等。这类存储有一些共同特点:方便扩容、容量可以无限大、性能一般(网络会成为瓶颈)、成本低、稳定性好。
本节的存储层我也归类为三大类:WEB层面的存储(比如存储代码、图片、js、css、视频、音频等静态文件)、日志、DB层面的存储(即数据库的数据存储)。
这三类存储,最要命的是DB层的存储,前面我也提到过DB层很关键,而决定DB层性能的因素中这个数据存储(磁盘)性能起到决定性作用。解决方案我也提到了,就是“大变小,一变多,自己管自己”。正常情况下巨量的数据库必然会使用大容量存储设备,这样最终的结果是—慢!所以,我们需要分模块、分库分表,最终单台机器的本地磁盘就可以支撑这些巨量的数据,读写速度不会被网络等因素影响。
日志类和WEB层静态文件的存储可以选择分布式共享存储解决方案,因为这类的存储不需要太高的吞吐量,它们所占用的空间比较大,而且会越来越大。
当你看完以上内容后,可能你的心中还是没有一个完整的答案,所以这个总结很关键!
1)高并发网站一定会使用CDN,而且需要把静态文件存储在边缘服务器上。
2)负载均衡一定要使用四层的,比如LVS,如果是LVS,选择IP Tunnel模式。
3)WEB层把静态的请求交给CDN处理,所以只处理动态的请求,要支持横向扩容,可以方便地通过加机器来增加并发量。
4)增加Cache层,把热数据搞到这一层,减少对DB层地压力。对这一层做分布式集群架构设计,方便扩容。
5)增加消息队列,实现解耦合、流量削峰,从而提升整个架构地并发能力。
6)DB层要通过拆分业务、分库分表来实现大变小、一变多,对单独模块做高可用负载均衡集群,从而提升并发能力。
7)DB层的存储使用本地磁盘,日志类、静态文件类使用分布式文件存储。