Skip to content

Latest commit

 

History

History
111 lines (65 loc) · 10.7 KB

服务发现.md

File metadata and controls

111 lines (65 loc) · 10.7 KB

背景

为什么一定需要一个服务发现系统呢?服务启动的时候直接读取一个本地配置,然后通过远程配置系统动态推送下来不行吗?实际上,当服务节点规模较小时,该方案也行得通,但如果遇到以下的场景呢?

  • 在微服务的世界中,服务节点的扩缩容、服务版本的迭代是常态,服务消费端需要能够快速及时的感知到节点信息的变更(网络地址、节点数量)。
  • 当服务节点规模巨大时,节点的不可用也会变成常态,服务提供者要能够及时上报自己的健康状态,从而做到及时剔除不健康节点(或降低权重)。
  • 当服务部署在多个可用区时,需要将多个可用区的服务节点信息互相同步,当某个可用区的服务不可用时,服务消费者能够及时切换到其他可用区(通过负载均衡算法自动切换或手动紧急切换),从而做到多活- 和高可用。
  • 服务发现背后的存储应该是分布式的,这样当部分服务发现节点不可用的时候,也能提供基本的服务发现功能
  • 除了 ip、port 我们需要更多的信息,比如节点权重、路由标签信息等等。

模式

客户端发现模式

由客户端负责向服务发现系统(可以认为是一个数据库,存储了所有服务提供者的所有节点位置信息)询问某个服务提供者的所有实例的ip、port信息,并采用某种负载均衡策略,直接发起对服务实例的访问

这种模式去除了对中心化单点(API Gateway or Load Balancer)的依赖,可以避开单点造成的性能瓶颈与故障问题,同时由于负载均衡的逻辑在客户端,它可以根据自身的配置选择负载均衡算法,比如一致性Hash算法。不过这种模式也存在缺陷,由于客户端的负载均衡逻辑是分布式的,各自为政,没有全局统一视角,在某些情景下会因为客户端的高度竞争而导致后端服务提供者节点的负载不均衡。同时客户端的业务逻辑和服务发现的逻辑耦合在一起,不同的服务使用了不同的编程语言,那么就需要有不同语言的SDK,如果未来某天服务发现的逻辑变更了,也需要重新发布所有的客户端节点。

服务端发现模式

把原本客户端执行的服务列表拉取&负载均衡&熔断&故障转移这部分逻辑抽象变成一个专属的服务。不过跟传统的 load balancer 不大一样的地方是: 这个的 load balancer会跟服务发现系统密切的配合,实时订阅服务发现系统中服务提供者节点列表信息,扮演反向代理的角色,将请求分发到合适的 Endpoint。

这块的一个代表是kubernetes的服务发现解决方案:运行在每个Node节点的kube-proxy会实时的watch Services和 Endpoints对象。每个运行在Node节点的kube-proxy感知到Services和Endpoints的变化后,会在各自的Node节点设置相关的iptables或IPVS规则,方便后面用户通过Service的ClusterIP去访问该Service下的服务。当kube-proxy把需要的规则设置完成之后,用户便可以在集群内的Node或客户端Pod上通过ClusterIP经过iptables或IPVS设置的规则进行路由和转发,最终将客户端请求发送到真实的后端Pod。

这种模式对于客户端来说是透明的,所有细节都被隔离在 load balancer 跟服务发现系统之间, 因此也沒有前面跨语言等相关问题,更新相关逻辑也只要統一部署 load balancer & service registry 就足够了。

很明显,这种模式下服务的架构等于多了一层转发,延迟事件会增加;整个系统也多了一个故障点,整体系統的运维难度会提高;另外这个load balancer 也可能会成为性能瓶颈


CAP


健康检查模式

客户端心跳

  • 客户端每隔一定时间主动发送“心跳”的方式来向服务端表明自己的服务状态正常,心跳可以是 TCP 的形式,也可以是 HTTP 的形式。
  • 也可以通过维持客户端和服务端的一个 socket 长连接自己实现一个客户端心跳的方式。

但是客户端心跳中,长连接的维持和客户端的主动心跳都只是表明链路上的正常,不一定是服务状态正常。

服务端主动探测

  • 服务端调用服务发布者某个 HTTP 接口来完成健康检查。
  • 对于没有提供 HTTP 服务的 RPC 应用,服务端调用服务发布者的接口来完成健康检查。
  • 可以通过执行某个脚本的形式来进行综合检查。

服务端主动调用服务进行健康检查是一个较为准确的方式,返回结果成功表明服务状态确实正常。但是服务端主动探测也存在问题。服务注册中心主动调用 RPC 服务的某个接口无法做到通用性;在很多场景下服务注册中心到服务发布者的网络是不通的,服务端无法主动发起健康检查,那么往往需要在宿主机器上部署一个agent来代替服务端的接口探测,比如Consul的健康检查机制就是这么实现的。

