在引入注册中心后,是否可以高枕无忧呢?先来看下一下几个问题:
图中问题主要分为两类,对注册中心数据的信任问题以及节点频繁导致的广播风暴问题。
在传统的观念中,我们肯定会选择信任引入的第三方基础设施,比如 MySQL 、Redis ,这种数据层的中间件,我们肯定是要完全信任其中的数据的。但对于注册中心,信任推送数据的风险非常大。
注册中心完全故障的情况,是很常见的。在程序进程中缓存访问服务的节点,几乎是一件必然的事情,不能每个请求都去注册中心拿相应服务的注册信息。
所以只要在进程中缓存服务的节点,影响就会可控。但是,当注册中心完全故障的时候,服务注册功能是失效的,此时的扩容操作无法进行。如果在容器中,因为 Pod(一组容器的集合) 滚动升级的原因造成先启动新的Pod,一定要在程序启动注册失败时抛出异常,使程序无法启动,否则容器 IP 的变化也会导致服务的访问异常。
如果服务节点不是特别多,很难遇到这个问题,但随着微服务规模的增大,注册中心很有可能遇到瓶颈。一旦出现高负载,会使服务和注册中心之间的健康检查或保活出现问题,注册中心使节点异常下线,只推送部分节点数据到订阅的服务。
这个问题看似不严重,但一旦推送了过少的节点到服务,会导致主调服务打挂被调服务,长时间不能恢复,甚至会导致整个微服务集群雪崩。
如何解决这种问题呢?
可以在客户端的服务发现 SDK 中加入自我保护机制:一旦服务的节点数量下降超过一定阈值,就进入自我保护状态,放弃使用新推送过来的服务注册信息。
实际上网络连通性问题是比较容易发生的,往往出于安全考虑,各个部门之间可能会处在不同的 VPC(私有网络) ,但现实中又有互相访问的情况,一旦网络规则维护不好,很容易出现新添加的机器注册中心的网段可以访问,但是服务之间却无法访问的情况。在注册中心的使用场景中,网络故障是最优先考虑的问题,如果发生了分区故障,问题 2 描述的情况也会发生。
如何解决此类问题呢?
这个就要发挥负载均衡器模块的作用了:在负载均衡中可以加入被动健康检查(节点熔断)和主动健康检查来在客户端主动剔除失效的节点。
对于这个问题,比较好的做法就是采用 Service Mesh 数据面之一 Envoy:相比注册中心的数据,更信任本地数据,所以 Envoy 设计了 2×2 矩阵来决定节点是否应该路由。
发现状态 | 健康检查成功 | 健康检查失败 |
发现 | 路由 | 不要路由 |
未发现 | 路由 | 不要路由,删除 |
如上表所示,只有在健康检查失败和注册中心未发现的情况才会删除节点,只要健康检查成功,无论是否发现此节点,都会路由。
实际上采用了这种方式,前面的三个问题,都可以迎刃而解了。当然实现一个健壮的负载均衡器可没这么简单,还有很多边缘情况你需要考虑,具体内容将会在后面的负载均衡器中详细展开。
前面四个问题都解决了,第 5 个问题即便出现了,它对线上服务的影响也非常小。但如何避免问题 5 的发生呢?
实际上此问题也可能导致问题 2 的发生。大量广播事件的发生,挤占网络带宽,甚至会导致网络带宽占满,此时注册中心和服务间的健康检查或保活,都会因为带宽不足造成信息丢失,使注册中心推送错误的数据。
如何解决此类问题呢?
其实很简单,可以将事件消息合并推送。在 Istio 的 Pilot 的模块中,实现了一种合并机制,100ms 内有新的事件消息时,便会继续等待下一条,最多等待 1s,当然时间的参数是可以配置的,这里说的是默认参数。
虽然这个解决方案会影响事件通知的时效性,但相对于收益来说,它是一个非常好的解决方案,可想而知,如果进一步增加时效性,那么付出的研发成本和机器资源成本都将呈指数级增加,显然是得不偿失的。
实际上在 Service Mesh 方案中,服务节点发现的问题用传统的注册中心方案也是可以解决的,但如果涉及 Kubernetes 和 ECS 跨集群访问,最好还是支持 Envoy 定义的 xDS 协议中的 EDS 协议。EDS是 endpoint discovery service 的缩写,无论是 Istio,还是最新版本的 gRPC,都已经默认支持了 EDS 协议,可以说EDS 实际上已经是服务发现的规范了。
在 Service Mesh 方案中,因为大多是和 Kubernetes 集群结合的方案,所以你要特别注意发版或者自动扩缩容引起的节点 IP 变化的问题。节点的频繁变化,对注册中心的健壮性提出了更高的要求,这些问题我在本讲前半部分已经详细说过了,这里就不再赘述了。
除了用传统的注册中心组件外,Kubernetes 内部的发现机制在 Service Mesh 中也得到了广泛应用,例如 Istio通过监听 Kubernetes Pod 的变化,实现服务发现的功能,这样就不需要服务自身来做服务注册了。
那么 Service Mesh 中实现的注册发现功能,相比传统微服务有哪些优势呢?
sidecar 通过接受控制面下发的配置信息,进行服务注册。相对于服务自身注册,这样可以减少服务自身开发的工作量,同时也很容易做到注册的配置信息一致化。比如如果服务自己注册,其实很难控制服务注册的 metadata 信息,在 SDK 中很难约束和升级,比如运行环境、地域、健康检查方式等。
sidecar 代理还带来了可以随时更新 meta 信息的好处。在传统的 SDK 模式中,想要动态调整服务的权重、metadata 等信息的时候,需要重新发布版本,或者依靠配置中心的能力,但这些控制信息往往散落在各个服务中,不方便管理,在 Service Mesh 中只需要依靠控制面的能力,就可以轻松做到了。
像 Istio 的 pilot 模块,在 1.1 版本就支持了单控制面多集群的功能,通过 pilot 将多个注册中心的数据聚合,可以有效降低单一注册中心的读写压力,使注册中心更容易水平扩展。
比如在实践中,将多个 Consul 数据中心的数据通过 pilot 模块聚合,然后提供 xDS 协议,供服务发现使用,实现了虚拟机到 Kubernetes 环境的无缝迁移。
在注册中心中,有一种健康检查方式是注册中心主动 ping 服务的模式。实际上如果服务 IP 发生变化,又用了同样的 ping 接口时,健康检查会出现错误。而通过 sidecar 模式,当发现服务 ping 接口过来的流量时,进行服务名称的检测,通过 header 中增加服务名称与本地服务名称做校验的方式进行检测,可以有效避免这样的错误。