当前位置: 首页 > news >正文

高并发系统的高可用架构

13 微博系统设计:怎么应对热点事件的突发访问压力?

微博(microblog)是一种允许用户即时更新简短文本(比如140个字符),并可以公开发布的微型博客形式。今天我们就来开发一个面向全球用户、可以支持10亿级用户体量的微博系统,系统名称为“Weitter”。

我们知道,微博有一个重要特点就是部分明星大V拥有大量的粉丝。如果明星们发布一条比较有话题性的个人花边新闻,比如宣布结婚或者离婚,就会引起粉丝们大量的转发和评论,进而引起更大规模的用户阅读和传播。

这种突发的单一热点事件导致的高并发访问会给系统带来极大的负载压力,处理不当甚至会导致系统崩溃。而这种崩溃又会成为事件热点的一部分,进而引来更多的围观和传播。

因此,Weitter的技术挑战,一方面是微博这样类似的信息流系统架构是如何设计的,另一方面就是如何解决大V们的热点消息产生的突发高并发访问压力,保障系统的可用性。今天我们就来看看这样的系统架构该怎么设计。

需求分析

Weitter的核心功能只有三个:发微博,关注好友,刷微博。

图片

  1. 发微博:用户可以发表微博,内容包含不超过140个字的文本,可以包含图片和视频。
  2. 关注好友:用户可以关注其他用户。
  3. 刷微博:用户打开自己的微博主页,主页显示用户关注的好友最近发表的微博;用户向下滑动页面(或者点刷新按钮),主页将更新关注好友的最新微博,且最新的微博显示在最上方;主页一次显示20条微博,当用户滑动到主页底部后,继续向上滑动,会按照时间顺序,显示当前页面后续的20条微博。
  4. 此外,用户还可以收藏、转发、评论微博。
性能指标估算

系统按10亿用户设计,按20%日活估计,大约有2亿日活用户(DAU),其中每个日活用户每天发表一条微博,并且平均有500个关注者。

而对于发微博所需的存储空间,我们做如下估算。

  • 文本内容存储空间

遵循惯例,每条微博140个字,如果以UTF8编码存储汉字计算,则每条微博需要(140×3=420140\times3=420140×3=420)个字节的存储空间。除了汉字内容以外,每条微博还需要存储微博ID、用户ID、时间戳、经纬度等数据,按80个字节计算。那么每天新发表微博文本内容需要的存储空间为100GB。

(2亿×(420B+80B)=100GB/天2亿 \times (420B +80B) = 100GB/天2亿×(420B+80B)=100GB/)

  • 多媒体文件存储空间

除了140字文本内容,微博还可以包含图片和视频,按每5条微博包含一张图片,每10条微博包含一个视频估算,每张图片500KB,每个视频2MB,每天还需要60TB的多媒体文件存储空间。

(2亿÷5×500KB+2亿÷10×2MB=60TB/天2亿\div5\times500KB+2亿\div10\times2MB=60TB/天2亿÷5×500KB+2亿÷10×2MB=60TB/)

对于刷微博的访问并发量,我们做如下估算。

  • QPS

假设两亿日活用户每天浏览两次微博,每次向上滑动或者进入某个人的主页10次,每次显示20条微博,每天刷新微博次数40亿次,即40亿次微博查询接口调用,平均QPS大约5万。

(40亿÷(24×60×60)=46296/秒40亿\div(24\times60\times60)=46296/秒40亿÷24×60×60=46296/)

高峰期QPS按平均值2倍计算,所以系统需要满足10万QPS。

  • 网络带宽

10万QPS刷新请求,每次返回微博20条,那么每秒需访问200万条微博。按此前估计,每5条微博包含一张图片,每10条微博包含一个视频,需要的网络总带宽为4.8Tb/s。

((200万÷5×500KB+200万÷10×2MB)×8bit=4.8Tb/s(200万\div5\times500KB+200万\div10\times2MB)\times8bit=4.8Tb/s200÷5×500KB+200÷10×2MB×8bit=4.8Tb/s)

概要设计

在需求分析中我们可以看到,Weitter的业务逻辑比较简单,但是并发量数据量都比较大,所以,系统架构的核心就是解决高并发的问题,系统整体部署模型如下。

图片

这里包含了“Get请求”和“Post请求”两条链路,Get请求主要处理刷微博的操作,Post请求主要处理发微博的请求,这两种请求处理也有重合的部分,我们拆分着来看。

我们先来看看Get请求的部分。

图片

用户通过CDN访问Weitter的数据中心、图片以及视频等极耗带宽的请求,绝大部分可以被CDN缓存命中,也就是说,4.8Tb/s的带宽压力,90%以上可以通过CDN消化掉。

没有被CDN命中的请求,一部分是图片和视频请求,其余主要是用户刷新微博请求、查看用户信息请求等,这些请求到达数据中心的反向代理服务器。反向代理服务器检查本地缓存是否有请求需要的内容。如果有,就直接返回;如果没有,对于图片和视频文件,会通过分布式文件存储集群获取相关内容并返回。分布式文件存储集群中的图片和视频是用户发表微博的时候,上传上来的。

对于用户微博内容等请求,如果反向代理服务器没有缓存,就会通过负载均衡服务器到达应用服务器处理。应用服务器首先会从Redis缓存服务器中,检索当前用户关注的好友发表的最新微博,并构建一个结果页面返回。如果Redis中缓存的微博数据量不足,构造不出一个结果页面需要的20条微博,应用服务器会继续从MySQL分片数据库中查找数据。

以上处理流程主要是针对读(http get)请求,那如果是发表微博这样的写(http post)请求呢?我们再来看一下写请求部分的图。

图片

你会看到,客户端不需要通过CDN和反向代理,而是直接通过负载均衡服务器到达应用服务器。应用服务器一方面会将发表的微博写入Redis缓存集群,一方面写入分片数据库中。

在写入数据库的时候,如果直接写数据库,当有高并发的写请求突然到来,可能会导致数据库过载,进而引发系统崩溃。所以,数据库写操作,包括发表微博、关注好友、评论微博等,都写入到消息队列服务器,由消息队列的消费者程序从消息队列中按照一定的速度消费消息,并写入数据库中,保证数据库的负载压力不会突然增加。

详细设计

用户刷新微博的时候,如何能快速得到自己关注的好友的最新微博列表?10万QPS的并发量如何应对?如何避免数据库负载压力太大以及如何快速响应用户请求?详细设计将基于功能需求和概要设计,主要讨论这些问题。

微博的发表/订阅问题

Weitter用户关注好友后,如何快速得到所有好友的最新发表的微博内容,即发表/订阅问题,是微博的核心业务问题。