消费端的订阅机制

  • Push推送:Push 的经典实现有两种,基于socket长连接的推送,典型的实现如 zookeeper;另一种为HTTP连接所使用的 Long Polling,这两种形式都保证了消息变更能够第一时间送达。但是基于 socket 长连接的推送和基于 HTTP 协议的 Long Polling 都会存在notify消息丢失的问题和代码实现复杂度过高的问题。

  • 定时轮询:比如eruka,客户端每隔一段时间(默认30秒)会去服务端拉取注册表信息,保证注册表是最新的,这样的基于http短链接的订阅模式实现起来是最简单、最通用的。但也很容易导致一个问题,就是服务节点信息会有30s的延迟,在这30s内有可能会有请求打到已下线的节点上去。

  • 推拉结合的方式:比如Consul,客户端和consul server之间会建立起一个最长30s的http长链接,如果期间有任何变更,则会立即推送,如果没有变更等到30s过后,客户端又会立即建立起新的连接,继续开始新的一轮订阅。这种模式的既吸收了http短链接方便通用的好处,又享受到消息即时推送的优势。

参考: 长轮询


服务的上线与下线

优雅上线

  1. 服务提供一个通用的Health check接口(比如spring boot actuator模块自带/actuator/health 接口,grpc也提供了health checking的标准模型),服务发现的sdk通过检查该接口来确定服务是否准备好接流,只有准备好节流才可将该节点注册上去。

  2. SDK也可以提供一个回调接口,服务一切都准备就绪后再调用这个接口通知sdk去注册。

优雅下线

服务发现SDK接收到系统发出的SigTerm或者SigInt信号后,需要先主动反注册本身的实例,此时如果服务框架提供了graceful shutdown能力,就可以直接调用该方法,此时会阻塞住直到当前的所有inflight请求都处理完成或者超时才真正退出(不通)(grpc server提供了直接graceful shutdown方法,spring web应用则可以通过java提供的ThreadPoolExecutor.awitTermination来实现此能力)。如果没有graceful shutdown的能力,则需要主动sleep一定时间以确保所有http、rpc请求都处理完成后再退出。


服务发现的容灾与高可用

服务端

  • 服务节点信息原本是分布式存储的,少数节点挂了,不会影响整体可用性。

  • 当大多数节点挂了的时候,如果是强一致的系统此时会进入只读不可写的模式(比如Zookeeper和开启了stale read的consul。如果是最终一致的系统,此时客户端 sdk会自动重试切换到正常节点上去,读和写都不受影响。

  • 当服务端所有的节点都挂了时候,此时需要服务端能够持久化存储之前注册的Provider节点信息,并在重启之后进入保护模式一段时间,在此期间先不剔除不健康的Provider节点(因为宕机过程中心跳没办法成功上报),否则可能会导致在一个ttl内大量Provider节点失效。

  • 网络闪断保护,监测到大面积出现服务提供者节点心跳没有上报,则自动进入保护模式,该模式下不会剔除因为心跳上报失败的服务提供者节点

客户端

  • 客户端SDK需要有不可用节点剔除能力,当服务端某个节点不可用的时候,能够立即切换到下一个节点尝试(切换的时候随机sleep 0-3s防止重试风暴打垮某个节点)。这里要注意客户端SDK每次请求的超时时间是否设置正确,我们发现部分服务发现官方SDK的默认超时时间过长,比如java的consul sdk中默认超时是10分钟,在生产实践中如果发生了网络闪断导致response包回不来就会导致sdk的心跳请求一致阻塞住,没办法进行下次的心跳上报,从而导致节点从注册中心中异常下线。

  • 当所有的服务端节点都不可用的时候,SDK能够使用内存中的缓存继续提供服务

  • 如果客户端重启了,内存中的数据不存在了,则走本地配置降级


服务注册的Metadata

服务注册的时候除了携带serviceName、ip、port这些信息就足够了呢?在一个大型为微服务系统中,服务支持的协议、服务的标签(比如Abtest、蓝绿发布的时候需要筛选这些tag作为服务路由信息)、服务的健康状态、服务的调度权重等信息可能都需要传递给消费者感知到。不过在生产实践中,一般不推荐将过多的信息放入注册中心,以免导致性能下降,比如swagger生成的api信息最好单独存储。

consul、zk、etcd

名称 优点 缺点 接口 一致性算法
zookeeper 1.功能强大,不仅仅只是服务发现 2.提供 watcher 机制能实时获取服务提供者的状态 3.dubbo 等框架支持 1.没有健康检查 2.需在服务中集成 sdk,复杂度高 3.不支持多数据中心 sdk Paxos
consul 1.简单易用,不需要集成 sdk 2.自带健康检查 3.支持多数据中心 4.提供 web 管理界面 1.不能实时获取服务信息的变化通知 http/dns Raft
etcd 1.简单易用,不需要集成 sdk 2.可配置性强 1.没有健康检查 2.需配合第三方工具一起完成服务发现 3.不支持多数据中心 http Raft