作者 | 张羽辰 (同昭)
引子——什么是服务发现
近日来,和很多来自传统行业、国企、政府的客户在沟通技术细节时,发现云原生所代表的技术曾经逐步成为大家的共识,从一个扑朔迷离的概念慢慢变成这些客户的下一个技术策略。天然,利用架构就会提到微服务,以及其中最重要的分布式合作的模式——服务发现。模式(pattern)是指在特定上下文中的解决方案,很适宜形容服务发现这个过程。不过绝对于 2016 年,当初咱们起码有十多种的形式能实现服务发现,这确实是个好时机来进行回顾和瞻望,最终帮忙咱们进行技术选型与确定演进方向。
微服务脱胎于 SOA 实践,外围是分布式,但单体利用中,模块之间的调用(比方让音讯服务给客户发送一条数据)是通过办法,而所收回的音讯是在同一块内存之中,咱们晓得这样的代价是十分小的,办法调用的老本可能是纳秒级别,咱们从未想过这样会有什么问题。然而在微服务的世界中,模块与模块别离部署在不同的中央,它们之间的束缚或者协定由办法签名转变为更高级的协定,比方 RESTful、PRC,在这种状况下,调用一个模块就须要通过网络, 咱们必须要晓得指标端的网络地址与端口,还须要晓得所裸露的协定,而后才可能编写代码比方应用 HttpClient 去进行调用,这个“晓得”的过程,往往被称为服务发现。
分布式的架构带来理解耦的成果,使得不同模块能够别离变动,不同的模块能够依据本身特点抉择编程语言、技术栈与数据库,能够依据负载抉择弹性与运行环境,使得零碎从传统的三层架构变成了一个个独立的、自治的服务,往往这些服务与业务畛域十分符合,比方订单服务并不会关怀如何发送邮件给客户,司机治理服务并不需要关注乘客的状态,这些服务应该是网状的,是通过组合来实现业务。解耦带来了响应变动的能力,能够让咱们大胆试错,咱们心愿启动一个服务的老本和编写一个模块的老本相似,同时编写服务、进行重构的老本也须要升高至于代码批改个别。在这种需要下,咱们也心愿服务之间的调用可能简略,最好能像办法调用一样简略。
然而 Armon(HashiCorp 的创始人)在他的技术分享中提到,实现分布式是没有收费午餐的,一旦你通过网络进行近程调用,那网络是否可达、提早与带宽、音讯的封装以及额定的客户端代码都是代价,在此基础上,有时候咱们还会有负载平衡、断路器、健康检查、受权验证、链路监控等需要,这些问题是之前不须要思考的。所以,咱们须要有“产品”来帮忙咱们解决这类问题,咱们能够先从 Eureka 开始回顾、整顿。
一个单体利用部署在多台服务器中,模块间通过办法间接调用。
分布式的状况下,模块之间的调用通过网络,兴许应用 HTTP 或者其余 RPC 协定。
Spring Cloud Eureka
从 Netflix OSS 倒退而来的 Spring Cloud 仍旧是目前最风行的实现微服务架构的形式,咱们很难形容 Spring Cloud 是什么,它是一些独立的应用程序、特定的依赖与注解、在应用层实现的一揽子的微服务解决方案。 因为是应用层解决方案,那就阐明了 Spring Cloud 很容易与运行环境解耦 ,尽管限定了编程语言为 Java 然而也能够承受,因为在互联网畛域 Java 占有相对的摆布位置,特地是在国内。所以服务发现 Eureka、断路器 Hystrix、网关 Zuul 与负载平衡 Ribbon 十分风行直至今日,再加上 Netflix 胜利的应用这些技术构建了一个宏大的分布式系统,这些成功经验使得 Spring Cloud 一度是微服务的代表。
对于 Eureka 来说,咱们晓得不论是 Eureka Server 还是 Client 端都存在大量的缓存以及 TTL 机制,因为 Eureka 并不偏向于维持零碎中服务状态的一致性,尽管咱们的 Client 在注册服务时,Server 会尝试将其同步至其余 Server,然而并不能保障一致性。同时,Client 的下线或者某个节点的断网也是须要有 timeout 来管制是否移除,并不是实时的同步给所有 Server 与 Client。确实,通过“最大致力的复制(best effort replication)”能够让整个模型变得简略与高可用,咱们在进行 A -> B 的调用时,服务 A 只有读取一个 B 的地址,就能够进行 RESTful 申请,如果 B 的这个地址下线或不可达,则有 Hystrix 之类的机制让咱们疾速失败。
对于 Netflix 来说,这样的模型是十分正当的,首先服务与 node 的关系绝对动态,一旦一个服务投入使用其应用的虚拟机(我记得大多是 AWS EC2)也确定下来,node 的 IP 地址与网络也是动态,所以很少会呈现频繁上线、下线的状况,即便在进行频繁迭代时,也是更新运行的 jar,而不会批改运行实例。国内很多实现也是相似的,在咱们参加的我的项目中,很多客户的架构图上总会清晰的表白:这几台机器是 xx 服务,那几台是 xx 服务,他们应用 Eureka 注册发现。第二,所有的实现都是 Java Code,高级语言尽管在效率上不如零碎级语言,然而易于表白与批改,使得 Netflix 可能放弃与云环境、IDC 的间隔,并且很多性能通过 annotation 退出,也能让代码批改的老本变低。
Eureka 的逻辑架构很分明的表白了 Eureka Client、Server 之间的关系,以及他们的 Remote Call 是调用的。
Eureka 的限度随着容器的风行被逐步的放大,咱们慢慢的发现 Eureka 在很多场景下并不能满足咱们的需要。首先对于弱一致性的需要使得咱们在进行弹性伸缩,或者蓝绿公布时就会呈现肯定的谬误,因为节点下线的音讯是须要工夫能力同步的。在容器时代,咱们心愿应用程序是无状态的,能够优雅的启动和终止,并且易于横向扩大。因为容器提供了很好的封装能力,至于外部的代码是 Java 还是 Golang 并不是调用者关怀的事件,这就带来了第二个问题,尽管应用 Java annotation 的形式方便使用,然而必须是 Java 语言而且须要一大堆 SDK,很多例如负载平衡的能力无奈做到过程之外。Eureka 会让零碎变得很简单,如果你有十几个微服务,每个微服务都有四五个节点,那保护这么多节点的地址就显得十分臃肿,对于调用者来说它只须要关注本人所依赖的服务。
Hashicorp Consul
Consul 作为继任者解决了很多问题,首先 Consul 应用了当初风行的 service mesh 模式,在一个“管制面”中提供了服务发现、配置管理与划分等能力,与 Netflix OSS 套件一样,任何的这些性能都是能够独立应用的,也能够组合在一起去构建咱们本人的 service mesh 实现。Service mesh 作为实现微服务架构的新模式,核心思想在于过程之外 out-of-process 的实现性能,也就是 sidecar,咱们能够通过 proxy 实现 interceptor 在不扭转代码的状况下注入某些性能,比方服务注册发现、比方日志记录、比方服务之间的授信。
Consul 的架构更为全面并简单,反对多 Data Center,应用了 GOSSIP 协定,有 Control Panel 提供 Mesh 能力,基本上解决为了 Eureka 的问题。
与 Eureka 不同,Consul 通过 Raft 协定提供了强一致性,反对各种类型的 health check,而且这些 health check 也是分布式的,也不须要应用大量的 SDK 来在代码中集成这些性能。因为 Consul 代理了流量,所以能够反对传输平安 TLS,在架构设计上 Consul 与 Istio 还是有所相似,然而确实还是有如下的有余:
- 没有提供 native 的形式去配置 circuit breaker,Netflix OSS suite 最大的劣势是,EurekaHystrixRibbon 可能提供残缺的分布式解决方案,特地是 Hystrix,可能提供“疾速失败”的能力,然而 Consul 的话,目前还没有提供原生的计划。
- 同样的,集成 Consul 也变得比拟麻烦,agent 的启动不是那么简略,特地是在 k8s 上咱们须要多级 sidecar 时,同时其提供的 ACL 配置也难以了解和应用。绝对于外部的实现,管控用的 GUI 界面也是大家吐槽比拟多的中央。
- 绝对于服务发现,其余 Consul 所提供的性能就显得不那么迷人了,比方 Key-Value 数据库以及多数据中心反对,当然我认为这也不是核心内容。
- 政治因素,尽管是开源产品,然而其公司也参加了对中国企业的制裁,所以在国内是无奈非法应用该产品的。
Alibaba Nacos
Nacos 曾经是目前我的项目中的首选,特地是那些急需 Eureka 替代品的场景下,当然这不是因为咱们无奈应用 Consul,更多的是因为 Nacos 曾经成为了稳固的云产品,你无需本人部署、运维、管控一个 Consul 或者别的机制,间接应用 Nacos 即可 。
而且 Nacos 代替 Eureka 基本上是一行代码的事件,某些时候客户并没有足够的估算和老本投入微服务的革新与降级,所以在进行微服务上云的过程中,Nacos 是目前的首选。绝对于 Consul 本人创造轮子的做法,Nacos 在协定的反对更全面,包含 Dubbo 与 gRPC,这对于宽泛应用 Dubbo 的国内企业是一个微小的劣势。
在这里笔者就不扩大 Nacos 的性能与外部实现了,Nacos 团队所做的科普、示例以及深度的文章都曾经足够多了,曾经所有的文档都能够在官网找到,代码也开源,有趣味的话请大家移步 Nacos 团队的博客:https://nacos.io/zh-cn/blog/index.html
SLB、Kubernetes Service 与 Istio
实际上,咱们方才提到的“服务发现”是“客户端的服务发现(client-side service discovery)”,假如订单零碎运行在四个节点上,每个节点有不同的 IP 地址,那咱们的调用者在发动 RPC 或者 HTTP 申请前,是必须要分明到底要调用个节点的,在 Eureka 的过程中,咱们会通过 Ribbon 应用轮询或者其余形式找到那个地址与端口,并且发动申请。
这个过程是十分间接的,作为调用者,我有所有可用服务的列表,所以我能够很灵便的决定我该调用谁,我能够简略的实现断路器。然而毛病的话也很分明,咱们必须依赖 SDK,如果是不同的编程语言或框架,咱们就必须要编写本人的实现。
像蜘蛛网一样的相互调用过程,并且每个服务都必须有 SDK 来实现客户端的服务发现,比方 IP3 这台机器,是由它来决定最终拜访 Service 2 的那个节点。同时,IP23 刚刚上线,然而还没有流量过去。
然而在逻辑架构上,这个零碎又非常简单,serivce 1 -> service 2 -> service 34。对于研发或者运维人员,你是心愿 order service 是这样形容:
https://internal.order-service.some-company.com:8443/ – online
还是,这样一大堆地址,并且不确定的状态?
http://192.168.20.19:8080 – online
http://192.168.20.20:8080 – online
http://192.168.20.21:8080 – offline
http://192.168.20.22:8080 – offline
事实上断路器所提供的疾速失败在客户端的服务发现中十分重要,然而这个性能并不完满,咱们想要的场景是调用的服务都是可用的,而不是等调用链路走到个节点后再疾速失败,而这时候另一个节点是能够提供服务的。
而且对于一个订单服务,在外来看它就应该是“一个服务”,它外部的几个节点是否可用并不是调用者须要关怀的,这些细节咱们并不想关怀 。
在微服务世界,咱们很心愿每个服务都是独立且残缺的,就像面向对象编程一样,细节应该被暗藏到模块外部。依照这种想法,服务端的服务发现(server-side serivce discovery)会更具备劣势,其实咱们对这种模式并不生疏,在应用 NGINX 进行负载平衡的代理时,咱们就在实际这种模式,一旦流量到了 proxy,由 proxy 决定下发至哪个节点,而 proxy 能够通过 healthcheck 来判断哪个节点是否衰弱。
逻辑上还是 serivce 1 -> service 2 -> service 34,然而 LB 或者 Service 帮忙咱们暗藏了细节,从 Service 1 看 Service 2,就只能看到一个服务,而不是一堆机器。
服务端服务发现确实有很多劣势,比方暗藏细节,让客户端无需关怀最终提供服务的节点,同时也打消了语言与框架的限度。毛病也很显著,每个服务都有这一层代理,而且如果你的平台不提供这样的能力的话,本人手动去部署与治理高可用的 proxy 组件,老本是微小的。然而这个缺点曾经有很好的应答,你能够应用阿里云的 SLB 实现,不管 client 应用 HTTP 还是 PRC 都能够通过 DNS 名称来拜访 SLB,甚至实现全链路 TLS 也非常简单,而 SLB 能够治理多个 ECS 实例,也反对实例的 health check 与弹性,这就像一个注册核心一样,每个实例的状态实际上保留在 SLB 之上。云平台自身就是利于管控和应用,退出更多的比方验证、限流等能力。
Kubernetes Service 也具备同样的能力,随着容器化的逐步成熟,在云原生的落地中 ACK 是必不可少的运行环境,那通过 Service 去综合治理一组服务的 pod 与之前提到的 SLB 的形式是统一的,当然绝对于平台绑定的 SLB + ECS 计划,k8s 的 service 更加凋谢与通明,也支持者企业进行混合云的落地。
作为 service mesh 目前最风行的产品,Istio 应用了 virtual service 与 destination rule 来解决了服务注册与发现的问题,virtual service 与其余 proxy 一样,都十分强调与客户端的解耦,除了咱们日常应用的轮询式的调用形式,virtual service 能够提供更灵便的流量管制,比方“20% 的流量去新版本”或者“来自某个地区的用户应用版本 2”,实现金丝雀公布也比较简单。绝对于 kubernetes serivce, virtual service 可管制的中央更多,比方通过 destination rule 可管制上游,也能够实现依据门路匹配抉择上游服务,也能够退出权重,重试策略等等。你同样能够通过 Istio 的能力实现服务间的传输平安,比方全链路的 TLS,也能够做到细粒度的服务受权,而这所有的一切都是不须要写入业务代码中的,只有进行一些配置就好。然而这也不是收费的,随着服务数量的回升,手动的治理这么多的 proxy 与 sidecar,没有自动化的报警和响应伎俩,都会造成效率的降落。
ZooKeeper 真的不适宜做注册发现吗?
在微服务刚刚开始风行的时候,很多企业在摸索的过程中开始应用 ZooKeeper 进行服务发现的实现,一方面是 ZooKeeper 的牢靠、简略、人造分布式的劣势能够说是间接的抉择,另一方面也是因为没有其余的机制让咱们模拟。上面这篇公布于 2014 年底的文章具体的阐明了为什么在服务发现中,应用 Eureka 会是一个更好的解决方案。
https://medium.com/knerd/eureka-why-you-shouldnt-use-zookeeper-for-service-discovery-4932c5c7e764
在 CAP 实践中,ZooKeeper 是面向 CP 的,在可用性(available)与一致性(consistent)中,ZooKeeper 抉择了一致性,这是因为 ZooKeeper 最开始用于进行分布式的系统管理与协调(coordination),比方管制大数据的集群或者 kafka 之类的,一致性在这类零碎中是红线。文章还提到了“如果咱们本人为 ZooKeeper 加上一种客户端缓存的能力,缓存了其余服务地址的话,这样就能缓解在集群不可用时,仍旧能够进行服务发现的能力,并且 Pinterest 与 Airbnb 都有相似的实现”,确实,看起来这样是修复了问题,然而在原理上和 Eureka 这种 AP 型的零碎就没有多少区别了,应用了 Cache 就必须要在一致性上进行斗争,必须要本人的实现能力缓存生效、无奈同步等问题。
应用 ZooKeeper 实现服务发现并没有什么问题,问题是使用者必须要想分明在这样一个分布式系统中,AP 还是 CP 是最终的指标,如果咱们的零碎是在激烈变动,面向终端消费者,然而又没有交易或者对一致性要求不高,那这种状况下 AP 是较为理想的抉择,如果是一个交易系统,一致性显然更重要。其实实现一个本人的服务发现并没有大多数人想的那么难,如果有一个 KV Store 去存储服务的状态,再加上注册、更新等机制,这也是很多服务注册与发现和配置管理常常做在一起的起因,剩下的事件就是 AP 与 CP 的抉择了,上面这篇文章是一个很好的例子,也提到了其余的服务发现,请查阅。
https://dzone.com/articles/zookeeper-for-microservice-registration-and-discov
一些思考
进行技术选型的压力是十分之大的,随着技术的演进、人员的更替,很多零碎逐步变成了无奈批改、无奈挪动的存在,作为技术负责人咱们在进行这件工作时应该更加留神,抉择某项技术时也须要思考本人是否累赘的起。Spring Cloud 提供的微服务计划在易用性上必定好于本人在 Kubernetes 上创造新的,然而咱们也放心它尾大不掉,所以在咱们当初接触的我的项目中,对 Spring Cloud 上的利用进行迁徙、重构还是能够累赘的起的,但我十分放心几年后,革新的老本就会变的十分高,最终导向重写的地步。
咱们将调用形式分为“同步”与“异步”两种状况,在异步调用时,应用 MQ 传输事件,或者应用 Kafka 进行 Pub / Sub,事实上,Event Driven 的零碎更有灵活性,也合乎 Domain 的关闭。
服务与服务之前的调用不仅仅是同步式的,别忘了在异步调用或者 pub-sub 的场景,咱们会应用中间件帮忙咱们解耦。尽管中间件(middleware)这个词很容易让人产生困惑,它并不能很好的形容它的性能,但起码在实现音讯队里、Event Bus、Stream 这种需要时,当初已有的产品曾经十分成熟,咱们已经应用 Serverless 实现了一个残缺的 web service,其中模块的相互调用就是通过事件。然而这并不是完满的,“如无必要,勿增实体”,退出了额定的零碎或者利用就得去运维与治理,就须要思考生效,思考 failure 策略,同时在这种场景下实现“exactly once”的指标更为简单,果然在分布式的世界中,真是没有一口饭是收费的。
参考
https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance
https://www.consul.io/docs/architecture
https://dzone.com/articles/zookeeper-for-microservice-registration-and-discov
https://medium.com/knerd/eureka-why-you-shouldnt-use-zookeeper-for-service-discovery-4932c5c7e764
https://istio.io/latest/docs/concepts/traffic-management/#why-use-virtual-services
https://microservices.io/patterns/server-side-discovery.html
作者简介
张羽辰(同昭) 阿里云交付专家,有着近十年研发教训,是一名软件工程师、架构师、咨询师,从 2016 年开始采纳容器化、微服务、Serverless 等技术进行云时代的利用开发。同时也关注在分布式应用中的平安治理问题,整顿《微服务平安手册》,对数据、利用、身份平安都有肯定的钻研。
“阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术畛域、聚焦云原生风行技术趋势、云原生大规模的落地实际,做最懂云原生开发者的公众号。”