一种简单的办法就是“推模式”,即建一张用户订阅表,用户关注的好友发表微博后,立即在用户订阅中为该用户插入一条记录,记录用户id和好友发表的微博id。这样当用户刷新微博的时候,只需要从用户订阅表中按用户id查询所有订阅的微博,然后按时间顺序构建一个列表即可。也就是说,推模式是在用户发微博的时候推送给所有的关注者,如下图,用户发表了微博0,他的所有关注者的订阅表都插入微博0。

图片

推模式实现起来比较简单,但是推模式意味着,如果一个用户有大量的关注者,那么该用户每发表一条微博,就需要在订阅表中为每个关注者插入一条记录。而对于明星用户而言,可能会有几千万的关注者,明星用户发表一条微博,就会导致上千万次的数据库插入操作,直接导致系统崩溃。

所以,对于10亿级用户的微博系统而言,我们需要使用“拉模式”解决发表/订阅问题。也就是说,用户刷新微博的时候,根据其关注的好友列表,查询每个好友近期发表的微博,然后将所有微博按照时间顺序排序后构建一个列表。也就是说,拉模式是在用户刷微博的时候拉取他关注的所有好友的最新微博,如下图:

图片

拉模式极大降低了发表微博时写入数据的负载压力,但是却又急剧增加了刷微博时候读数据库的压力。因为对于用户关注的每个好友,都需要进行一次数据库查询。如果一个用户关注了大量好友,查询压力也是非常巨大的。

所以,首先需要限制用户关注的好友数,在Weitter中,普通用户关注上限是2000人,VIP用户关注上限是5000人。其次,需要尽量减少刷新时查询数据库的次数,也就是说,微博要尽量通过缓存读取。

但即使如此,你会发现每次刷新的查询压力还是太大,所以Weitter最终采用“推拉结合”的模式。也就是说,如果用户当前在线,那么就会使用推模式,系统会在缓存中为其创建一个好友最新发表微博列表,关注的好友如果有新发表微博,就立即将该微博插入列表的头部,当该用户刷新微博的时候,只需要将这个列表返回即可。

如果用户当前不在线,那么系统就会将该列表删除。当用户登录刷新的时候,用拉模式为其重新构建列表。

那么如何确定一个用户是否在线?一方面可以通过用户操作时间间隔来判断,另一方面也可以通过机器学习,预测用户的上线时间,利用系统空闲时间,提前为其构建最新微博列表。

缓存使用策略

通过前面的分析我们已经看到,Weitter是一个典型的高并发读操作的场景。10万QPS刷新请求,每个请求需要返回20条微博,如果全部到数据库中查询的话,数据库的QPS将达到200万,即使是使用分片的分布式数据库,这种压力也依然是无法承受的。所以,我们需要大量使用缓存以改善性能,提高吞吐能力。

但是缓存的空间是有限的,我们必定不能将所有数据都缓存起来。一般缓存使用的是LRU淘汰算法,即当缓存空间不足时,将最近最少使用的缓存数据删除,空出缓存空间存储新数据。

但是LRU算法并不适合微博的场景,因为在拉模式的情况下,当用户刷新微博的时候,我们需要确保其关注的好友最新发表的微博都能展示出来,如果其关注的某个好友较少有其他关注者,那么这个好友发表的微博就很可能会被LRU算法淘汰删除出缓存。对于这种情况,系统就不得不去数据库中进行查询。

而最关键的是,系统并不能知道哪些好友的数据通过读缓存就可以得到全部最新的微博,而哪些好友需要到数据库中查找。因此不得不全部到数据库中查找,这就失去了使用缓存的意义。

基于此,我们在Weitter中使用时间淘汰算法,**也就是将最近一定天数内发布的微博全部缓存起来,用户刷新微博的时候,只需要在缓存中进行查找。如果查找到的微博数满足一次返回的条数(20条),就直接返回给用户;如果缓存中的微博数不足,就再到数据库中查找。

最终,Weitter决定缓存7天内发表的全部微博,需要的缓存空间约700G。缓存的key为用户ID,value为用户最近7天发表的微博ID列表。而微博ID和微博内容分别作为key和value也缓存起来。

此外,对于特别热门的微博内容,比如某个明星的离婚微博,这种针对单个微博内容的高并发访问,由于访问压力都集中一个缓存key上,会给单台Redis服务器造成极大的负载压力。因此,微博还会启用本地缓存模式,即应用服务器在内存中缓存特别热门的微博内容,应用构建微博刷新页的时候,会优先检查微博ID对应的微博内容是否在本地缓存中。

Weitter最后确定的本地缓存策略是:针对拥有100万以上关注者的大V用户,缓存其48小时内发表的全部微博。

现在,我们来看一下Weitter整体的缓存架构。

图片

数据库分片策略

前面我们分析过,Weitter每天新增2亿条微博。也就是说,平均每秒钟需要写入2400条微博,高峰期每秒写入4600条微博。这样的写入压力,对于单机数据库而言是无法承受的。而且,每年新增700亿条微博记录,这也超出了单机数据库的存储能力。因此,Weitter的数据库需要采用分片部署的分布式数据库。分片的规则可以采用用户ID分片或者微博 ID分片。

如果按用户ID(的hash值)分片,那么一个用户发表的全部微博都会保存到一台数据库服务器上。这样做的好处是,当系统需要按用户查找其发表的微博的时候,只需要访问一台服务器就可以完成。

但是这样做也有缺点,对于一个明星大V用户,其数据访问会成热点,进而导致这台服务器负载压力太大。同样地,如果某个用户频繁发表微博,也会导致这台服务器数据增长过快。

要是按微博 ID(的hash值)分片,虽然可以避免上述按用户ID分片的热点聚集问题,但是当查找一个用户的所有微博时,需要访问所有的分片数据库服务器才能得到所需的数据,对数据库服务器集群的整体压力太大。

综合考虑,用户ID分片带来的热点问题,可以通过优化缓存来改善;而某个用户频繁发表微博的问题,可以通过设置每天发表微博数上限(每个用户每天最多发表50条微博)来解决。最终,Weitter采用按用户ID分片的策略。

小结

微博事实上是信息流应用产品中的一种,这类应用都以滚动的方式呈现内容,而内容则被放置在一个挨一个、外观相似的版块中。微信朋友圈、抖音、知乎、今日头条等,都是这类应用。因此这些应用也都需要面对微博这样的发表/订阅问题:如何为海量高并发用户快速构建页面内容

在实践中,信息流应用也大多采用文中提到的推拉结合模式,区别只是朋友圈像微博一样推拉好友发表的内容,而今日头条则推拉推荐算法计算出来的结果。同样地,这类应用为了加速响应时间,也大量使用CDN、反向代理、分布式缓存等缓存方案。所以,熟悉了Weitter的架构,就相当于掌握了信息流产品的架构。

