本篇博客参阅:余春龙的《架构设计2.0》

什么是高牢靠体系?站在运用者的角度,高牢靠体系便是靠谱的体系,值得信任的体系,不简单呈现不可思议的问题,运用十分丝滑。本篇博客分为上下两部分,会从多个维度聊聊怎么构建一个高牢靠的体系。

过载维护:限流与熔断

体系上线前,咱们会对流量进行预算,恳求对应的服务器,为了不背锅,咱们往往会多恳求几台服务器或许恳求装备更高的服务器,即便这样,也没方法保证咱们的体系必定能接受所有的用户恳求。这个时分,体系就需要有过载维护:回绝一部分用户的恳求,确保大多数用户能够正常的运用体系。

过载维护有两种常用的方法:限流与熔断。

限流

限流,在生活中也十分常见,比方:

  • 进入火车站候车室需要安检,假如进入安检区域的人太多,那会变得一团糟,所以在人多的时分,管理人员会操控进入安检区域的人数,渐渐的将人放进去;
  • 去景点游玩,假如景点人太多,会严重影响用户体验,也不太安全,所以管理人员有时分会在景点外设置阻拦,操控入园人数,等有游客从景点出来了,再放一部分游客进入景点。

限流的两种维度

限流一般有两种约束维度:并发数,频率。

Nginx中的限流

Nginx就提供了约束并发数,频率这两种维度的模块:limit_conn(约束并发数),limit_req(约束频率)。 在秒杀体系中,有一个产品的库存是1000,现在有几万人抢购,那意味着大部分人都是没方法抢购到产品的,将所有用户恳求悉数放到后边的运用服务器,运用服务器再将所有用户恳求悉数放到后边的数据库,这是没有任何含义的,并且会形成运用服务器、数据库的压力激增,甚至或许将运用服务器、数据库打垮。根据这两点原因,能够在Nginx进行如下的限流:

  • 并发连接数的操控:只放2000个人进到后边的运用服务器,剩余的直接呼应“已售完”,那运用服务器、数据库的压力就会小的多;
  • 频率的操控:每个IP,每秒只能进行一次恳求,防止刷子流量。

RPC中的整体限流

某些根据TCP的RPC服务提供方大致工作原理如下图所示:

如何构建一个高可靠系统(上)
客户端向服务端建议恳求,服务端有一个监听线程、多个Work线程,监听线程将恳求暂存到Request缓冲区,Work线程从Request缓冲区取出Request,进行处理。这个Request缓冲区往往是有界的内存行列,为什么是有界的?

  • 有些Request很大,假如Request缓冲区是无界的,很简单撑爆内存;
  • 客户端往往会设置超时时刻(没有手动设置,也会有默许的),假如Request缓冲区是无界的,积压了太多的恳求,那后边的恳求即便处理了也没有任何含义,由于客户端现已超时,建议重试或许抛出反常了,还不如直接将Request进行丢掉。

Hystrix中的限流

大部分开发小伙伴,第一次听说限流,应该便是在Hystrix了,Hystrix默许运用线程阻隔模式,能够通过线程数+行列巨细进行限流。

限流的特殊用法

限流便是回绝一部分用户的恳求,确保大多数用户能够正常的运用体系,可是限流偶然会有特殊的用法,我在开发中,就特殊的运用过限流。 当时,我负责的体系是面向商家的,接收商家的下单恳求:

  • 假如某一时刻,没有触发限流,我接收到商家的下单恳求后,就及时处理下单恳求,及时呼应商家;
  • 假如某一时刻,触发了限流,我接收到商家的下单恳求后,就将恳求放入MQ或许放入音讯表中,后续渐渐处理,处理完结后,以站内告诉的方法或许邮件的方法奉告商家。

能够看到,即便触发了限流,也没有回绝用户的恳求,仍是能正常处理用户的恳求,这算是限流的特殊用法吧。

RateLimiter

RateLimiter是Guava库中的一个限流器,是根据令牌桶限流算法完结的,运用起来十分方便:

//创建一个限流器,每秒钟发生100个令牌
RateLimiter rateLimiter = RateLimiter.create(100);
//获取一个令牌,假如能够取得到,马上返回;不能取得到,则堵塞,等待新的令牌发生
rateLimiter.acquire(1);
//不论有没有取得到令牌,都马上返回,根据返回的布尔值,判别是否取得到令牌
rateLimiter.tryAcquire(1);

假如是我来完结这个限流器,必定会在后台启动一个线程,这个线程的使命便是每隔一段时刻,往“桶”里丢必定数量的令牌,可是神一般的Guava不是这么做的,它会记录最终一次取得令牌的时刻,拿令牌的时分,核算最终一次取得令牌的时刻与现在的时刻差,在这个时刻差内,能够取得多少令牌,然后进行令牌的添加与扣减。

中心限流

在大部分场景中,咱们运用的都是单机限流,可是单机限流有局限性,比方咱们的运用有3台服务器,咱们运用依靠的服务最多只能支撑100个并发,那咱们只需要操控每台服务器最多能够同时“流”下去30个恳求就能够了(30∗3=90<10030*3=90<100),可是假如有一天,咱们添加了服务器,却没有告诉依靠服务,那情况可就不妙了(30∗4=120>10030*4=120>100),有什么方法能够解决问题呢?能够运用中心限流。

