当前位置 博文首页 > 三分恶:高并发,我把握不住啊!

    三分恶:高并发,我把握不住啊!

    作者:三分恶 时间:2021-05-12 18:17

    慎入,作者高并发搞得少(没搞过),这里面水太深,什么高并发,大流量的东西都是虚拟的,作者还太年轻,没有那个经历,把握不住。系统只有几QPS,开心快乐就行,不PK,文明PK。

    高并发,你把握不住的

    我关注的大佬更新了,在干货文章的下面有这么一小条:

    高并发课程广告

    我承认我有赌的成分,点进去一看,果然是广告。说真的,内容看起来还是很有吸引力的,但是贫穷阻止了我消费的冲动。

    作为一个高并发的门外汉,尝试结合学过的课程和一些网上的资料来整理一下对于高并发的认识。——实战是不可能实战的,只能动动嘴皮这样子。

    什么是高并发

    高并发指的是系统同时处理很多请求。

    高并发是一个结果导向的东西,例如,常见的高并发场景有:淘宝的双11、春运时的抢票、微博大V的热点新闻等,这些典型场景并不是陡然出世,而是随着业务发展的发展而逐渐出现。像2020年淘宝双11全球狂欢季,订单创建峰值达到了惊人的58.3万笔/秒,4年前的2016年,这个数字大概是四分之一,再往前四年,这个数据不可考,但是肯定就没这么夸张了。

    高并发的业务场景出现了,随之而来的就是要支持这个高并发业务场景的架构——技术要为业务服务,业务倒逼技术发展。高并发的架构也不是某个天才冥思苦想或者灵机一动,这个过程是随着业务的发展而演进。用一个比喻,先有了秋名山,才到了老司机。

    img

    那到底多大并发才算高并发呢?

    这个本身是没有具体标准的事情,只看数据是不行的,要结合具体的场景。不能说10W QPS的秒杀是高并发,而1W QPS的信息流就不是高并发。信息流场景涉及复杂的推荐模型和各种人工策略,它的业务逻辑可能比秒杀场景复杂10倍不止。业务场景不一样,执行复杂度不一样,单看并发量也没有意义。

    总结就是,高并发无定势,是要和具体的业务场景相结合的。无高并发场景,无高并发架构。

    高并发目标

    宏观目标

    高并发绝不意味着只追求高性能。从宏观角度看,高并发系统设计的目标有三个:高性能、高可用,以及高可扩展。就是所谓的“三高”,三高不是孤立的,而是相互支撑的。

    1、高性能:性能体现了系统的并行处理能力,在有限的硬件投入下,提高性能意味着节省成本。同时,性能也反映了用户体验,响应时间分别是100毫秒和1秒,给用户的感受是完全不同的。

    2、高可用:表示系统可以正常服务的时间。一个全年不停机、无故障;另一个隔三差五出线上事故、宕机,用户肯定选择前者。另外,如果系统只能做到90%可用,也会大大拖累业务。

    3、高扩展:表示系统的扩展能力,流量高峰时能否在短时间内完成扩容,更平稳地承接峰值流量,比如双11活动、明星离婚等热点事件。

    三高

    这3个目标是需要通盘考虑的,因为它们互相关联、甚至也会相互影响。

    比如说:考虑系统的扩展能力,你需要将服务设计成无状态的,这种集群设计保证了高扩展性,其实也间接提升了系统的性能和可用性。

    再比如说:为了保证可用性,通常会对服务接口进行超时设置,以防大量线程阻塞在慢请求上造成系统雪崩,那超时时间设置成多少合理呢?一般,我们会参考依赖服务的性能表现进行设置。

    具体目标

    性能指标

    性能指标通过性能指标可以度量目前存在的性能问题,也是高并发主要关注的指标,性能和流量方面常用的一些指标有

    1. QPS/TPS/HPS:QPS是每秒查询数,TPS是每秒事务数,HPS是每秒HTTP请求数。最常用的指标是QPS。

    需要注意的是,并发数和QPS是不同的概念,并发数是指系统同时能处理的请求数量,反应了系统的负载能力。

    并发数 = QPS?平均响应时间
    1. 响应时间:从请求发出到收到响应花费的时间,例如一个系统处理一个HTTP请求需要100ms,这个100ms就是系统的响应时间。

    2. 平均响应时间:最常用,但是缺陷很明显,对于慢请求不敏感。比如 1 万次请求,其中 9900 次是 1ms,100 次是 100ms,则平均响应时间为 1.99ms,虽然平均耗时仅增加了 0.99ms,但是 1%请求的响应时间已经增加了 100 倍。

    3. TP90、TP99 等分位值:将响应时间按照从小到大排序,TP90 表示排在第 90 分位的响应时间, 分位值越大,对慢请求越敏感。

    img

    1. RPS(吞吐量):单位时间内处理的请求量,通常由QPS和并发数决定。
      通常,设定性能目标时会兼顾吞吐量和响应时间,比如这样表述:在每秒 1 万次请求下,AVG 控制在 50ms 以下,TP99 控制在 100ms 以下。对于高并发系统,AVG 和 TP 分位值必须同时要考虑。

      另外,从用户体验角度来看,200 毫秒被认为是第一个分界点,用户感觉不到延迟,1 秒是第二个分界点,用户能感受到延迟,但是可以接受。
      因此,对于一个健康的高并发系统,TP99 应该控制在 200 毫秒以内,TP999 或者 TP9999 应该控制在 1 秒以内。

    2. PV:综合浏览量,即页面浏览量或者点击量,一个访客在24小时内访问的页面数量。

    3. UV:独立访客 ,即一定时间范围内相同访客多次访问网站,只计算为一个独立的访客。

    4. 带宽: 计算带宽大小需要关注两个指标,峰值流量和页面的平均大小。

      日网站带宽可以使用下面的公式来粗略计算:

      日网站带宽=pv/统计时间(换算到秒)*平均页面大小(单位kB)*8

    峰值一般是平均值的倍数;

    QPS不等于并发连接数,QPS是每秒HTTP请求数量,并发连接数是系统同时处理的请求数量:

    峰值每秒请求数(QPS) = (总PV数 * 80%) /(6小时秒数 * 20%)

    可用性指标

    高可用性是指系统具有较高的无故障运行能力,可用性 = 平均故障时间 / 系统总运行时间,一般使用几个 9 来描述系统的可用性。

    可用性指标

    对于大多数系统。2个9是基本可用(如果达不到开发和运维可能就要被祭天了),3个9是较高可用,4个9是具有自动恢复能力的高可用。要想达到3个9和4个9很困难,可用性影响因素非常多,很难控制,需要过硬的技术、大量的设备资金投入,工程师要具备责任心,甚至还要点运气。

    可扩展性指标

    面对突发流量,不可能临时改造架构,最快的方式就是增加机器来线性提高系统的处理能力。

    对于业务集群或者基础组件来说,扩展性 = 性能提升比例 / 机器增加比例,理想的扩展能力是:资源增加几倍,性能提升几倍。通常来说,扩展能力要维持在 70%以上。

    但是从高并发系统的整体架构角度来看,扩展的目标不仅仅是把服务设计成无状态就行了,因为当流量增加 10 倍,业务服务可以快速扩容 10 倍,但是数据库可能就成为了新的瓶颈。

    像 MySQL 这种有状态的存储服务通常是扩展的技术难点,如果架构上没提前做好规划(垂直和水平拆分),就会涉及到大量数据的迁移。

    我们需要站在整体架构的角度,而不仅仅是业务服务器的角度来考虑系统的扩展性 。所以说,数据库、缓存、依赖的第三方、负载均衡、交换机带宽等等都是系统扩展时需要考虑的因素。我们要知 道系统并发到了某一个量级之后,哪一个因素会成为我们的瓶颈点,从而针对性地进行扩展。

    高并发架构演进

    谁不是生下来就是老司机,架构也不是架起来就支持高并发。我们来看一个经典的架构演进的例子——淘宝,真实诠释了“好的架构是进化来的,不是设计来的”。

    以下是来自《淘宝技术这十年》描述的淘宝2003—2012年的架构演进。

    个人网站

    初代淘宝的团队人员只有十来个,而且面临千载难逢的商业机会,所以要求上线的时间越快越好(实际用了不到一个月),那么淘宝的这些牛人是怎么做到的呢?

    ——买一个。

    初代淘宝买了这样一个架构的网站: LAMP(Linux+Apache+MySQL+PHP)。整个系统的架构如下:

    初代架构

    最后开发的网站是这样的:

    初代淘宝网站

    由于商品搜索比较占用数据库资源,后来还引入了阿里巴巴的搜索引擎iSearch。

    Oracle/支付宝/旺旺

    淘宝飞速发展,流量和交易量迅速提升,给技术带来了新的问题——MySQL抗不住了。怎么办?要搞点事情吗?没有,淘宝买了Oracle数据库,当然这个也考虑到团队里有Oracle大牛的原因。

    替换了数据库之后的架构:

    引入Oracle之后的淘宝架构

    比较有意思的,当时由于买不起商用的连接池,所以用了一个开源的连接池代理服务SQLRelay,这个代理服务经常会死锁,怎么解决呢?人肉运维,工程师24小时待命,出现问题赶紧重启SQL Relay服务。????

    后来为了优化存储,又买了NAS(Network Attached Storage,网络附属存储),NetApp 的 NAS 存储作为了数据库的存储设备,加上 Oracle RAC(Real Application Clusters,实时应用集群)来实现负载均衡。

    Java 时代 1.0

    2004年,淘宝已经运行了一年的时间,上面提到的SQLRelay的问题解决不了,数据库必须要用Oracle,所以决定更换开发语言。

    在不拖慢现有业务发展的情况下,平滑更换整体的架构,对当时的淘宝仍然是个有挑战性的事情。所以怎么办?淘宝的解决方案是请了Sun公司的大佬。

    当时,由于struts1.x存在很多问题,所以淘宝自研了一套MVC框架。Sun当时在推EJB,所以这套架构里也引入了EJB。

    Java 时代 1.0

    Java 时代 2.0

    在之前,淘宝的架构的架构主要思路还是“买”,随着业务的发展,到了2005 年,“买”已经很难解决问题了,需要对整个架构进行调整和优化,需要综合考虑容量、性能、成本的问题。

    在Java时代2.0,主要做了对数据分库、放弃EJB、引入Spring、加入缓存、加入CDN等。

    Java时代2.0

    Java时代3.0

    Java时代3.0的最大特点就是淘宝开始从商用转为“自研”,开始真正创造自己的核心技术,例如缓存存储引擎Tair,分布式存储系统TFS。搜索引擎iSearch也进行了升级。引入了自研技术的淘宝架构:

    Java时代3.0

    分布式时代1.0

    到了2008年的时候,淘宝的业务进一步发展。

    整个主站系统的容量已经到了瓶颈,商品数在1亿个以上,PV在2.5亿个以上,会员数超过了 5000万个。这时Oracle的连接池数量都不够用了,数据库的容量到了极限,即使上层系统加机器也无法继续扩容,我们只有把底层的基础服务继续拆分,从底层开始扩容,上层才能扩展,这才能容纳以后三五年的增长。

    淘宝开始对业务模块逐步拆分和服务化改造。例如拆分出了商品中心、商品中心等等。同时引入了一些自研的中间件,如分布式数据库中间件,分布式消息中间件等等。

    面向服务架构

    《淘宝技术这十年》这本书只描述到了2012年,也就是分布式时代。上图是根据参考【8】画的一张图。

    转眼理2012又快过了十年,这十年,阿里巴巴逐渐进入极盛时代,技术上也是风起云涌,才人辈出。粒度更细的微服务、隔离差距的容器化技术、快速伸缩的云平台技术…… 如果《淘宝技术这十年》的作者能再写一个十年,一定也是非常精彩。

    按照参考【10】,接下来的淘宝服务化开始逐渐演进到云平台架构,由于资料实在难找,而且这时候以淘宝的体量,内部的架构复杂度足以写一本书了。所以接下来的架构演进参考服务端高并发分布式架构演进之路,是一个牛人以淘宝为模拟对象进行的架构演进,虽然不是淘宝真正的架构技术演进,但也很值得借鉴。

    在这里我们略过了微服务架构——分布式时代2.0,微服务本身是更细粒度、更轻量级的服务化,这里插入一个关于微服务很有意思的说法——马丁老哥老被人说设计的东西不符合面向服务的概念,于是他就自己发明创造了一个灵活的微服务理论,以后再有人说:马老师,你又不遵循微服务架构设计的原则了。嗯,你说哪一点不符合,我立马去改微服务的理论。

    容器化时代

    前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

    在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响。

    容器化时代

    云平台时代

    在服务化的时候,淘宝已经演进到了云平台架构。

    所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体,在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用,用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。

    clipboard.png

    简单总结一下:高并发的架构某种程度上是逼出来的,一般人谁能想到淘宝当年抛弃php是因为解决不了数据库连接池的问题。架构演进就像是西湖的水——西湖的水,工程师的泪,说起来容易,里面究竟灭了多少火,填了多少坑。我们外人看到的平湖秋波,里面水很深??。

    高并发架构实现

    想让系统抗住更多的并发,主要就是两个方向:

    • 纵向扩展

      1、提升单机的硬件性能:通过增加内存、 CPU核数、存储容量、或者将磁盘 升级成SSD等堆硬件的方式来提升

      2、提升单机的软件性能:使用缓存减少IO次数,使用并发或者异步的方式增加吞吐量。

    • 横向扩展:单机性能总会存在极限,所以最终还需要引入横向扩展,通过集群部署以进一步提高并发处理能力。

      1、做好分层架构:这是横向扩展的前提,因为高并发系统往往业务复杂,通过分层处理可以简化复杂问题,更容易做到横向扩展。

      2、各层进行水平扩展:无状态水平扩容,有状态做分片路由。业务集群通常能设计成无状态的,而数据库和缓存往往是有状态的,因此需要设计分区键做好存储分片,当然也可以通过主从同步、读写分离的方案提升读性能。

    用一个比喻,你要去打十个大汉,你大概是打不过的,最好的结果就是他们打不倒你——吊起来打。所以这时候就得想办法了。第一个办法就是努力锻炼,然后全副武装,也许还有点希望,这就是纵向扩展;第二个办法,不行,你一看对面人多,你就叫了十九个兄弟,然后你们二十个打他们十个,唉,这下看上去能打的过了,这就是横向扩展;还有第三个不常用的办法,你找个门把住,每次就放一个大汉进来,打倒一个再放下一个,这个就是削峰限流的做法。

    我要打十个

    我们看一下一个大概的支持三高的典型架构:

    高并发典型架构

    接下来,我们从上往下,看一下,各层的一些关键技术。

    网络层

    多机器

    堆机器不是万能的,不堆机器是万万不能的。

    我们努力地升级改造架构,最后让我们提供的服务能够快速横向扩展。横向扩展的基础同样是要有一定数量的、一定性能的机器。

    还是上面哪个比喻,你要打十个大汉,等你努力练成了叶师傅,你突然发现对面的孩子都长大了,人数×2,这时候你还是得叫兄弟。

    一般狗大户大厂在全国各地都有机房,可能光北京就有两个,把不同地方的请求分到不同的机房,再分到不同的集群,再分到不同的机器,这么一匀,就在服务能扛的范畴之内了。我们大概来看一下,怎么估算所需机器的数量。

    • 通过QPS和PV计算部署服务器的台数

    单台服务器每天PV计算:

    公式1:每天总PV = QPS * 3600 * 6
    公式2:每天总PV = QPS * 3600 * 8

    服务器计算:

    服务器数量 = ceil( 每天总PV / 单台服务器每天总PV )
    • 峰值QPS和机器计算公式

    原理:每天80%的访问集中在20%的时间里,这20%时间叫做峰值时间

    公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)

    机器:峰值时间每秒QPS / 单台机器的QPS = 需要的机器。

    一般有大流量业务的公司都实现了多机房,包括同城多机房、跨城多机房、跨国多机房等。为了保证可用性,财大气粗的公司会预备大量的冗余,一般会保证机器数是计算峰值所需机器数的两倍。需要节约成本的,也可以考虑当前流行的云平台,之前热点事件的时候,微博就从阿里云租了不少云服务器。

    DNS

    DNS简单示意图

    DNS是请求分发的第一个关口,实现的是地理级别的均衡。dns-server对一个域名配置了多个解析ip,每次DNS解析请求来访问dns-server。通常会返回离用户距离比较近的ip,用户再去访问ip。例如,北京的用户访问北京的机房,南京的用户访问南京的资源。

    一般不会使用DNS来做机器级别的负载均衡,因为造不起,IP资源实在太宝贵了,例如百度搜索可能需要数万台机器,不可能给每个机器都配置公网IP。一般只会有有限的公网IP的节点,然后再在这些节点上做机器级别的负载均衡,这样各个机房的机器只需要配置局域网IP就行了。

    DNS负载均衡的优点是通用(全球通用)、成本低(申请域名,注册DNS即可)。

    缺点也比较明显,主要体现在:

    • DNS 缓存的时间比较长,即使将某台业务机器从 DNS 服务器上删除,由于缓存的原因,还是有很多用户会继续访问已经被删除的机器。

    • DNS 不够灵活。DNS 不能感知后端服务器的状态,只能根据配置策略进行负载均衡,无法做到更加灵活的负载均衡策略。比如说某台机器的配置比其他机器要好很多,理论上来说应该多分配一些请求给它,但 DNS 无法做到这一点。

    所以对于时延和故障敏感的业务,有实力的公司可能会尝试实现HTTP-DNS的功能,即使用HTTP 协议实现一个私有的 DNS 系统。HTTP-DNS 主要应用在通过 App 提供服务的业务上,因为在 App 端可以实现灵活的服务器访问策略,如果是 Web 业务,实现起来就比较麻烦一些,因为 URL 的解析是由浏览器来完成的,只有 Javascript 的访问可以像 App 那样实现比较灵活的控制。

    CDN

    CDN是为了解决用户网络访问时的“最后一公里”效应,本质是一种“以空间换时间”的加速策略,即将内容缓存在离用户最近的地方,用户访问的是缓存的内容,而不是站点实时访问的内容。

    由于CDN部署在网络运营商的机房,这些运营商又是终端用户的网络提供商,因此用户请求路由的第一跳就到达了CDN服务器,当CDN中存在浏览器请求的资源时,从CDN直接返回给浏览器,最短路径返回响应,加快用户访问速度。

    下面是简单的CDN请求流程示意图:

    CDN请求流程图

    CDN能够缓存的一般是静态资源,如图片、文件、CSS、Script脚本、静态网页等,但是这些文件访问频度很高,将其缓存在CDN可极大改善网页的打开速度。

    反向代理层

    我们把这一层叫反向代理层,也可以叫接入层、或者负载层。这一层是流量的入口,是系统抗并发很关键的一层。

    还是那个比喻,还是你打十个大汉,这次你叫了十九个兄弟,理想的情况是你们两个打对面一个,但是你由于太激动,冲在了最前面,结果瞬间被十个大汉暴打……

    反向代理会对流量进行分发,保证最终落到每个服务上的流量是服务能扛的范围之内。

    Nginx、LVS、F5

    DNS 用于实现地理级别的负载均衡,而 Nginx、 LVS、 F5 用于同一地点内机器级别的负载均衡。其中 Nginx 是软件的 7 层负载均衡,LVS 是内核的 4 层负载均衡,F5 是硬件的 4 层负载均衡。

    软件和硬件的区别就在于性能,硬件远远高于软件,Ngxin 的性能是万级,一般的 Linux 服务器上装个 Nginx 大概能到 5 万 / 秒;LVS 的性能是十万级,据说可达到 80万 / 秒;F5 性能是百万级,从 200 万 / 秒到 800 万 / 秒都有。

    硬件虽然性能高,但是单台硬件的成本也很高,一台最便宜的 F5 都是几十万,但是如果按照同等请求量级来计算成本的话,实际上硬件负载均衡设备可能会更便宜,例如假设每秒处理 100 万请求,用一台 F5 就够了,但用 Nginx, 可能要 20 台,这样折算下来用 F5 的成本反而低。因此通常情况下,如果性能要求不高,可以用软件负载均衡;如果性能要求很髙,推荐用硬件负载均衡。

    4 层和 7 层的区别就在于协议和灵活性。Nginx 支持 HTTP、 E-mail 协议,而 LVS 和 F5 是 4层负载均衡,和协议无关,几乎所有应用都可以做,例如聊天、数据库等。目前很多云服务商都已经提供了负载均衡的产品,例如阿里云的 SLB、UCIoud 的 ULB 等,中小公司直接购买即可。

    对于开发而言,一般只需要关注到Nginx这一层面就行了。

    Nginx负载均衡架构示意图

    负载均衡典型架构

    像上面提到的负载均衡机制,在使用中,可以组合使用。

    DNS负载均衡用于实现地理级别的负载均衡,硬件件负载均衡用于实现集群级别的负载均衡;软件负载均衡用于实现机器级别的负载均衡。

    三层负载均衡

    整个系统的负载均衡分为三层。

    • 地理级别负载均衡:www.xxx.com 部署在北京、广州、上海三个机房,当用户访问时,DNS 会根据用户的地理位置来决定返回哪个机房的 IP,图中返回了广州机房的 IP 地址,这样用户就访问到广州机房了。
    • 集群级别负载均衡:广州机房的负载均衡用的是 F5 设备,F5 收到用户请求后,进行集群级别的负载均衡,将用户请求发给 3 个本地集群中的一个,我们假设 F5 将用户请求发给了 “广州集群 2” 。
    • 机器级别的负载均衡:广州集群 2 的负载均衡用的是 Nginx, Nginx 收到用户请求后,将用户请求发送给集群里面的某台服务器,服务器处理用户的业务请求并返回业务响应。

    Nginx负载均衡

    我们主要关心是Nginx这一层的负载,通常LVS 和 F5这两层都是由网络运维工程师管控。

    Nginx负载均衡/反向代理示意图

    对于负载均衡我们主要关心的几个方面如下:

    • 上游服务器配置:使用 upstream server配置上游服务器

    • 负载均衡算法:配置多个上游服务器时的负载均衡机制。

    • 失败重试机制:配置当超时或上游服务器不存活时,是否需要重试其他上游服务器。

    • 服务器心跳检查:上游服务器的健康检查/心跳检查。

    upstream server中文直接翻译是上游服务器,意思就是负载均衡服务器设置,就是被nginx代理最后真实访问的服务器。

    负载均衡算法

    负载均衡算法数量较多,Nginx主要支持以下几种负载均衡算法:

    1、轮询(默认)

    每个请求按时间顺序逐一分配到不同的后端服务,如果后端某台服务器死机,自动剔除故障系统,使用户访问不受影响。

    2、weight(轮询权值)

    weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。或者仅仅为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。

    3、ip_hash

    每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题。

    4、fair

    比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间 来分配请求,响应时间短的优先分配。Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream_fair模块。

    5、url_hash

    按访问的URL的哈希结果来分配请求,使每个URL定向到一台后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身不支持url_hash,如果需要这种调度算法,则必须安装Nginx的hash软件包。

    失败重试

    Nginx关于失败重试主要有两部分配置,upstream server 和 proxy_pass。

    通过配置上游服务器的 max_fails和 fail_timeout,来指定每个上游服务器,当fail_timeout时间内失败了max_fail次请求,则认为该上游服务器不可用/不存活,然后将会摘掉该上游服务器,fail_timeout时间后会再次将该服务器加入到存活上游服务器列表进行重试。

    健康检查

    Nginx 对上游服务器的健康检查默认采用的是惰性策略,Nginx 商业版提供了healthcheck 进 行 主 动 健 康 检 查 。当 然 也 可 以 集 成 nginx_upstream_check_module( https://github.com/yaoweibin/nginx_upstream_check module ) 模块来进行主动健康检查。

    nginx_upstream_check_module 支持 TCP 心跳和 HTTP 心跳来实现健康检查。

    流量控制

    流量分发

    流量分发就不多说了,上面已经讲了,是接入层的基本功能。

    流量切换

    我听朋友说过一个有意思的事情,他们公司将流量从一个机房切到另一个机房,结果翻车,所有工程师运维平台一片飘红,全公司集体围观,运维团队就很丢面子。

    幸灾乐祸

    流量切换就是在某些情况下,比如机房故障、光纤被挖断、服务器故障故障情况,或者灰度发布、A/B等运维测试场景,需要将流量切到不同的机房、服务器等等。

    就像我们上面提到的负载均衡典型架构,不同层级的负载负责切换不同层级的流量。

    1. DNS:切换机房入口。
    2. HttpDNS:主要 APP 场景下,在客户端分配好流量入口,绕过运营商 LocalDNS并实现更精准流量调度。
    3. LVS/HaProxy:切换故障的 Nginx 接入层。
    4. Nginx:切换故障的应用层。

    另外,有些应用为了更方便切换,还可以在 Nginx 接入层做切换,通过 Nginx 进行一些流量切换,而没有通过如 LVS/HaProxy 做切换。

    限流

    限流是保证系统可用的一个重要手段,防止超负荷的流量直接打在服务上,限流算法主要有令牌桶、漏桶。

    漏桶和令牌桶

    可以在很多层面做限流,例如服务层网关限流、消息队列限流、Redis限流,这些主要是业务上的限流。

    这里我们主要讨论的是接入层的限流,直接在流量入口上限流。

    对于 Nginx接入层限流可以使用 Nginx自带的两个模块:连接数限流模块 ngx_http_limit_conn_module 和漏桶算法实现的请求限流模块 ngx_http_limit_req_moduleo

    还可以使用 OpenResty提供的 Lua限流模块 ua-resty-limit-traffic应对更复杂的限流场景。

    limmit_conn用来对某个 key 对应的总的网络连接数进行限流,可以按照如 IP、域名维度进行限流。limit_req用来对某个 key对应的请求的平均速率进行限流,有两种用法:平滑模式(delay ) 和允许突发模式(nodelay )。

    流量过滤

    很多时候,一个网站有很多流量是爬虫流量,或者直接是恶意的流量。

    可以在接入层,对请求的参数进行校验,如果参数校验不合法,则直接拒绝请求,或者把请求打到专门用来处理非法请求的服务。

    最简单的是使用Nginx,实际场景可能会使用OpenResty,对爬虫 user-agent 过滤和一些恶意IP (通过统计 IP 访问量来配置阈值),将它们分流到固定分组,这种情况会存在一定程度的误杀,因为公司的公网 IP —般情况下是同一个,大家使用同一个公网出口 IP 访问网站,因此,可以考虑 IP+Cookie 的方式,在用户浏览器种植标识用户身份的唯一 Cookie。访问服务前先种植 Cookie, 访问服务时验证该 Cookie, 如果没有或者不正确,则可以考虑分流到固定分组,或者提示输入验证码后访问。

    流量过滤

    降级

    降级也是保证高可用的一把利剑,降级的思路是“弃车保帅”,在眼看着不能保证全局可用的情况下,抛弃或者限制一些不重要的服务。

    降级一般分为多个层级,例如在应用层进行降级,通过配置中心设置降级的阈值,一旦达到阈值,根据不同的降级策略进行降级。

    也可以把降级开关前置到接入层,在接入层配置功能降级开发,然后根据情况行自动/人工降级。后端应用服务出问题时,通过接入层降级,可以避免无谓的流量再打到后端服务,从而给应用服务有足够的时间恢复服务。

    Web层

    经过一系列的负载均衡,用户终于请求到了web层的服务。web服务开发完成,经过部署,运行在web服务器中给用户提供服务。

    集群

    一般会根据业务模块,来划分不同的服务,一个服务部署多个实例组成集群。

    集群

    为了隔离故障,可以再将集群进行分组,这样一个分组出现问题,也不会影响其它分组。像比较常问的秒杀,通常会将秒杀的服务集群和普通的服务集群进行隔离。

    能做到集群化部署的三个要点是无状态、拆分、服务化。

    • 无状态:设计的应用是无状态的,那么应用比较容易进行水平扩展。
    • 拆分:设计初期可以不用拆分,但是后期访问量大的时候,就可以考虑按功能拆分系统。拆分的维度也比较灵活,根据实际情况来选择,例如根据系统维度、功能维度、读写维度、AOP 维度、模块维度等等。
    • 服务化:拆分更多的是设计,服务化是落地,服务化一般都得服务治理的问题。除了最基本的远程调用,还得考虑负载均衡、服务发现、服务隔离、服务限流、服务访问黑白名单等。甚至还有细节需要考虑,如超时时间、重试机制、服务路由、故障补偿等。

    Web服务器

    独立开发一个成熟的 Web 服务器,成本非常高,况且业界又有那么多成熟的开源 Web 服务器,所以互联网行业基本上都是 "拿来主义" ,挑选一个流行的开源服务器即可。大一点的公司,可能会在开源服务器的基础上,结合自己的业务特点做二次开发,例如淘宝的 Tengine,但一般公司基本上只需要将开源服务器摸透,优化一下参数,调整一下配置就差不多了。

    服务器的选择主要和开发语言相关,例如,Java 的有 Tomcat、JBoss、Resin 等,PHP/Python 的用 Nginx。

    Web服务器的性能之类的一般不会成为瓶颈,例如Java最流行的Web服务器Tomcat默认配置的最大请求数是 150,但是没有关系,集群部署就行了。

    容器

    容器是最近几年才开始火起来的,其中以 Docker 为代表,在 BAT 级别的公司已经有较多的应用。

    容器化可以说给运维带来了革命性的变化。Docker 启动快,几乎不占资源,随时启动和停止,基于Docker 打造自动化运维、智能化运维逐渐成为主流方式。

    容器化技术也天生适合当前流行的微服务,容器将微服务进程和应用程序隔离到更小的实例里,使用更少的资源,更快捷地部署。结合容器编排技术,可以更方便快速地搭建服务高可用集群。

    k8s集群

    服务层

    开发框架

    一般,互联网公司都会指定一个大的技术方向,然后使用统一的开发框架。例如,Java 相关的开发框架 SSH、SpringBoot, Ruby 的 Ruby on Rails, PHP 的 ThinkPHP, Python 的Django 等。

    框架的选择,有一个总的原则:优选成熟的框架,避免盲目追逐新技术!

    对于一般的螺丝工而言,所做的主要工作都是在这个开发框架之下。对于开发语言和框架的使用,一定要充分了解和利用语言和框架的特性。

    以Java为例,在作者的开发中,涉及到一个加密解密的服务调用,服务提供方利用了JNI的技术——简单说就是C语言编写代码,提供api供Java调用,弥补了Java相对没那么底层的劣势,大大提高了运算的速度。

    在服务开发这个日常工作的层面,可以做到这些事情来提高性能:

    • 并发处理,通过多线程将串行逻辑并行化。
    • 减少IO次数,比如数据库和缓存的批量读写、RPC的批量接口支持、或者通过冗余数据的方式干掉RPC调用。
    • 减少IO时的数据包大小,包括采用轻量级的通信协议、合适的数据结构、去掉接口中的多余字段、减少缓存key的大小、压缩缓存value等。
    • 程序逻辑优化,比如将大概率阻断执行流程的判断逻辑前置、For循环的计算逻辑优化,或者采用更高效的算法
    • 各种池化技术的使用和池大小的设置,包括HTTP请求池、线程池(考虑CPU密集型还是IO密集型设置核心参数)、数据库和Redis连接池等。
    • JVM优化,包括新生代和老年代的大小、GC算法的选择等,尽可能减少GC频率和耗时。
    • 锁选择,读多写少的场景用乐观锁,或者考虑通过分段锁的方式减少锁冲突。

    可以通过这些事情来提高可用性:

    • 设置合适的超时时间、重试次数及机制,必要时要及时降级,返回兜底数据等,防止把服务提方供打崩
    • 防重设计:通过防重key、防重表等方式实现防重
    • 幂等设计:在接口层面实现幂等设计

    服务中心

    当系统数量不多的时候,系统间的调用一般都是直接通过配置文件记录在各系统内部的,但当系统数量多了以后,这种方式就存在问题了。

    比如说总共有 10 个系统依赖 A 系统的 X 接口,A 系统实现了一个新接口 Y, 能够更好地提供原有 X 接口的功能,如果要让已有的 10 个系统都切换到 Y 接口,则这 10 个系统的几十上百台器的配置都要修改,然后重启,可想而知这个效率是很低的。

    服务中心的实现主要采用服务名字系统。

    • 服务务名字系统 (Service Name System)

    看到这个翻译,相信你会立刻联想到 DNS, 即 Domain Name System。没错,两者的性质是基本类似的。

    DNS 的作用将域名解析为 IP 地址,主要原因是我们记不住太多的数字 IP, 域名就容易记住。服务名字系统是为了将 Service 名称解析为 "host + port + 接口名称" ,但是和 DNS一样,真正发起请求的还是请求方。

    image-20210501154424095

    在微服务的架构下,实现这个功能的称之为注册中心,例如在Java语言体系下,开源的注册中心有Nacos、Ecuraka等。

    配置中心

    配置中心就是集中管理各个服务的配置。

    在服务不多的时候,各个服务各自管理自己的配置,没有问题,但是当服务成百上千,再各行其政,就是一个比较头疼的事。

    所以将配置中心抽象成公共的组件,集中配置多个系统,操作效率高。

    在微服务架构体系下,配置中心的开源方案有SpringCloud的SpringCloud Config、阿里的Nacos等。

    下一篇:没有了