思考题

面对微博的高并发访问压力,你还能想到哪些方案可以优化系统?

14 百科应用系统设计:机房被火烧了系统还能访问吗?

百科知识应用网站是互联网应用中一个重要的类别。很多人上网是为了获取知识,而互联网上的信息良莠并存,相对说来,百科知识应用网站能为普通人提供较为可信的信息。因此,百科知识网站虽然功能单一、设计简单,但是依然撑起了互联网的一片天空:维基百科是全球访问量TOP10的网站,百度百科是百度的核心产品之一。

我们准备开发一个供全球用户使用的百科知识应用系统,系统名称为“Wepedia”。

Wepedia的功能比较简单,只有编辑词条和搜索查看词条这两个核心功能。但是Wepedia的设计目标是支撑每日10亿次以上的访问压力。因此设计目标主要是简单、高效地支持高并发访问,以及面对全球用户时保证(7×247\times247×24)小时高可用。

概要设计

Wepedia的整体架构,也就是简化的部署模型如图。

图片

在梳理Wepedia整体逻辑之前,先说明下架构图中核心组件的作用。

图片

用户在Web端查看一个百科词条的时候,首先通过GeoDNS进行域名解析,得到离用户最近的数据中心所属的CDN服务器的IP地址。用户浏览器根据这个IP地址访问CDN服务器,如果CDN服务器上缓存有用户访问的词条内容,就直接返回给用户;如果没有,CDN会访问和自己在同一个区域的Wepedia的数据中心服务器。

准确地说,CDN访问的是Wepedia数据中心负载均衡服务器LVS的IP地址。请求到达LVS后,LVS会将该请求分发到某个Nginx服务器上。Nginx收到请求后,也查找自己服务器上是否有对应的词条内容,如果没有,就将请求发送给第二级LVS负载均衡服务器。

接着,第二级LVS将请求分发给某个Apache服务器,Apache会调用PHP程序处理该请求。PHP程序访问Redis服务器集群,确认是否有该词条的对象。如果有,就将该对象封装成HTML响应内容,返回给用户;如果没有,就访问MySQL数据库来查找该词条的数据内容。PHP程序一方面会将MySQL返回的数据构造成对象,然后封装成HTML返回用户,一方面会将该对象缓存到Redis。

如果用户的HTTP请求是一个图片,那么Nginx则会访问LightHttp服务器,获取图片内容。

因为Nginx缓存着词条内容,那么当词条编辑者修改了词条内容时,Nginx缓存的词条内容就会成为脏数据。解决这个问题通常有两种方案,一种是设置失效时间,到了失效时间,缓存内容自动失效,Nginx重新从Apache获取最新的内容。但是这种方案并不适合Wepedia的场景,因为词条内容不会经常被编辑,频繁失效没有意义,只是增加了系统负载压力;而且,在失效时间到期前,依然有脏数据的问题。

Wepedia为了解决Nginx缓存失效的问题,采用了另一种解决方案:失效通知。词条编辑者修改词条后,Invalidation notification模块就会通知所有Nginx服务器,该词条内容失效,进而从缓存中删除它。这样,当用户访问的时候,就不会得到脏数据了。

多数据中心架构

Wepedia在全球部署多个数据中心,可以就近为用户提供服务。因为即使是最快的光纤网络,从地球一端访问另一端的数据中心,在通信链路上的延迟就需要近150ms。

