服务注册与调用

在前几节我们确定了服务的划分、通讯协议的选择及接口的设计等,那接下我们考虑这样一个问题:我们将车贷通拆分成了30个左右的服务,这些服务怎么调用呢?
最简单的方式是用类似Nginx的反向代理服务来充当轻量ESB实现Orchestration,由它来做服务路由。这的确是个不错的方案,对于一些小型系统也完全可以这么做,但这一方案存在一些不足导致其无法应用于中大型系统,比如我们前面提到ESB的性能问题,所有调用都到ESB本身的调用链路变长,ESB本身的压力也很大,再则这种方案对服务注册发现不友好,以Nginx为例,在其上只能静态配置服务路由并实现多实例负载均衡,对服务存活的感知及动态增删服务支持并不完善。
要有效地管理各个服务我们需要一个带有服务注册与监控、智能负载调用、高性能的注册与调度服务,而这也正是微服务最核心的技术之一。
要实现服务注册监控最简单的做法是让注册与调度服务定时ping各个服务进程是否存活,但这过于粗放,有很多情况是服务进程还在但服务僵死,所以真正有效的做法是要求各服务提供服务状态API,由各服务定时主动上报或注册调度服务定时采集的方式来确定服务是否正常。
注册与调度服务常见的几种形式如下:
中心化的注册与调度服务
服务的注册与调度完全交由中心化的注册调度中心完成,这其实就是上面Nginx的版本,它的优点是对各个服务本身没有侵入性,方便做服务Orchestration,问题上文也提到,每次调用都要走ESB,对性能一定影响。
对等网络,嵌入式注册与负载
各个服务中内嵌了服务注册与负载调度,所以各个实例都是对等的,优点在于服务自治,无需另外的支撑服务,服务调用直连,速度快,存在的问题是对服务有侵入,不纯粹,并且需要为不同语言开发完整的注册调度实现,在通用化上有所欠缺,另外服务数量多时不利于做统一的管控。知名微服务框架Vertx(默认基于Hazelcast实现集群能力,后文会有介绍)就支持这一模式。
独立注册服务,嵌入式负载
这一模式吸收了上两个模式的优点,它使用独立的、中心化的注册中心,而服务的调用则直接下放到各服务侧,实现点对点地调用。
所有服务启动后定时同步服务状态,注册中心拥有最新的全量路由表,各服务定时拉取(也可以是注册中心事件触发并推送的方式)与自己有关的路由表,这样在服务调用时就优先在本地寻址并实现本地化的负载策略,进而直接与对应的服务通信。
这种模式既保留了独立注册中心,方便管控,又让服务调用直连,兼顾了性能,所以诸如Spring Cloud、Dubbo等主流的微服务框架都采用这一模式。
在服务调用时我们需要有负载均衡的策略以决定在多实例情况下调用哪一个实例,常见的策略有:
  • 随机:随机选择一个服务实例,这种方式过于简单粗暴,一般不推荐使用
  • 轮询:将所有服务实例组成一个环,每次都调用上次调用节点的下一节点
  • 加权:这是更智能的策略,可以根据不同的加权项组合出很多策略,比如:
    • 响应时间权重:每次请求后都会统计对应实例的响应时间,下次请求时优先分配响应时间快的实例
    • 区域权重:服务会跨机架、机房甚至国家与地区,这一策略会优先分配请求给相近节点的实例
说到这,有读者会想到我们即要注册中心以方便管控又要避免嵌入式的调用负载对服务的侵入,那么是否可以实现类似下图的注册调用方式:
这里我们将调用负载从服务中独立出来,一个服务实例外挂一个对应的调用负载,所有注册调度相关的能力都由这个调用负载组件实现,这样我们的服务就很干净了。同时服务的实例与调用负载部署在同一节点中本身只有很少地网络开销,也可以兼顾到性能。
如果你有想到这个方案,那么恭喜你,“英雄所见略同”你的想法正是当下讨论最火热的Service Mesh的核心思路。Service Mesh试图去构建一种对服务最小化侵入的服务治理体系,也被广泛地认为是未来微服务发展的方向,关于此本书会有专门的章节来介绍,此处不再赘述。
注册中心有很多开源实现,比如主流的有:
  • Zookeeper,它是一个高可用的分布式服务协调器,本质上它维护了一颗分布式的节点树,节点有永久、临时、顺序三类,临时节点在客户端连接时创建,宕机或正常下线时删除,注册功能的核心正是基于此特性实现
  • Consul,与Zookeeper不同,Consul是专门为服务注册发现而生,不需要像Zookeeper需要定制开发,它开箱即用,同时有友好的UI,支持多数据中心等高级特性,它对Service Mesh提供了很好的支持
  • Etcd,是一个分布式的K-V结构数据存储器,与Zookeeper类似,用它做注册发现需要定制开发
  • Eureka,这是Spring Cloud默认的注册中心,与Spring Cloud深度集成,功能与Consul类似,但通用性欠缺
如果我们项目是Spring Cloud构架,那么Eureka应该是首选,否则如无特性情况Consul会是很不错的选择。
绝大部分情况下我们使用指定框架提供的方案就可以实现需求,不需要太多的架构设计,但在一些特殊情况下也可能会需要自定义调用及负载策略,比如让金融、电商等行业最为头疼的“薅羊毛”行为,最常见的做法是加验证、使用各类手机、图形、滑块等验证方案,再则通过一定的方式获取到黑产库(黑卡、IP、设备等)建立自己的黑白灰名单库,对于白名单直接放行,黑名单直接过滤,但对于标记可疑的灰名单我们不能100%确定是羊毛党,但如果不处理,在诸如秒杀场景下又会给系统带来很大的负载,这时就可以活用调用负载,通过自定义负载策略,将灰名单用户路由到专用实例中,抑或是建立两套应用相对独立的集群用于分流正常请求和可疑请求以确保正常用户的访问不受影响。
❓只要有利益存在并足够大就会有动歪脑筋的人,“攻”与“防”也在一次次较量中不断地升级,做安全架构时我们要深知:没有攻不破的系统,我们能做的唯有让攻击的成本超过其收益。这也正是基于PoW(Proof of Work,工作量证明)机制的比特币能风靡全球而没有遭受致命攻击的原因。
上文提到为解决薅羊毛而加的各类验证功能在我们实际的生产中均发现有不同程度的渗透,在足够的利益面前即使加了国内某著名的验证产品那也是形同虚设。
服务注册与调用是微服务组织管理与通信的关键,对这一概念大家要所有了解,灵活地应用以优雅的方式解决特定的问题。