咱们来看下,运用中心限流的处理流程:

如何构建一个高可靠系统(上)

  1. 客户端恳求服务端;
  2. 服务端恳求中心限流模块,询问是否能够处理此恳求;
  3. 中心限流模块呼应服务端,是否能够处理此恳求。

服务端处理完结后,或许还需要上报中心限流模块:恳求现已处理完结。当然也能够不上报,中心限流模块默许必定时刻后,恳求被服务端处理完结。

能够看出,运用中心限流有如下的弊端:

  • 单点问题:中心限流模块是单点的,假如中心限流模块呈现毛病,应该怎么办?当然,咱们能够选用ZooKeeper、ETCD等方法,使中心限流模块高可用,这就十分复杂了;
  • 性能问题:每个恳求都要先通过服务端,服务端再恳求中心限流模块,中心限流模块再呼应服务端,尽管都在内网,可是损耗也必定是有的;
  • 中心限流模块压力问题:众多服务端恳求中心限流模块,中心限流模块的压力剧增,中心限流模块是否能够扛得住?
  • 超出服务端接受能力问题:由于此计划中没有单机限流的存在,只是询问中心限流模块,当中心限流模块呼应“能够处理恳求”,或许关于服务端而言,现已超出了自身的接受能力。

那有没有方法解决这些问题呢?很惋惜,现在没有方法彻底解决这些问题,可是咱们能够利用单机限流+中心限流的方法最大程度的去缓解这些问题:

  • 当中心限流模块呈现毛病,就选用单机限流,这样就能够防止中心限流模块的单点问题;
  • 不用每次都去恳求中心限流模块,能够从中心限流模块恳求一批额度,当这批额度用完之后,再去恳求新的额度,这样就能够缓解中心限流模块的压力和中心限流模块带来的性能问题。

针关于第二点,咱们还能够再次进行优化:最开始,咱们并不需要恳求中心限流模块,能够彻底选用单机限流,当单机限流到达必定的阈值后,再去中心限流模块恳求一批额度。

“从中心限流模块恳求一批额度”这种方法很美好,但仍是存在问题:

  • 服务端的某台机器向中心限流模块恳求了最终一批额度后,服务端的其他机器就恳求不到额度了,就限流了,此刻只有恳求了最终一批额度的机器能够正常处理客户端恳求;
  • 服务端恳求的额度没有及时用完,应该怎么处理?

超时

只需发生了网络交互:比方Http恳求、发送音讯到MQ、RPC调用等等,都需要设置超时时刻,这是构建高牢靠体系最简略的,但也是最简单被忽视的一个点,在建议Http恳求、发送音讯到MQ、RPC调用的时分,请合理设置超时时刻。

客户端建议恳求,需要设置超时时刻,那是不是意味着服务端就没有超时时刻了,非也,有时分在服务端也有超时时刻的概念,并且这超时时刻也尤为重要,如下图所示:

如何构建一个高可靠系统(上)
小伙伴必定很熟悉这张图,在介绍限流的那一节,现已呈现过了,这里又呈现了,为了增强我们的记忆,我再介绍下工作流程:客户端向服务端建议恳求,服务端有一个监听线程、多个Work线程,监听线程将恳求暂存到Request缓冲区,Work线程从Request缓冲区取出Request,进行处理。这个Request缓冲区往往是有界的内存行列。 那超时体现在哪里呢?Work线程从Request缓冲区取出Request,能够先查看下这个Request是否现已超时了,假如现已超时了,那Work线程处理这个恳求是没有任何含义的,还不如直接丢掉。 在这种情况之下,客户端和服务端或许都会有一个超时时刻,此刻就需要满意公式:

客户端超时时刻>=服务端超时时刻

假如客户端的超时时刻设置为3秒,服务端的超时时刻设置为5秒,那服务端多出来的2秒超时时刻是没有任何含义的:服务端辛辛苦苦处理完结恳求后,客户端现已超时,建议重试或许抛出反常了。

重试

进行网络交互,网络有时会发生抖动,服务端或许也有或许正在GC,没有及时处理客户端恳求,这个时分,客户端恳求就会失利,咱们能够进行重试,可是有一个前提:服务端有必要幂等。 重试也并非是无脑的,会有两种常见的战略:

  • 延迟战略:延迟必定的时刻再建议重试,甚至能够逐步提高延迟时刻,比方第一次重试距离50ms,第2次重试距离100ms,第三次重试距离200ms;
  • 退避战略:向某台服务器建议恳求失利,建议重试就避开这台服务器,向其他服务器建议重试。

重试的时分,还有一个小的优化点:客户端设置的总超时时刻(包括重试)是3秒,服务端处理恳求需要2秒,由于网络原因,客户端第一次恳求通过1.5s后失利了,那还要必要进行重试吗?

本篇博客主要介绍了构建一个高牢靠体系的三个要素:限流、超时、重试,下一篇博客将会介绍阻隔、降级、熔断、监控、灰度、告警。