((地球周长4万KM÷2)÷光速30万KM/s×请求响应2次通信≈133ms(地球周长4万KM\div2)\div光速30万KM/s\times请求响应2次通信\approx133ms(地球周长4KM÷2÷光速30KM/s×请求响应2次通信133ms)

150ms是一个人类能够明显感知的卡顿时间。再加上服务器的处理时间,用户的响应等待时间可能会超过1秒钟,而页面加载时间超过1秒钟,用户就会明显不耐烦。多数据中心架构可以通过GeoDNS为用户选择最近的数据中心服务器,减少网络通信延迟,提升用户体验。

另一方面,多数据中心还具有容灾备份功能,如果因为天灾或者人祸导致某个数据中心机房不可用,那么用户还可以访问其他数据中心,保证Wepedia是可用的。

但是多数据中心需要解决数据一致性的问题:如果词条编辑者修改词条内容,只记录在距离自己最近的数据中心,那么这份数据就会和其他数据中心的不一致。所以,Wepedia需要在多个数据中心之间进行数据同步,用户不管访问哪个数据中心,看到的词条内容都应该是一样的。

Wepedia的多数据中心架构如图。

图片

Wepedia的多数据中心架构为一主多从架构,即一个主数据中心,多个从数据中心。如果用户请求是Get请求(读请求),那么请求就会在该数据中心处理。如果请求是Post请求(写请求),那么请求到达Nginx的时候,Nginx会判断自己是否为主数据中心,如果是,就直接在该数据中心处理请求;如果不是,Nginx会将该Post请求转发给主数据中心。

通过这种方式,主数据中心根据Post请求更新数据库后,再通过Canal组件将更新同步给其他所有从数据中心的MySQL,从而使所有数据中心的数据保持一致。同样,LightHttp中的图片数据也进行同步,开发LightHttp插件,将收到的图片,发送给所有从数据中心。

数据中心之间采用类似ZooKeeper的选主策略进行通信,如果主数据中心不可用,其他数据中心会重新选举一个主数据中心。而如果某个从数据中心失火了,用户请求域名解析到其他数据中心即可。

这种多数据中心架构虽然使词条编辑操作的时间变长,但是由于Wepedia的绝大多数请求都是Get请求(Get与Post请求比超过1000:1),因此对系统的整体影响并不很大。同时用一种简单、廉价的方式实现多数据中心的数据一致性,开发和运维成本都比较低。

详细设计

作为一个百科服务类网站,Wepedia 主要面临的挑战是:应对来自全球各地的巨量并发的词条查询请求。因此详细设计重点关注Wepedia的性能优化。

前端性能优化

前端是指应用服务器(也就是 PHP 服务器)之前的部分,包括 DNS 服务、 CDN 服务、反向代理服务、静态资源服务等。对 Wepedia 而言,80% 以上的用户请求可以通过前端服务返回,请求根本不会到达应用服务器,这也就使得网站最复杂、最有挑战的PHP应用服务端和存储端压力骤减。

Wepedia 前端架构的核心是反向代理服务器 Nginx 集群,大约需要部署数十台服务器。请求通过 LVS 负载均衡地分发到每台 Nginx 服务器,热点词条被缓存在这里,大量请求可直接返回响应,减轻应用负载压力。而Nginx 缓存 不能命中的请求,会再通过 LVS 发送到 Apache 应用服务器集群。

在反向代理 Nginx 之前,是 CDN 服务,它对于 Wepedia 性能优化功不可没。因为用户查询的词条大部分集中在比重很小的热点词条上,这些词条内容页面缓存在 CDN 服务器上,而 CDN 服务器又部署在离用户浏览器最近的地方,用户请求直接从 CDN 返回,响应速度非常快,这些请求甚至根本不会到达 Wepedia 数据中心的 Nginx 服务器,服务器压力减小,节省的资源可以更快地处理其他未被 CDN 缓存的请求。

Wepedia CDN 缓存的几条准则:

  1. 内容页面不包含动态信息,以免页面内容缓存很快失效或者包含过时信息。
  2. 每个内容页面有唯一的 REST 风格的 URL,以便 CDN 快速查找并避免重复缓存。
  3. 在 HTML 响应头写入缓存控制信息,通过应用控制内容是否缓存及缓存有效期等。
服务端性能优化

服务端主要是 PHP 服务器,这里是业务逻辑的核心部分,运行的模块都比较复杂笨重,需要消耗较多的资源,Wepedia 需要将最好的服务器部署在这里(和数据库配置一样的服务器),从硬件上改善性能。

除了硬件改善,Wepedia 还需要使用其他开源组件对应用层进行优化:

  1. 使用 APC,这是一个 PHP 字节码缓存模块,可以加速代码执行,减少资源消耗。
  2. 使用 Tex 进行文本格式化,特别是将科学公式内容转换成图片格式。
  3. 替换 PHP 的字符串查找函数 strtr(),使用更优化的算法重构。
存储端性能优化

包括缓存、存储、数据库等被应用服务器依赖的服务都可以归类为存储端服务。存储端服务通常是一些有状态的服务,即需要进行数据存储。这些服务大多建立在网络通信和磁盘操作基础上,是性能的瓶颈,也是性能优化的关键环节。

存储端优化最主要的手段是使用缓存,将热点数据缓存在分布式缓存系统的内存中,加速应用服务器的数据读操作速度,减轻存储和数据库服务器的负载。

Wepedia 的缓存使用策略如下:

  1. 热点特别集中的数据直接缓存到应用服务器的本地内存中,因为要占用应用服务器的内存且每台服务器都需要重复缓存这些数据,因此这些数据量很小,但是读取频率极高。
  2. 缓存数据的内容尽量是应用服务器可以直接使用的格式,比如 HTML 格式,以减少应用服务器从缓存中获取数据后解析构造数据的代价。
  3. 使用缓存服务器存储 session 对象。

作为存储核心数据资产的 MySQL 数据库,需要做如下优化:

  1. 使用较大的服务器内存。在 Wepedia 应用场景中,增加内存比增加其他资源更能改善 MySQL 性能。
  2. 使用 RAID5 磁盘阵列以加速磁盘访问。
  3. 使用MySQL 主主复制及主从复制,保证数据库写入高可用,并将读负载分散在多台服务器。

小结

高可用架构中的各种策略,基本上都是针对一个数据中心内的系统架构、针对服务器级别的软硬件故障而进行设计的。但如果整个数据中心都不可用,比如数据中心所在城市遭遇了地震,机房遭遇了火灾或者停电,不管我们架构的设计多么的高可用,应用依然是不可用的。

为了解决这个问题,同时也为了提高系统的处理能力、改善用户体验,很多大型互联网应用都采用了异地多活的多机房架构策略,也就是说将数据中心分布在多个不同地点的机房里,这些机房都可以对外提供服务。用户可以连接任何一个机房进行访问,这样每个机房都可以提供完整的系统服务,即使某一个机房不可使用,系统也不会宕机,依然保持可用。

思考题

词条编辑者修改词条的时候,可能会同时修改(新增)词条文本和图片。而数据从主数据中心同步到多个从数据中心的时候,数据库同步可能和图片同步时间不一致,导致用户查看词条的时候,图片无法加载或者图片和文本内容不一致。

如何解决这个问题?
附1:阿里巴巴在十几年前,也遇到数据和图片同步不一致的问题,后来解决这个问题的开发工程师晋升为阿里集团副总裁,欢迎有志于成为副总裁的同学思考下这个问题。
附2:阿里当年遇到并解决这个问题的系统:https://github.com/alibaba/otter
附3:阿里当年解决这个问题的工程师访谈:https://www.infoq.cn/article/pl-alibaba

15 限流器设计:如何避免超预期的高并发压力压垮系统?

在互联网高可用架构设计中,限流是一种经典的高可用架构模式。因为某些原因,大量用户突然访问我们的系统时,或者有黑客恶意用DoS(Denial of Service,拒绝服务)方式攻击我们的系统时,这种未曾预期的高并发访问对系统产生的负载压力可能会导致系统崩溃。

解决这种问题的一个主要手段就是限流,即拒绝部分访问请求,使访问负载压力降低到一个系统可以承受的程度。这样虽然有部分用户访问失败,但是整个系统依然是可用的,依然能对外提供服务,而不是因为负载压力太大而崩溃,导致所有用户都不能访问。

为此,我们准备开发一个限流器,产品名称为“Diana”。

需求分析

我们将Diana定位为一个限流器组件,即Diana的主要应用场景是部署在微服务网关或者其他HTTP服务器入口,以过滤器的方式对请求进行过滤,对超过限流规则的请求返回“服务不可用”HTTP响应。

Diana的限流规则可通过配置文件获取,并需要支持本地配置和远程配置两种方式,远程配置优先于本地配置。限流方式包括:

  • 全局限流:针对所有请求进行限流,即保证整个系统处理的请求总数满足限流配置。
  • 账号限流:针对账号进行限流,即对单个账号发送的请求进行限流。
  • 设备限流:针对设备进行限流,即对单个客户端设备发送的请求进行限流。
  • 资源限流:针对某个资源(即某个URL)进行限流,即保证访问该资源的请求总数满足限流配置。

并且Diana设计应遵循开闭原则,能够支持灵活的限流规则功能扩展,即未来在不修改现有代码和兼容现有配置文件的情况下,支持新的配置规则。

概要设计

Diana的设计目标是一个限流器组件,即Diana并不是一个独立的系统,不可以独立部署进行限流,而是部署在系统网关(或者其他HTTP服务器上),作为网关的一个组件进行限流,部署模型如下:

图片

用户请求(通过负载均衡服务器)到达网关服务器。网关服务器本质也是一个HTTP服务器,限流器是部署在网关中的一个过滤器(filter)组件,和网关中的签名校验过滤器、用户权限过滤器等配置在同一个过滤器责任链(Chain of Responsibility)上。限流器应该配置在整个过滤器责任链的前端,也就是说,如果请求超过了限流,请求不需要再进入其他过滤器,直接被限流器拒绝。

用户请求进入限流器后,根据限流策略,判断该请求是否已经超过限流,如果超过,限流器直接返回状态码为503(Too Many Requests)的响应;如果没有超过限流,请求继续向下处理(经过其他网关过滤器),并最终调用微服务完成处理。

限流器的策略可以在本地配置,也可以通过远程的配置中心服务器加载,即远程配置。远程配置优先于本地配置。

限流模式设计

请求是否超过限流,主要就是判断单位时间请求数量是否超过配置的请求限流数量。单位时间请求数量,可以本地记录,也可以远程记录。方便起见,本地记录称作本地限流,远程记录称作远程限流(也叫分布式限流)。

本地限流意味着,每个网关服务器需要根据本地记录的单位时间请求数量进行限流。假设限流配置为每秒限流50请求,如果该网关服务器本地记录的当前一秒内接受请求数量达到50,那么这一秒内的后续请求都返回503响应。如果整个系统部署了100台网关服务器,每个网关配置本地限流为每秒50,那么,整个系统每秒最多可以处理5000个请求。

远程限流意味着,所有网关共享同一个限流数量,每个网关服务器收到请求后,从远程服务器中获取单位时间内已处理请求数,如果超过限流,就返回503响应。也就是说,可能某个网关服务器一段时间内根本就没有请求到达,但是远程的已处理请求数已经达到了限流上限,那么这台网关服务器也必须拒绝请求。我们使用Redis作为记录单位时间请求数量的远程服务器。

高可用设计

为了保证配置中心服务器和Redis服务器宕机时,限流器组件的高可用。限流器应具有自动降级功能,即配置中心不可用,则使用本地配置;Redis服务器不可用,则降级为本地限流。

详细设计

常用的限流算法有4种,固定窗口(Window)限流算法,滑动窗口(Sliding Window)限流算法,漏桶(Leaky Bucket)限流算法,令牌桶(Token Bucket)限流算法。我们将详细讨论这四种算法的实现。

此外,限流器运行期需要通过配置文件获取对哪些URL路径进行限流;本地限流还是分布式限流;对用户限流还是对设备限流,还是对所有请求限流;限流的阈值是多少;阈值的时间单位是什么;具体使用哪种限流算法。因此,我们需要先看下配置文件的设计。

配置文件设计

Diana限流器使用YAML进行配置,配置文件举例如下:

Url:/
rules:- actor:deviceunit:secondrpu:10algo:TBscope:global- actor:allunit:secondrpu:50algo:Wscope:local

配置文件的配置项有7种,分别说明如下:

  1. Url记录限流的资源地址,”/“表示所有请求,配置文件中的路径可以互相包含,比如“/”包含“/sample”,限流器要先匹配“/”的限流规则,如果“/”的限流规则还没有触发(即访问”/“的流量,也就是单位时间所有的请求总和没有达到限流规则),则再匹配“/sample”。
  2. 每个Url可以配置多个规则rules,每个规则包括actor,unit,rpu,algo,scope
  3. actor为限流对象,可以是账号(actor),设备(device),全部(all)
  4. unit为限流时间单位,可以是秒(second),分(minute),时(hour),天(day)
  5. rpu为单位时间限流请求数(request per unit),即上面unit定义的单位时间内允许通过的请求数目,如unit为second,rpu为100,表示每秒允许通过100个请求,每秒超过100个请求就进行限流,返回503响应
  6. scope为rpu生效范围,可以是本地(local),也可以是全局(global),scope也决定了单位时间请求数量是记录在本地还是远程,local记录在本地,global记录在远程。
  7. algo限流算法,可以是window,sliding window,leaky bucket,token bucket 。

Diana支持配置4种限流算法,使用者可以根据自己的需求场景,为不同资源地址配置不同的限流算法,下面详细描述这四种算法实现。

固定窗口(Window)限流算法

固定窗口限流算法就是将配置文件中的时间单位unit作为一个时间窗口,每个窗口仅允许限制流量内的请求通过,如图。

图片

我们将时间轴切分成一个一个的限流窗口,每个限流窗口有一个窗口开始时间和一个窗口结束时间,窗口开始时,计数器清零,每进入一个请求,计数器就记录+1。如果请求数目超过rpu配置的限流请求数,就拒绝服务,返回503响应。当前限流窗口结束后,就进入下个限流窗口,计数器再次清零,重新开始。处理流程活动图如下。

图片

上图包括“初始化”和“处理流程”两个泳道。初始化的时候,设置“窗口计数器”和“当前窗口结束时间”两个变量。处理请求的时候,判断当前时间是否大于“当前窗口结束时间”,如果大于,那么重置“窗口计数器”和“当前窗口结束时间”两个变量;如果没有,窗口计数器+1,并判断计数器是否大于配置的限流请求数rpu,根据结果决定是否进行限流。

这里的“窗口计数器”可以本地记录,也可以远程记录,也就是配置中的local和global。固定窗口算法在配置文件中algo项可配置“window”或者缩写“W”。

固定窗口实现比较容易,但是如果使用这种限流算法,在一个限流时间单位内,通过的请求数可能是rpu的两倍,无法达到限流的目的,如下图。

图片

假设单位时间请求限流数rpu为100,在第一个限流窗口快要到结束时间的时候,突然进来100个请求,因为这个请求量在限流范围内,所以没有触发限流,请求全部通过。然后进入第二个限流窗口,限流计数器清零。这时又忽然进入100个请求,因为已经进入第二个限流窗口,所以也没触发限流。在短时间内,通过了200个请求,这样可能会给系统造成巨大的负载压力。

滑动窗口(Sliding Window)限流算法

改进固定窗口缺陷的方法是采用滑动窗口限流算法,如下图。

图片

滑动窗口就是将限流窗口内部切分成一些更小的时间片,然后在时间轴上滑动,每次滑动,滑过一个小时间片,就形成一个新的限流窗口,即滑动窗口。然后在这个滑动窗口内执行固定窗口算法即可。

滑动窗口可以避免固定窗口出现的放过两倍请求的问题,因为一个短时间内出现的所有请求必然在一个滑动窗口内,所以一定会被滑动窗口限流。

滑动窗口的算法实现基本和固定窗口一致,只要改动重置“窗口计数器”和“当前窗口结束时间”的逻辑就可以。固定窗口算法重置为窗口结束时间+1 unit 时间,滑动窗口算法重置为窗口结束时间+1个时间片。但是固定窗口算法重置后,窗口计数器为0,而滑动窗口需要将窗口计数器设置为当前窗口已经经过的时间片的请求总数,比如上图里,一个滑动窗口被分为5个时间片,滑动窗口2的浅蓝色部分就是已经经过了4个时间片。

滑动窗口算法在配置文件中algo项可配置“sliding window”或者缩写“SW”。

漏桶(Leaky Bucket)限流算法

漏桶限流算法是模拟水流过一个有漏洞的桶进而限流的思路,如图。

img

水龙头的水先流入漏桶,再通过漏桶底部的孔流出。如果流入的水量太大,底部的孔来不及流出,就会导致水桶太满溢出去。

限流器利用漏桶的这个原理设计漏桶限流算法,用户请求先流入到一个特定大小的漏桶中,系统以特定的速率从漏桶中获取请求并处理。如果用户请求超过限流,就会导致漏桶被请求数据填满,请求溢出,返回503响应。

所以漏桶算法不仅可以限流,当流量超过限制的时候会拒绝处理,直接返回503响应,还能控制请求的处理速度。

实践中,可以采用队列当做漏桶。如图。

图片

构建一个特定长度的队列queue作为漏桶,开始的时候,队列为空,用户请求到达后从队列尾部写入队列,而应用程序从队列头部以特定速率读取请求。当读取速度低于写入速度的时候,一段时间后,队列会被写满,这时候写入队列操作失败。写入失败的请求直接构造503响应返回。

但是使用队列这种方式,实际上是把请求处理异步化了(写入请求的线程和获取请求的线程不是同一个线程),并不适合我们目前同步网关的场景(如果使用前面设计过的Flower框架开发的异步网关就可以用这种队列方式)。

因此Diana实现漏桶限流算法并不使用消息队列,而是阻塞等待。根据限流配置文件计算每个请求之间的间隔时间,例如:限流每秒10个请求,那么每两个请求的间隔时间就必须>=100ms。用户请求到达限流器后,根据当前最近一个请求处理的时间和阻塞的请求线程数目,计算当前请求线程的sleep时间。每个请求线程的sleep时间不同,最后就可以实现每隔100ms唤醒一个请求线程去处理,从而达到漏桶限流的效果。

计算请求线程sleep时间的伪代码如下:

初始化 :
间隔时间 = 100ms;
阻塞线程数 = 0;
最近请求处理时间戳  = 0;long sleep时间(){//最近没有请求,不阻塞if((now - 最近请求处理时间戳) >= 间隔时间 and 阻塞线程数 <= 0){最近请求处理时间戳 = now;return 0; //不阻塞}//排队请求太多,漏桶溢出if(阻塞线程数 > 最大溢出线程数) {return MAX_TIME;//MAX_TIME表示阻塞时间无穷大,当前请求被限流}//请求在排队,阻塞等待阻塞线程数++;return 间隔时间 * 阻塞线程数 - (now - 最近请求处理时间戳) ;
}

请求线程sleep时间结束,继续执行的时候,修改阻塞线程数:

最近请求处理时间戳 = now;
阻塞线程数--;

注意,以上代码多线程并发执行,需要进行加锁操作。

使用漏桶限流算法,即使系统资源很空闲,多个请求同时到达时,漏桶也是慢慢地一个接一个地去处理请求,这其实并不符合人们的期望,因为这样就是在浪费计算资源。因此除非有特别的场景需求,否则不推荐使用该算法。

漏桶算法的algo配置项名称为“leaky bucket”或者“LB”。

令牌桶(Token Bucket)限流算法

令牌桶是另一种桶限流算法,模拟一个特定大小的桶,然后向桶中以特定的速度放入令牌(token),请求到达后,必须从桶中取出一个令牌才能继续处理。如果桶中已经没有令牌了,那么当前请求就被限流,返回503响应。如果桶中的令牌放满了,令牌桶也会溢出。

图片

上面的算法描述似乎需要有一个专门线程生成令牌,还需要一个数据结构模拟桶。实际上,令牌桶的实现,只需要在请求获取令牌的时候,通过时间计算,就可以算出令牌桶中的总令牌数。伪代码如下:

初始化 :
最近生成令牌时间戳 = 0;
总令牌数 = 0;
令牌生成时间间隔 = 100ms;boolean 获取令牌(){//令牌桶中有令牌,直接取令牌即可if(总令牌数 >= 1){总令牌数--;return true;}//令牌桶中没有令牌了,重算现在令牌桶中的总令牌数,可能算出的总令牌数依然为0总令牌数 = min(令牌数上限值,总令牌数 + (now - 最近生成令牌时间戳) / 令牌生成时间间隔);if(总令牌数 >= 1){总令牌数--;最近生成令牌时间戳 = now;//有令牌了,才能重设时间return true;}return false;
}

令牌桶限流算法综合效果比较好,能在最大程度利用系统资源处理请求的基础上,实现限流的目标,建议通常场景中优先使用该算法,Diana的缺省配置算法也是令牌桶。令牌桶算法的algo配置项名称为“token bucket”或“TB”。

小结

限流器是一个典型的技术中间件,使用者是应用系统开发工程师,他们在自己的应用系统中使用限流器,通过配置文件来实现满足自己业务场景的限流需求。这里隐含了一个问题:大家都是开发者,这些应用系统开发工程师为什么要用你开发的中间件?事实上,技术中间件天然会受到更多的挑剔,架构师在设计技术组件的时候要格外考虑易用性和扩展性,开发出来的技术中间件要能经得起同行的审视和挑战。

这篇设计文档中,包含了很多伪代码,这些伪代码是限流算法实现的核心逻辑。架构师一方面需要思考宏观的技术决策,一方面要思考微观的核心代码。这里两方面的能力支撑起架构师的技术影响力,既要能上得厅堂,在老板、客户等外部相关方面前侃侃而谈,保障自己和团队能掌控自己的技术方向;也要能下得厨房,搞定最有难度的代码实现,让团队成员相信跟着你混,没有迈不过去的技术坎。

思考题

滑动窗口算法中,如何管理时间片,以及如何计算滑动过程中的一个窗口内各个时间片的窗口计数器之和?用什么样的数据结构和算法比较合适?

16 高可用架构的十种武器:怎么度量系统的可用性?

互联网应用是面向一般大众的应用系统,他们可能会随时需要使用应用,那么应用就必须要保持随时可用,即所谓的(7×247\times247×24)小时可用。但是互联网应用又可能会遇到硬件故障、软件故障、黑客攻击等等各种不可用的场景。

业界通常用多少个9来说明互联网应用的可用性。比如说淘宝的可用性是4个9,就是说淘宝的服务99.99%可用。这句话的意思是,淘宝的服务要保证在所有的运行时间里只有0.01%不可用,那么一年就只有大概53分钟不可用。这个99.99%就叫做系统的可用性指标,这个值的计算公式是:(可用性指标=(1−年度不可用时间÷年度总时间)×100可用性指标=(1-年度不可用时间\div年度总时间)\times100%可用性指标=1年度不可用时间÷年度总时间)×100)

