日调1000亿,腾讯微服务平台的架构演进

1、微服务平台简介java



1. 微服务平台


图片
要搭建一套能稳定支持海量调用的微服务系统,须要先看看系统由哪些模块组成。如上图所示,从下往上看,不一样的用户 VPC 表明多租户,中间是服务注册发现的模块,顶部是应用管理模块和数据化运营模块,应用管理模块用来进行 CICD,包括了分发、部署以及配置管理等应用生命周期相关的功能。
数据化运营这个模块主要用于帮助业务进行分析,包括但不限于调用链、日志、metrics 等。
从系统架构上来讲,蓝框里的属于数据面,也就是常说的 data plane,是影响业务请求的核心链路;灰色区域内更多的偏向控制流,也就是 control plane ,帮助咱们更简单的使用好微服务。
数据面和控制面属于两种不一样的架构,面临的挑战也各不相同。数据面的挑战主要难点在于大流量、稳定性和高可用等,而控制面的更多则是业务复杂度。本文将围绕着整个数据面分享如何让微服务系统变得更稳定。
2. 海量调用的起始——微服务
2014 年云兴起以后,提出了云时代下的微服务概念:单一应用程序构成的小程序,本身拥有本身的逻辑与轻量化处理能力,服务以全自动方式部署,与其余的服务之间进行通讯,服务可使用不一样编程语言和数据库实现。
这个概念和早期 Dubbo 这种基于 Netty 框架构成的系统本质上没区别,只不过云兴起以后增长了一些自动部署和 docker 的能力,也作了更多的集成。

经过上图能够看到,咱们每一个微服务都内聚了本身的业务逻辑,容许访问不一样的数据库,以及经过 rest API 进行互相通讯。从模型来看有点像是蜂巢,也很像一张网。这里就会引伸出一个小问题,这么多的微服务,他们之间是如何进行调用的?
http client 自己咱们知道是经过 IP 和 port 来进行互相调用,早期单体应用还可以简单的进行配置。可是在微服务时代,特别是用了 K8s 和 docker,每次启动的 IP 也可能会变,这里该怎么办呢?其实能够经过服务注册发现模块进行机器实例的互相发现和调用。
mysql

3. 微服务之间如何互相发现


咱们来看图解释一下什么是服务注册发现。

图上能看到有两个微服务,ServiceA 和 ServiceB。在这里咱们定义 ServiceA 为服务调用方,ServiceB 为服务提供者。
前面也说到了,在单体应用时代,咱们都须要经过配置来指定须要访问的 IP,可是云时代下的微服务,IP 自己会变,因此须要有一个地方来记录这些 IP,那就是服务注册和发现的能力。
ServiceB 的每一个实例在启动时,会将自身的 IP 和 port 写入到服务发现模块的某个路径,而后 ServiceA 会从这个路径来拉取到想要访问的 ServiceB 的服务提供者的列表。这里就会拉取到三个实例节点,从中选择一个节点进行访问。
目前市面上已经有不少成熟的注册发现组件,像是 zookeeper,nacos,Consul 等。Consul 自己做为一个开箱即用,而且支持 http 请求,同时拥有丰富的文档和简单的 API 的系统被不少的中小型公司青睐和使用。
固然,Consul 和 Spring 的对接也很成熟,不少中小型公司,特别是比较新的公司不少都会选择 Consul 来做为服务注册发现。因此咱们选择基于 Consul 做为底层基础组件,在上面一点点的进行扩展,来搭建一套稳定的服务注册发现模块。
redis

2、突破原生开源架构sql



1. 原生Consul的能力与限制


图片
Consul 有开源版本和企业版本,对于开源版原本说,基本功能都齐全,但企业级能力却不提供。缺乏这些企业级的能力,对想用 Consul 来实现支持海量调用的微服务系统会有不足。
原生的Consul的服务发现 API 参数只有一个服务名,想要多租户或者带上 namespace,只能拼接在服务名上。可是这里须要强行依赖 SDK,对于 Consul 这样一个暴露 http 的通用服务,不能限定用户的语言,也不可能每一个语言去实现一套 SDK,咱们须要用其余方式来实现。
docker

