原文: 服务发现——需求与模式
date: 2019-04-19 15:10:49
[TOC]
前言
从自成一体的单体应用到分布式应用, 演进出了面向服务架构
服务发现的本质在于让服务之间发现彼此, 这是服务提供方与服务消费方完成消费的前提
在微服务架构下, 无论是使用Dubbo, Thrift, gRPC这类标准的PRC实现进行通讯, 还是微服务所倡导的RESTful风格API, 服务发现都是一个避不开的话题
需求
来举例一个简单的场景:
在固定数量, 固定地址的情况下, Consumer当然可以将服务提供者的地址写死到自己的应用内, 按照随机或轮询的策略去访问服务提供者
但在云时代, 微服务的架构下作为服务提供者的实例不一定运行在传统的物理机/虚拟机上, 网络地址动态变化, 实例数量还可能依据访问流量进行动态伸缩
其中有两个最核心的问题:
消费者怎么找到服务提供者? ——服务发现
在找到多个提供者实例后, 选择哪个实例来消费服务? ——负载均衡
可见, 服务发现和负载均衡是分布式架构最根本的问题, 同样也应该作为微服务的基础框架/组件
方案
目前主要分为两类模式: 客户端发现和服务端发现, 也就是client-side discovery pattern and server-side discovery pattern
下面来介绍几种典型模式
- 集中式(传统)
- 客户端嵌入式(进程内)
- 主机独立进程
这几种模式都有业界较好的实践方案及开源组件
一: 集中式
常用的独立LB可以使用硬件: F5 或软件: Nginx 等
独立LB中需要手工配置服务列表的地址, 例如在Nginx中配置服务列表:
upstream service_provider {
server 192.168.100.100:8080...;
server 192.168.100.101:8080...;
}
软负载的特点是配置灵活且易于扩展, 还可以与硬件负载均衡器性能稳定, 负载能力强的特点结合使用.
例如F5下配多个Nginx, 对其进行负载均衡, 多个Nginx再对后台服务进行负载均衡
这种方式重点提供的是LB能力, 而"服务发现"通常是在F5或Nginx上配置来实现的, 其功能对客户端来说是透明的
这种模式相对简单, 在中小型公司中是很主流的方案
二: 客户端嵌入式
也可以称为进程内代理
这种模式下, 多了一个注册中心, 也是服务注册表的概念, 并且在客户端会集成一个类似SDK的东西:
- 对于服务提供者(Provider)来讲: 它需要注册到注册中心, 定期进行heartbeat检测
- 对于消费者(Consumer)来讲: 首先它能够从注册中心获取服务的实例, 也就是具备服务发现的能力
其次获取到服务列表后选择一个进行调用, 也就是具备负载均衡的能力
通常情况下, 整个过程可以做到自发现和自注册, 无需其它代理的介入
三: 主机独立进程式
这种模式类似于上面一种, 区别在于提供服务发现和负载均衡能力的组件没有嵌入每个客户端内, 而是放在了同一主机下, 可以理解为同一主机下的不同进程
四: Other
实际中除了上述三种模式外, 还有很多变种和折中方案, 例如集中式的代理结合服务注册中心实现服务的注册于发现
以注册中心Eureka Server为例, 可以提供UI/API对接发布系统或是手工注册, 以及定期的健康检查, 而无需在客户端集成Eureka Client, 集中式的Proxy需要同步注册中心中的服务地址
总结
可以看到, 以上三种最典型的方案中, 相同点都是把具备服务发现和负载均衡能力的组件交给一个类似Proxy的角色中
而区别在于该角色所处的位置, 放入客户端内还是客户端外, 这也对应了两种分类: 客户端发现与服务端发现, 客户端负载与服务端负载
通过以上几种方案的说明, 我们可以理清它的分类:
优劣性
每种方式都各有优略, 没有绝对, 主要从这几方面来对比
可用性 & 性能
集中式方案中如果LB Proxy角色挂了, 那么影响的是所有客户端, 由于这种单点问题, 为了保证HA软负载通常需要部署多个, 好在Nginx无状态可水平扩展
进程内的方案如果客户端/Proxy挂了只对自己产生影响, 而主机独立进程的方案相对折中, 影响面仅为同一主机下的客户端
从图中能看出集中式的方案在服务的调用上会多一跳, 有一定的性能开销
语言栈
相较其它两种, 嵌入式的客户端需要对针对客户端做集成和改造才能实现自注册和自发现, 说的难听点就是需要对服务有一定的侵入
并且这种改造需针对每种语言栈的服务定制
Netflix Eureka & Ribbon是该模式最典型的实现, 对于Java语言栈, 这一切都变得轻松.
微服务的异构旨在不同的数据库和语言栈, Eureka服务端提供了完善的REST API, 对于其它非Java语言栈只需实现自己语言对应的Eureka客户端程序, 就可以将非Java实现的服务纳入自己的服务治理体系
统一管控
从图中也能看出, 集中式方案可以做到集中式的访问控制, 而客户端进程内的模式则不易做到统一的管控, 主机独立进程的方式在两者之中
对比
相信通过以上三种典型模式的了解, 对于业界常见的一些服务发现组件对应哪种模式应该很清楚了, 例如:
客户端发现: Netflix Eureka, Apache Dubbo, Motan
服务端发现: Nginx, Zookeeper, Kubernetes
而独立于主机进程的代理更像是Service Mesh的风格
模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
集中式代理 | 运维简单, 集中治理, 语言栈无关 | 配置麻烦, 单点问题, 多一跳有性能开销 | 具备一定运维能力 |
客户端嵌入式代理 | 无单点问题, 性能较好 | 客户端复杂, 不易集中治理, 语言栈相关 | 语言栈统一 |
主机独立进程代理 | 以上两者折中 | 运维部署较复杂 | 具备一定运维能力 |