可用性指标反映系统的可用程度,也可以估算出年度不可用时间。我们熟悉的互联网产品(淘宝、百度、微信等)的可用性大多是4个9。

图片

不同的应用可用性可能会相差很大,主要差别就是在面对各种故障的时候,高可用设计做得是否足够好,我总结了一些高可用架构的技术方案,并称之为高可用架构的十种武器。

第一种武器:解耦

耦合度过高是软件设计的万恶之源,也是造成系统可用性问题的罪魁祸首。一个高度耦合的系统,牵一发而动全身,任何微小的改动都可能会导致意想不到的bug和系统崩溃。连最基本的功能维护都已经勉为其难,更不用奢谈什么高可用了。

历数软件技术进化史,就是一部软件开发解耦的历史。从汇编语言到面向过程的语言,再到面向对象的语言,编程语言的要素本身就越来越低耦合。各种编程框架的出现也几乎只有一个目标:使软件变得更加低耦合。Web应用容器使得HTTP协议处理与业务开发解耦,开发者不需要关注网络通信和协议处理,只需要关注请求和响应对象的逻辑处理即可。MVC框架进一步将视图逻辑与业务逻辑解耦,前后端工作进一步分离。

这里,我再介绍两种低耦合的设计原则。

组件的低耦合原则:无循环依赖原则,即技术组件之间不能循环依赖,不能A依赖B,B又依赖A;稳定依赖原则,即被依赖的组件尽量稳定,尽量少因为业务变化而变化;稳定抽象原则,即要想使组件稳定,组件就要更加抽象。