2. 实现多租户

           图片
实现多租户的能力,须要在原有的 Consul-server 集群以前,加一层 Consul-access 层。对外暴露的 API 和原生的 Consul 彻底同样,但背后针对一些 API 能够进行了一些改造。
先简单看看经过加入 access 层,怎么来实现多租户的能力:
服务注册发现通常来讲分为三步
数据库

  1. 服务提供进行实例注册;
  2. 心跳上报;
  3. 服务消费者拉取服务提供者列表。


为了简化问题,咱们在这里不考虑异常状况和顺序状况。
针对这三步,咱们分别进行一些改造,用户侧使用原生的 Consul-SDK 进行服务注册,当 access 接收到注册请求后,会将该请求翻译成一个 KV 请求,而后存储到 Consul-server 上。
第一步,实现多租户的能力,服务发现第一步是服务提供者进行实例注册,接到注册请求时会将该请求翻译成key是/租户/service/serviceA/instance-id/data,value 是用户注册上来的节点信息的请求,每个 instance-id 对应的目录就表明着一个服务提供者。
并无采起原生的服务注册的 Consul 提供的服务注册,而是为了作治理能力分开成了 KV 进行存储。
第二步,心跳请求也会被 Consul-access 拦截,翻译成 KV 请求,存在 Consul 集群上。
第三步,是服务发现请求,该请求被 Consul-access 拦截后,首先会从前面所说的路径下拉取当前所有的实例列表,而后将对应实例的最后心跳上报时间取出,第二步存 KV 的地方将服务注册的实例信息和心跳数据进行合并。
合并是将最近 30 s 内没有心跳数据的节点状态置为 critical,30 s 内有心跳的节点状态置为 passing,而后返回给客户端,这样对于客户端来讲,和直接请求 Consul-server 行为保持一致。
但这种实现方式有个问题:用户若是使用原生的 API 进行服务注册的话,自己其实并不包含租户的信息,那 access 如何知道写入的路径呢?
3. 透明地生成租户信息


这种实现有一个问题,用户使用的原生 API 进行注册,原本不带有租户信息,Access 第一步就没法实现了,这块如何处理?
原生 Consul 的有一个参数叫 token,是个 String 字段,为了即兼容原生的 Consul请求,又可以获取咱们想要的信息,咱们在这里作了一些文章。
首先须要实现 “token” 模块,该模块提供 token 授予和验证,同时也提供根据 token 返回相应信息的能力。
用户首先申请 token 密钥,填写信息,如租户名等等,而后 token-server 模块会根据这些信息生成一个 token 密钥并返回给客户端。用户在使用原生 API 的时候,须要把该 token 带上。Consul-access 收到 token 后,会从 token-server 来换取对应的信息,这样对于用户而言作到了彻底透明。
Token-server 模块的存在,使得 access 层除了可以实现一些企业级的功能,好比多租户,也可使得整个 Consul 集群能够更加稳定,好比防止用户恶意的进行调用,来对 Consul-server 形成 ddos ***。
也能够针对不一样的业务模块或者用户能够进行不一样的治理手段,好比咱们能够根据用户的 token 等级来设定 Consul 的配额,或者能够在测试环境限制访问频率,或者说限制某些用户的某些操做权限。
引入了 token 模块和 access 层,还能够作很是多有意思的事情,这里是给你们发散思考的地方,若是大家来作的话,还会加入哪些能力呢?
咱们已经发现加入了 access 层从功能层面上可以让整个 Consul 集群可以更加稳定,有些有经验的工程师可能会想到,会不会由于多了一层,而致使性能变差呢?
下面咱们来看看,在加入 access 层后,咱们作了哪些性能优化。
编程

3、让服务发现更稳定小程序



1. 性能优化Ⅰ             图片 咱们先来看一下没有 Consul-access 这一层的状况:
假设有 A,B,C 三个微服务,服务 A 须要调用服务 B 和服务 C。
服务 A 有 100 个实例,每一个客户端实例都须要订阅服务 B 和服务 C,那么和 Consul 须要创建的长链接数为 2* 100=200 个链接。每一个微服务须要创建 2 个长链接,一共 100 个,因此须要 200 个。
再来看一下加了 Consul-access 层以后,每一个微服务须要创建两个长连接,100 个实例是 200 个,客户端到 access 的链接数仍是 100* 2=200 没有变化,可是 access 到 Consul-server 的链接却变成了 3* 2=6 个!
这里的 3 指的是 Consul-access 的台数,而 2 指的是须要订阅的服务数目,这里就是为何 B 和 C 两个能够作聚合的缘由。
由于对于 access 而言,不一样实例的相同订阅请求是能够合并的,好比实例 1-33 服务监听请求都发到了第一台 access,access 只须要发送一次 watch 请求到 Consul-server 上。当 Consul 集群的这个值出现变化后,会返回给 access,而 access 会从缓存中拉取出监听该服务的链接,而后依此将变化后的值推送回客户端。
这里可能有人会有疑问,就算 access 到 Consul-server 的链接数从 200 降为了 6,可是客户端到 access 层的链接仍是 200 啊,并且总数仍是 206,还多了 6 个,这里的区别在哪里呢?
Consul 自己是一个 CP 的系统,自身是基于 raft 来保证强一致的,即便请求链接发到 follower,也会一样的转发到 leader 上面。而 watch 类型的读请求(也就是上面说的订阅类型请求,须要长链接挂载的),在没有开启 stale-read 参数的状况下,也会被转发至 leader。
所以,leader 上挂载的长链接数会是整个集群的总体链接数,随着链接数的增多,每当数据有变化时,leader 须要一次遍历全部 watch 该路径的链接,将变动数据返回,会消耗大量的 CPU 和 IO。
好比说 100 个服务监听请求会 watch 在 Consul-server,当监听的微服务实例数变化时,Consul-server 就须要遍历 100 个链接。
虽然 Consul-access 层也须要作遍历链接这个操做,但 access 自己是无状态的,这是很是重要的一点。
一台 access 须要承载 100 个链接,3 台 access 的话,每台只须要负责 33 个链接,若是继续水平扩容,每台的负载能够更低。而每扩容一台 access,对 Consul-server 的集群压力很小,只会增长 watch 的服务数个请求。
相比之下 Consul-server 没有办法无限水平扩容,你扩的越多,反而对 leader 的压力越大,可是垂直扩容又是有上限的,不可能一直扩展下去,因此咱们经过增长 access 层来解决这个难题。
这是一个很是通用的架构思想,当底层系统有瓶颈没法水平扩容时,能够想办法把压力上提到一个能够水平扩展的层级,将压力转移出去,从而使整个系统变得更加稳定。好比数据库中间件和背后的 mysql。
有些想的比较远的同窗可能发现了,这样作只能大大缓解 Consul-server 的瓶颈,可是随着服务数的增多,仍是会出现瓶颈。

2. 性能优化后依然到了瓶颈怎么办?

           
解决方案很简单,Consul-access 是无状态能够水平扩容的,可是 Consul-server 集群有瓶颈,那么咱们能够以 Consul-server 集群为粒度进行水平扩容。
咱们仍是借助以前说的 token 模块,根据 token 返回的信息来进行断定,而后决定当前的用户请求应当转发到哪一个 server 集群,而后经过这种思路,让整个服务发现系统作到彻底水平扩容。
这种架构目前也被普遍采用,好比 shardingredis,水平分库中间件等。
缓存