面向对象的低耦合原则:开闭原则,即对修改封闭、对扩展开放,对象可以扩展新功能,但是不能修改代码;依赖倒置原则,即高层对象不能依赖低层对象,而是要依赖抽象接口,而抽象接口属于高层;接口隔离原则,不要强迫使用者依赖它们不需要的方法,要用接口对方法进行隔离。

第二种武器:隔离

如果说解耦是逻辑上的分割,那么隔离就是物理上的分割。即将低耦合的组件进行独立部署,将不同组件在物理上隔离开来。每个组件有自己独立的代码仓库;每个组件可以独立发布,互不影响;每个组件有自己独立的容器进行部署,互不干扰。

所以,隔离就是分布式技术在业务上的应用,最常见的就是我们前面案例中也多次使用的微服务技术方案。微服务将一个复杂的大应用(单体架构系统)进行拆解,拆分成若干更细粒度的微服务,这些微服务之间互相依赖,实现原来大应用的功能逻辑。然后将这些微服务独立开发和发布,独立部署,微服务之间通过RPC(远程过程调用)进行依赖调用,就是微服务架构。

隔离使得系统间关系更加清晰,故障可以更加隔离开来,问题的发现与解决也更加快速,系统的可用性也更高。

不过,还要强调一下,隔离必须在低耦合的基础上进行才有意义。如果组件之间的耦合关系千头万绪、混乱不堪,隔离只会让这种混乱更雪上加霜。