3. 性能优化II

           
这里再讲一个小优化,在运行过程当中,发现 Consul 的 CPU 占用仍是比较高的,这里用 pprof 进行调用采样分析后,发现大量的 CPU 消耗都来自于一个请求。
经过排查,发现是 Spring-SDK 中会每 2s 定时发一个 watch 请求,在这里咱们作个转换,将 watchtimeout 2s 的请求在 access 侧转换成了 55s 转发给 Consul-server。须要注意的是,这里的转换和原来没有区别,也是会在 55s 有变化的时候返回,可是大大下降了 CPU。上面两张 CPU 负载图是在运行了 3 天后的结果。
比起分享具体的某一次优化是怎么作的,更但愿能给你们一些启发,某些看起来可能很难的事情,好比这里的 CPU 下降 60% 负载,但你去尝试优化了,可能发现仍是比较容易的,可是成本收益很高,相比起某些架构上或者代码上的细节,一些点的修改可能会极大的影响稳定性。
性能优化

4. CP系统如何作到高可用


讲了如何从功能纬度和性能纬度让整个服务发现系统更稳定,下面是如何让服务发现变得高可用。           图片           Consul 是一个 CP 系统,根据 CAP 定理,CP 系统是无法作到高可用的,因此咱们只能尽量的在别的环节来增强,弥补一下 CP 系统的可用性。里我会从客户端、SDK 和 access 层来分享如何尽可能让整个系统更高可用。
为何是在 SDK 和 access 层作加强呢,实际上是由于 Consul-server 对咱们来讲是黑盒,咱们不对他进行改造,由于不一样于 access 层,修改 Consul 源码超出了大部分中小型公司的范围,在此咱们不作定制化。
一样的,咱们仍是先分析没有 access 层的状况:
要搞清楚的一点是,服务注册发现三步骤中、注册、心跳和发现,出问题下分别会对系统形成什么样子的影响?
首先是注册,若是一个实例注册不上去,那么再有其余实例存在的状况下,对总体微服务是没有任何影响的。
而后是心跳,若是由于某个缘由,好比网络闪断,或者丢包,丢失了心跳,那么会致使该节点从服务注册列表中下线。若是实例数少的状况下,部分实例下线会致使流量不均匀甚至整个系统垮掉。
最后发现,若是由于异常,好比 Consul 重启后丢失了数据,或者好比网络缘由,服务提供者的实例没有注册上去,会致使拉取到的实例列表为 0,这里会直接形成服务不可用。
根据上面的分析,咱们能够发现,首先要增强的是服务发现:
Spring 原生的服务发现是每 30s 左右去拉取新的实例列表,这里其实还有个 bug,这个参数没法经过配置来指定。
因此第一步,咱们将定时拉取改成了 watch 机制。好处在于,某些实例若是已经反注册下线后,能够马上通知客户端更新列表,不然在最多 30s 的时间内,可能请求仍是会打到已经下线的机器上。
同时,增长了本地缓存,每次拉回来的服务列表,会存储在本地,这样若是机器 crash 或者由于某些缘由,Consul 集群不可用时,不至于致使整个微服务系统所有不可用。
最后增长零实例保护,指的是,若是从 Consul 拉取的列表为空时,不替换内存中的数据,也不刷新缓存。由于若是 Consul 集群不可用,或者冷启动,或者其余不可预知的场景时,拉取回空列表会形成巨大的影响。
第二步咱们要增强心跳上报的流程,心跳上报是 put 请求,因此这里须要设置 readtimeout,默认是 1min。
这里要注意的是,1min 多是两个心跳周期,若是客户端和 Consul 之间的网络抖动或者丢包,会直接形成 1 分钟内不可用,因此这里首先要设置 readtimeout,通常推荐 5s 左右。
同时须要配套增长重试机制,不然一次失败就会致使一个生命周期掉线,这里推荐 3 次,配合上刚刚超时的 5s,一共 15s,小于一个周期的 30s。           
说完 SDK 后,咱们再来看看 access 层,在计算机系统里,每增长一个中间层,解决一些问题的同时,也会带来一些问题,特别是可用性这里,引入中间层,必须作的更多,才能保证和没有这一层同样的可用性。
和 SD不同,access 对于客户端来讲就是服务端,因此要尽量的保证每一个请求的成功,因此咱们能够作一些通用性质的可用性加强建设:
第一,和 SDK 的同样,减小 timeout 和重试,可是因为 raft 的实现机制,咱们只须要重试最多 1/2+1 次就行。
第二,当出现某个极端场景,好比整个 Consul-server 集群不可用,咱们须要增长一个兜底集群。在某个集群整个不可用时,将流量转发到兜底集群,并作下记录,等服务发现等 get 类型请求时,须要知道从哪一个集群拉取合并数据。
第三,咱们还须要增长主动发现问题的能力,这里咱们增长集群探测的 agent,定时发送请求给每台 access 和每一个 Consul-server 集群,出了问题及时告警。
除了以上针对每一个请求的通用能力建设外,咱们还能够针对服务发现,作一些更多的加强。
首先若是真的出现内部错误,须要用 500 来代替空列表返回。这样无论是原生的 SDK 仍是刚刚通过咱们增强的 SDK,都不会替换内存里的列表,至少能够保证微服务系统继续运行。
而后,咱们须要加入零实例保护的机制,这里和 SDK 有些区别,指的是若是发现全部实例都不可用,则以最近一个不可用的实例节点的最后心跳时间做为基线,往前一个心跳周期做为时间范围,将这个时间段的实例状态置为 passing 返回给客户端。
这里有点拗口,咱们举个实际的例子,好比当前服务 B 有 10 个实例,其中 2 个在几个小时前就已经掉线了,还有 8 个在正常运行,此时,Consul 集群彻底不可用十分钟,因此心跳信息没法上报,当 Consul 集群恢复时,access 会发现最近的心跳是 Consul 集群不可用那个时间点,也就是 10min 前,可是因为已经超过了一个心跳周期 30s,因此这里全部的实例都不可用,返回给客户端的话,会形成很是剧烈的影响。
可是若是增长了零实例保护,则会在返回实例列表时,发现最后一次上报心跳的节点在十分钟前,同时往前推 30s 发现一共有 8 个节点是在这个时间段丢失心跳,因此会将这 8 个实例返回给客户端。
固然,这里须要有个界限,通常选择 Consul-server 集群可以修复的时间,好比 1~3 小时,再长的话可能脏数据几率会比较大。
服务注册发现的话题先说到这,再来回顾咱们经过增长 access 层和 token 模块,来实现了企业级能力,加强了功能性的稳定性。同时经过聚合链接以及多 Consul-server 集群模式,大大加强了整个 Consul 的性能稳定性,最后,经过一些高可用的加强,咱们增强了整个服务发现系统的可用性。
在咱们拥有一个稳定的服务发现系统以后,咱们进入下一个话题,如何让服务之间的调用变得更加稳定。

5. 微服务之间如何互相调用


图片
咱们先来看看一个基于 Springcloud 开发的微服务之间的调用流程:
当用户发起 API 调用时,首先会来到 feign 模块。在 Springcloud 中,feign 起到了一个承上启下的做用,他封装了 http 的调用,让用户能够像使用接口同样的方式发起 rest 调用,同时也是在这里配置了须要访问的微服务名称。
feign 带着须要访问的服务名和拼接好的 http 请求来到了 ribbon 模块,
ribbon 简单来讲就是一个负载均衡模块,若是给定的是 IP,则会直接像该IP发起调用,若是是服务名的话,会从服务注册发现模块中获取服务名对应的服务提供者列表,而后从中选择一台进行调用。
看起来整个调用过程很是简单,可是实际生产上,特别是流量较大的状况下,若是直接这么使用开源组件,而且使用默认配置的话,一旦某个环节出了一点问题,可能会直接致使一条线所有都崩溃。这也是不少研发遇到的问题。
那么若是在我不想改动业务代码的状况下,咱们在这里又能够作哪些措施来让系统变得更稳定?

6. 基于开源咱们还能多作些什么

           图片             
总体的调用图和上一幅没有太大变化,这里咱们增长了几个环节,下面来讲一下。
首先,在 feign 和 ribbon 之间,增长了 hystrix 和 fallback。也是分别对应了熔断和容错这两个能力。
熔断这里要说的一点是,熔断自己不是万能药,通常来讲,熔断只针对弱依赖,或者直白的说就是不那么重要的下游服务开启。
熔断这个能力是为了防止雪崩,所谓雪崩就是下游某个服务不可用时,调用该服务的服务调用方也会受到影响从而使得自身资源被吃光,也逐渐变得不可用。慢慢的整个微服务系统都开始变得瘫痪。
额外补充说明,在微服务体系或者说分布式系统里,服务半死不活远比服务整个宕掉难处理的多。
设想一下,假设某个下游服务彻底挂了,进程也不存在,此时若是调用者调用该服务,获得的是 connection refuesd 异常,异常会很是快返回,因此就时间上来讲和你调用成功没有太大区别,若是该服务是一个弱依赖服务,那影响更是微乎其微。
但若是下游服务不返回,上游调用者会一直阻塞在那里,随着请求的增多,会把线程池,链接池等资源都吃满,影响其余接口甚至致使整个都不可用。
那在这种场景下,咱们须要一种办法,让咱们达到和下游服务挂掉同样的快速失败,那就是熔断的做用。经过熔断模块,咱们能够防止整个微服务被某一个半死不活的微服务拖死。同时也提醒你们,要重视快速识别出半死不活的那些机器。
说回容错,容错的使用的场景不是很是的频繁,更可能是针对一些不那么重要的接口返回一些默认的数据,或者配和熔断等其余治理能力进行搭配使用。
当 ribbon 选定了实例以后,要正式发起调用的以前,能够添加一个重试和超时,加上重试和超时,若是真的可以配置好这几个参数,能将系统稳定性提高一大截的。
关于超时,不少人、甚至不少有必定经验的开发者都喜欢用默认的超时时间。通常来讲,默认的超时时间都是分钟以上,有的框架甚至默认是 0,也就是没有超时。
这种行为在生产上十分危险,可能有一台服务提供者假死在那里,因为超时时间设置为 10 分钟,同时因为 loadbalance 的缘由,使得线程慢慢积压起来,从而致使了自身系统的崩溃。
给出建议——绝对不要使用默认的超时,而且必须合理配置 timeout。超时通常来讲主要是 connect timeout 和 read timeout,connect timeout 是指创建链接的超时时间,这个比较简单,通常同机房 5s 差很少了。
Read timeout 也就是 socketTimeout 须要用户根据本身下游接口的复杂度来配置,好比响应通常在毫秒级别的,我推荐设置 3s 到 5s 就好了,对一些秒级的 5s 也行,对于比较重要的大查询,耗时十几 s 到几十 s 的我建议设置为 1min,同时开启熔断,以及最好可以使用异步线程去将这种大请求和业务请求分离。
若是你们以为这种须要根据每一个请求来自行设定 read timeout 的方式太过于麻烦,其实还有一种更方便的方式,就是自适应超时。在网关若是传入的时候能够设置一个最大的超时时间,每一个微服务都会将该时间传递下去,这样能够动态的设置当前请求的超时时间。
说完超时咱们再来看一下重试,ribbon 其实自带了一些配置参数,经常使用的是 MaxAutoRetries,MaxAutoRetriesNextServer,OkToRetryOnAllOperations,这几个参数具体的含义和计算公式官网都有,这里不详细介绍了。
想提醒你们的是,对于一些幂等的请求,配合上较短的 timeout,合理的设置 1 次到 2 次重试,会让你总体的微服务系统更加稳定。
上面的全部加强其实不须要用户修改业务逻辑,基本上都是配置和依赖,可是正确的配置和使用这些开源组件,可让你的整个系统变得更加稳定。下面看一些进阶场景。