第三种武器:异步

异步可以认为是在隔离的基础上进一步解耦,将物理上已经分割的组件之间的依赖关系进一步切断,使故障无法扩散,提高系统可用性。异步在架构上的实现手段主要是使用消息队列。

比如用户注册的场景。新用户提交注册请求后,需要给用户发送邮件,发送短信,保存数据库,还要将注册消息同步给其他产品等等。如果用微服务调用的方式,那么后续操作任何一个故障,都会导致业务处理失败,用户无法完成注册。

使用消息队列的异步架构,新用户注册消息发送给消息队列就立即返回,后续的操作通过消费消息来完成,即使某个操作发生故障也不会影响用户注册成功。如下图。

图片

第四种武器:备份

备份主要解决硬件故障下系统的可用性,即一个服务部署在多个服务器上,当某个服务器故障的时候,请求切换到其他服务器上继续处理,保证服务是可用的。所以,备份与失效转移(failover)总是成对出现的,共同构成一个高可用解决方案。

最常见的备份就是负载均衡,前面的课程中说过,负载均衡主要解决高性能问题。但是,多台服务器构成一个集群,这些服务器天然就是互相备份的关系,任何一台服务器失效,只需要将分发到这台服务器的请求分发给其他服务器即可,如下图

图片

由于应用服务器上只运行程序,不存储数据,所以请求切换到任何一台服务器,处理结果都是相同的。而对于存储数据的服务器,比如数据库,互相备份的服务器必须要互相同步数据,下图是MySQL主主备份的架构图。

图片

第五种武器:重试

远程服务可能会由于线程阻塞、垃圾回收或者网络抖动,而无法及时返回响应,调用者可以通过重试的方式修复单次调用的故障。

需要注意的是,重试是有风险的。比如一个转账操作,第一次请求转账后没有响应,也许仅仅是响应数据在网络中超时了,如果这个时候进行重试,那么可能会导致重复转账,反而造成重大问题。

所以,可以重试的服务必须是幂等的。所谓幂等,即服务重复调用和调用一次产生的结果是相同的。有些服务天然具有幂等性,比如将用户性别设置为男性,不管设置多少次,结果都一样。

第六种武器:熔断

重试主要解决偶发的因素导致的单次调用失败,但是如果某个服务器一直不稳定,甚至已经宕机,再请求这个服务器或者进行重试都没有意义了。所以为了保证系统整体的高可用,对于不稳定或者宕机的服务器需要进行熔断。

熔断的主要方式是使用断路器阻断对故障服务器的调用,断路器状态图如下。

图片

断路器有三种状态,关闭、打开、半开。断路器正常情况下是关闭状态,每次服务调用后都通知断路器。如果失败了,失败计数器就+1,如果超过开关阈值,断路器就打开,这个时候就不再请求这个服务了。过一段时间,达到断路器预设的时间窗口后,断路器进入半开状态,发送一个请求到该服务,如果服务调用成功,那么说明服务恢复,断路器进入关闭状态,即正常状态;如果服务调用失败,那么说明服务故障还没修复,断路器继续进入到打开状态,服务不可用。

第七种武器:补偿

前面几种方案都是故障发生时如何处理,而补偿则是故障发生后,如何弥补错误或者避免损失扩大。比如将处理失败的请求放入一个专门的补偿队列,等待失败原因消除后进行补偿,重新处理。