4、稳定调用的学问



1. 更细粒度的降级

                      介绍了熔断,hystrix 是服务级别或者接口级别,但生产过程当中,都是个别实例出现问题,特别是某一个实例假死或者不返回。
针对这种场景,hyxtrix 的熔断配置不是很灵活,由于它是经过总体的错误率来进行熔断的,通常一个实例异常是没有办法处罚熔断。
若是不改代码,咱们有没有办法处理这种场景?咱们其实能够经过减小 timeout 搭配上重试 1 次绕过该错误,若是是一个幂等的服务的话,一个实例坏掉,其实对整个系统不会有太大影响。
但若是是一个非幂等的,或者 put 请求,这里该怎么办?有没有办法不让失败率升高到 33% ?
TSF 用 resilience4j 从新本身实现了一整套的熔断,并加入了实例级别熔断的粒度,用来解决上述场景。
先说一下选型,为何要用 resilience4j 来做为底座替换 hystrix?缘由很简单,首先 resilience4j 是官方推荐的替代 hystrix 的框架,也更轻巧灵活。
它将容错、熔断、舱壁等治理能力独立,让用户能够自行组合,通过压测,发现单个 resilience4j 熔断器实例的 qps 能够高达百万,对应用不会形成额外的负担,因此这里选择基于 resilience4j 来进行二次开发。
熔断的原理是在发送请求前,根据当前想要访问的微服务或者接口,获取到对应的熔断器状态。若是是进入 open 状态,则直接拒绝调用,而在调用请求后,会将成功,失败的结果更新至对应的熔断器,若是失败比例超过了阈值,则打开熔断器,若是用户想扩展一些 metrics,好比慢调用等,只要记录时间,而后传给 resilience4j 熔断器便可, resilience4j 默认支持慢调用熔断。
再看一下实例级别的熔断,实例级别熔断和上面两个维度在实现上有些不一样,它更多的是剔除有问题的节点。
具体的时机是在 ribbon从Consul 获取可用的服务列表后,会增长一步:断定当前访问的微服务有哪些节点是打开状态,而后须要将打开状态的节点从可用列表中剔除,而后再进行 loadbalance,这样就能够作到及时的将不可用节点剔除,大大下降失败率。

2. 如何升级应用

           
咱们已经介绍了如何让服务发现和运行时调用变得更稳定,那么接下去,看看怎么让服务可以平滑的下线和上线。
可能有人会以为奇怪,上下线能对系统稳定性形成什么影响?那咱们就先来看一下简单的下线和上线会遇到什么问题:
首先是下线,一个考虑的比较周到的微服务框架在下线前会发送反注册请求给 Consul,而后再下线。看起来彷佛没什么问题,可是从反注册发送到下线其实间隔很短。
这里若是用户用的是刚刚咱们增强过的 SDK,也就是将 30s 定时轮训改成 watch 的机制后,影响大概在秒级。
具体影响的时间为从注册推送到 Consul,以及 Consul 到变动后返回给 watch 的客户端,而后客户端执行替换和缓存文件写入的时间,基本上在几百 ms 到 1s 左右。假设咱们 qps 为 1000,服务实例列表为 10 台,那么大概会有 100 个请求失败。
若是用户用的是原生的 SpringSDK 的话,那么这里影响就比较大了,30 秒内会不停的有流量打过去,错误率会快速升高。
问题已经清楚,怎么解决?因为目前 k8S 是主流的容器编排的系统,因此介绍下如何利用 K8S 的一些特性,来作到优雅下线。
先明确这是由于反注册和 shutdown 间隔时间过短而致使的异常,若是咱们能作到,先反注册,而后过 40s 再下线,那即便使用的是原生的 SDK,也不会有错误,由于最大 30s 后会更新到新的实例列表。
知道方法后,来看看在不改代码的状况下怎么作到优雅下线:
首先能够利用 k8s 自带的 pre-stophook 能力,这个 hook 是在须要 stop 容器以前进行的一个操做。当 pre-stop hook 执行完毕后才正式执行销毁容器。在 pre-stophook 里面进行反注册,成功后 sleep 35 秒,而后再执行 shutdown。
这里有个细节,虽然 pre-stophook 里面进行了反注册,可是应用仍是会继续发心跳,因此须要在 Consul-access 层屏蔽掉发送了反注册请求实例节点的心跳数据。
咱们再接着来看看优雅发布,正常发布会状况是,k8s 有两个状态,分别是 liveness 和 readiness。
默认状况下,容器启动了就算是 live了,启动完成后就变成了 ready 了。但大多数的 Spring 应用其实启动仍是耗时间的,有的启动甚至须要好几分钟,若是 k8s 发布参数设置的很差,可能所有滚动更新后,全部的程序都还没初始化完毕,这样直接就致使整个服务全都不可用了。
咱们如何避免以上状况?简单的办法是评估本身应用启动的时间,在 k8s 滚动发布的间隔参数配置的长一点,大于你预估的启动时间就行。那有没有更简单更自动的办法呢?
经过 k8s 的 readinessprobe,而后在 readinessprobe 中咱们向 Consul 发送请求,查询该实例是否已经注册到 Consul 上,若是已经在 Consul 上了,则认为 ready。
由于通常来讲,注册到 Consul 都已是启动的最后一步了,经过这种方式,咱们就能够不作任何干预的进行优雅发布。
从整个优雅发布和优雅下线能够看到,只是利用了原生 k8s 的 probe 能力,在正确的时间点,与 Consul 一块儿配合,就能没有任何异常的让系统稳定的更新,但愿你们在本身的生产上也能用到这一点。
今天分享到此结束,感谢你们的观看!