补偿最典型的使用场景是事务补偿。在一个分布式应用中,多个相关事务操作可能分布在不同的服务器上,如果某个服务器处理失败,那么整个事务就是不完整的。按照传统的事务处理思路,需要进行事务回滚,即将已经成功的操作也恢复到事务以前的状态,保证事务的一致性。

传统的事务回滚主要依赖数据库的特性,当事务失败的时候,数据库执行自己的undo日志,就可以将同一个事务的多条数据记录恢复到事务之初的状态。但是分布式服务没有undo日志,所以需要开发专门的事务补偿代码,当分布式事务失效的时候,调用事务补偿服务,将事务状态恢复如初。

第八种武器:限流

在高并发场景下,如果系统的访问量超过了系统的承受能力,可以通过限流对系统进行保护。限流是指对进入系统的用户请求进行流量限制,如果访问量超过了系统的最大处理能力,就会丢弃一部分用户请求,保证整个系统可用。这样虽然有一部分用户的请求被丢弃,但大部分用户还是可以访问系统的,总比整个系统崩溃,所有的用户都不可用要好。

我们在[第15篇]专门讨论过限流器的设计,这里不再赘述。

第九种武器:降级

降级是保护系统高可用的另一种手段。有一些系统功能是非核心的,但是也给系统产生了非常大的压力,比如电商系统中有确认收货这个功能,即便用户不确认收货,系统也会超时自动确认。

但实际上确认收货是一个非常重的操作,因为它会对数据库产生很大的压力:它要进行更改订单状态,完成支付确认,并进行评价等一系列操作。如果在系统高并发的时候去完成这些操作,那么会对系统雪上加霜,使系统的处理能力更加恶化。

解决办法就是在系统高并发的时候(例如淘宝双十一),将确认收货、评价这些非核心的功能关闭,也就是对系统进行降级,把宝贵的系统资源留下来,给正在购物的人,让他们去完成交易。

第十种武器:多活

多活,即异地多活,在多个地区建立数据中心,并都可以对用户提供服务,任何地区级的灾难都不会影响系统的可用。异地多活的架构案例我们已经在[第14讲]讨论过了。异地多活最极端的案例,是某应用准备将自己的服务器发射到太空,即使地球毁灭也能保证系统可用。

异地多活的架构需要考虑的重点是,用户请求如何分发到不同的机房去。这个主要可以在域名解析的时候完成,也就是用户进行域名解析的时候,会根据就近原则或者其他一些策略,完成用户请求的分发。另一个至关重要的技术点是,因为是多个机房都可以独立对外提供服务,所以也就意味着每个机房都要有完整的数据记录。用户在任何一个机房完成的数据操作,都必须同步传输给其他的机房,进行数据实时同步。

数据库实时同步最需要关注的就是数据冲突问题。同一条数据,同时在两个数据中心被修改了,该如何解决?某些容易引起数据冲突的服务采用类似MySQL的主主模式,也就是说多个机房在某个时刻是有一个主机房的,某些请求只能到达主机房才能被处理,其他的机房不处理这一类请求,以此来避免关键数据的冲突。

小结

除了以上的高可用架构方案,还有一些高可用的运维方案。

通过自动化测试减少系统的Bug。对于一个稳定运行的系统,每次变更发布可能只改动极小的一部分,如果只测试这一小部分的功能,那么潜在的其他可能引起故障的连带变更就会被忽视,进而可能引发大问题。但是如果全部都回归测试一遍,投入的测试成本又非常高。自动化测试可以实现自动化回归,对于那些没有变更的功能,自动发现是否有引入的Bug或预期之外的变更。

通过自动化监控尽早发现系统的故障。监控系统是技术团队的眼睛,没有监控的系统犹如盲人在崎岖的山路狂奔。所以,一个成熟的高可用系统中必定包含着完整的监控系统,实时监控各种技术指标和业务指标的变化。如果系统出现故障,超过设定的阈值就会引发监控系统报警,或者启动自动化故障修复服务。

通过预发布验证发现测试环境无法发现的Bug。系统在发布上线之前要经过各种测试,但是测试环境和线上运行环境还是会有很多不同。所以需要在线上集群中部署一台专门的预发布服务器,这台服务器访问的数据和资源完全是线上的,但是不会被用户访问到。开发人员发布代码的时候,先发布到这台预发布服务器,然后在这台服务器上做预发布验证,没有问题才会将代码发布到其他服务器上;如果有问题,也不会影响到用户访问,保证系统的高可用。

此外还可以通过灰度发布降低软件错误带来的影响。在一个大规模的应用集群中,每次只发布一小部分服务器,观察没有问题,再继续发布,保证即使程序有Bug,产生的影响也控制在较小的范围内。

思考题

你还能想到哪些文中没有提到的高可用方法?

http://www.dtcms.com/a/420228.html

相关文章:

  • 怎么自己开一个网站当当网网站开发计划和预算
  • 网站建设可行性报告范文网站建设 投资合作
  • 惠城网站建设有哪些做财经类新闻的网站
  • 有自己的网站做淘宝联盟号做吗房地产网站开发文档
  • 连云港公司企业网站建设网站创建人
  • C++ 并发编程最佳实践详解
  • 大型网站怎样做优化PHP国内优秀vi设计案例
  • 【论文精读】Few-Shot Object Detection with Attention-RPN and Multi-Relation Detector
  • 山东建设和城乡建设厅注册中心网站驾校视频网站模板
  • 莱芜住房和城乡建设厅网站专业网站建设哪家好
  • 时间轴网站模板仁怀哪儿做网站
  • 装修公司网站asp源码郴州做网站seo
  • 会网站开发想找兼职免费ppt模板下载简约
  • 网站运营与推广电子商务网站建设策略
  • 沈阳网站建设德泰诺谁有qq网站开发资源群
  • 北京公司网站建设定广州英铭网站建设
  • 网站建设详细步骤建建建设网站公司电话
  • 公司网站搭建费用wordpress积分 充值
  • 手机网站布局教程几分钟弄清楚php做网站
  • 网站建设与维护的试卷网站建设里面包含什么语言
  • 【实验报告】华东理工大学随机信号处理实验报告
  • 个人做网站 需要学什么只是陕西网站建设
  • 数据结构入门 (四):双向通行的“高速公路” —— 深入双向循环链表
  • 西安做网站公司玖佰网络如何在google上免费推广
  • 个人域名备案完成了 可以改网站内容吗wordpress个人展示网站6
  • MySQL中SELECT 语句如何 INTO OUTFILE 保存数据到文件
  • 防火墙技术、模型、发展趋势、局限性及安全体系相关分析
  • 上海网站建设公司推荐排名网站建设中英语
  • 电脑从做系统怎么找回以前登录的网站wordpress网站合并
  • 做网站排名多少钱有没有专门做儿童房的网站