5、Q&A



Q: hystrix 相对较重,技术选型的时候为什么选择了hystrix? A:  hystrix 确实比较重,特别像是线程池隔离,在线程较多的状况下,无论是上下文切换,仍是线程自己带来的影响都是比较大的,因此后续咱们在实现本身的熔断功能时使用了相对轻量级的 Resilience4j 来实现。若是你们有必定动手能力的话,我也推荐你们用 Resilience4j 来定制化开发。由于 hystrix 对于异步开发框架的新人来讲,改造体验和断点体验都不是很友善。     Q: 熔断屏蔽掉出错节点后,须要把对应机器下掉或者告警出来吗? A: 熔断实际上是一个临时解决问题的方案,不能说有熔断就不去作异常的告警和发现,熔断能够在最短期内帮助你快速下降错误异常率,可是真正要永久性的解决错误和异常率仍是须要经过比较完整的告警机制以及监控机制快速定位节点而且把它剔除掉。
由于熔断从 close 状态到 open 状态把出错的服务器熔断掉,可是这里面的参数实际上是有时间的,过了这段时间后,它会从 open 状态变为 halfopen 状态,而在 halfopen 状态再次回到 open 状态的时候,若是节点仍是有问题,就会大量的抛错。
综上,若是有能力的话,请务必要把告警和监控机制完善好。         Q:Consul 自己有权限,为何还开发 access 模块? A: 若是想对 Consul 作进一步的开发,能够回看本视频,Access 的功能不只仅是为了权限能力,它能够作很是多的事情,包括治理、限流、熔断和权限认证等,固然也包括须要增长性能优化,好比聚合链接。增长 Access 这一层从功能性到性能上均可以提高稳定性。
Q:java 除了 hystrix、sentinel 还有哪些熔断实现呢? A: 熔断自己其实不是一个很难实现的系统,若是你们感兴趣的话能够本身去查一下。无论是 sentinel 仍是 Resilience4j,真正核心的数据结构主要就是滑动窗口:sliding window,是用来统计单位时间内失败率的一个数据结构。固然无论是 sentinel 仍是 Resilience4j,它们的性能都很是高。
相关文章
相关标签/搜索