3.4.2 微服务构建的主要步骤
微服务简单模式的开发主要包括以下四个步骤:
第一步:沿用组织中现有的技术体系,开发单一职责的微服务;
第二步:服务提供方将地址信息注册到注册中心中,调用方将服务地址从注册中心拉下来;
第三步:通过门户后端(服务网关)将微服务API暴露给门户和移动App;
第四步:将管理端模块集成到统一的操作界面上。
为了完成以上四个步骤,需要掌握一定的基础技术(必需的组件),这些技术在前文已有了相关介绍,在这里根据微服务构建的步骤对其进行详细说明。
1. 服务注册、发现、负载均衡和健康检查
微服务架构是由一系列职责单一的细粒度服务构成的分布式网状结构,服务之间通过轻量机制进行通信,则必然会引入一个服务注册发现问题,也就是说,服务提供方要将自己的服务地址注册在某个地方(服务注册中心,Service Registry Center),服务调用方可以从服务注册中心找到需要调用的服务的地址(服务发现,Service Discovery)。同时,服务提供方一般以集群方式提供服务,这样便引入了负载均衡的需求。他们是实现微服务构建的第一步、第二步的关键所在。由于负载均衡(Load Balance,LB)所处位置的差异,其服务注册、发现和负载均衡方案也就不同,目前主要有以下三种。
(1)集中式负载均衡(LB)方案。
第一种是集中式负载均衡(LB)方案,在服务调用方和服务提供方之间有一个独立的LB,LB通常是专门的硬件设备或者基于软件等实现的,LB上所有服务的地址映射表通常由运维配置注册,当服务调用方调用某个目标服务时,它向LB发起请求,由LB以某种策略(如轮询、随机、最小响应时间、最小并发数等)做负载均衡后将请求转发到目标服务中,如图3-21所示为集中式LB方案的工作原理。LB一般具备健康检查能力,能自动摘除不健康的服务实例。服务调用方通过DNS方式来发现LB,这是由于运维人员为每一个服务配置一个DNS域名,并让这个域名指向LB。
图3-21 集中式LB 方案的工作原理
集中式LB方案实现简单,在LB上也容易进行集中式的访问控制,这一方案目前还是业界主流。集中式LB方案的主要问题是单点问题,所有服务调用流量都经过LB,当服务数量和调用量大的时候,LB容易成为瓶颈,且一旦LB发生故障,对整个系统的影响是灾难性的。另外,LB在服务调用方和服务提供方之间增加了一跳(hop),有一定的性能开销。
(2)进程内负载均衡(LB)方案。
第二种是进程内负载均衡(LB)方案,针对集中式LB的不足,进程内LB方案将LB的功能以库的形式集成到服务调用方进程中,该方案也被称为软负载(SLB)或客户端负载方案,其原理是,服务提供者将自身的地址发送至服务注册中心,同时定时发送心跳给注册中心,注册中心根据心跳情况判断是否将此节点从注册表中摘除。服务调用方调用服务时,先从注册中心拉取服务注册信息,然后根据一定的策略调用服务节点,如图3-22所示为这种方案的工作原理。这一方案需要一个服务注册表支持服务自注册和自发现,服务提供方启动时,首先将服务地址注册到服务注册表中(同时定期报心跳给服务注册表以表明服务的存活状态,相当于健康检查),服务调用方要访问某个服务时,它通过内置的LB组件向服务注册表查询(同时缓存并定期刷新)目标服务地址列表,然后以某种负载均衡策略选择一个目标服务地址,最后向目标服务发起请求。
这一方案对服务注册表的可用性(Availability)的要求很高,一般采用高可用、分布式一致的组件(如Zookeeper、Consul等)来实现。进程内LB方案是一种分布式方案,LB和服务发现能力被分散到每一个服务消费者的进程内部,同时,服务调用方和服务提供方之间是直接调用,没有额外开销,性能比较好。但是,该方案以客户库(Client Library)的方式集成到服务调用方的进程中,如果企业内有多种不同的语言栈,就要配合开发多种不同的客户端,有一定的研发和维护成本。另外,一旦客户端跟随服务调用方发布到生产环境中,后续如果要对客户库进行升级,势必要求服务调用方修改代码并重新发布,也就是说,这个方案的最大问题是,服务调用方可能需要集成注册中心的客户端,即将来注册中心服务端升级时,可能会需要升级注册中心客户端。进程内LB的案例是Netflix的开源服务框架,对应的组件:Eureka作为服务注册表,Karyon服务端框架支持服务自注册和健康检查,Ribbon客户端框架支持服务自发现和软路由。另外,阿里开源的服务框架Dubbo也采用类似机制。
图3-22 进程内LB 方案的工作原理
(3)主机独立负载均衡(LB)进程方案。
第三种是主机独立LB进程方案,该方案是针对第二种方案的不足而提出的一种方案,原理和第二种方案基本类似,不同之处是,它将LB和服务发现功能从进程内移出来,变成主机上的一个独立进程,主机上的一个或多个服务要访问目标服务时,他们都通过同一主机上的独立LB进程进行服务发现和负载均衡,如图3-23所示为主机独立LB方案的工作原理。该方案也是一种分布式方案,没有单点问题,一个LB进程出现问题只影响该主机上的服务调用方,服务调用方和LB之间是进程内调用,性能更好;同时,该方案还简化了服务调用方,不需要为不同语言开发客户库,LB的升级不需要服务调用方改写代码。该方案的不足是部署较复杂,环节多,在出错时调试、排查问题不方便。
图3-23 主机独立LB方案的工作原理
该方案的典型案例是Airbnb的SmartStack服务发现框架,对应组件:Zookeeper作为服务注册表,Nerve独立进程负责服务注册和健康检查,Synapse/HAproxy独立进程负责服务发现和负载均衡。Google最新推出的基于容器的PaaS平台Kubernetes,其内部服务发现采用类似的机制。
2. 服务前端路由
除了内部相互之间的调用和通信,微服务最终要以某种方式暴露才能让外界系统(如客户的浏览器、移动设备等)访问到,这就涉及服务的前端路由,对应的组件是服务网关(Service Gateway),即实现微服务构建的第三步。网关连接企业内部和外部系统,有如下关键作用。
(1)服务反向路由。
网关要负责将外部请求反向路由到内部具体的微服务中,这样一来,虽然企业内部是复杂的分布式微服务结构,但是外部系统从网关上看到的是一个统一的完整服务,网关屏蔽了后台服务的复杂性,同时也屏蔽了后台服务的升级和变化。
(2)安全认证和防爬虫。
所有外部请求必须经过网关,网关可以集中对访问进行安全控制,如用户认证和授权,同时还可以分析访问模式以实现防爬虫功能,网关是连接企业内外系统的安全之门。
(3)限流和容错。
在流量高峰期,网关可以限制流量,保护后台系统不被大流量冲垮,在内部系统出现故障时,网关可以集中做容错,保持外部良好的用户体验。
(4)监控。
网关可以集中监控访问量、调用延迟、错误计数和访问模式,为后端的性能优化或扩容提供数据支持。
(5)日志。
网关可以收集所有的访问日志,并进入后台系统进行进一步分析。
除以上基本能力外,网关还可以实现线上引流、线上压测、线上调试、金丝雀测试、数据中心双活等高级功能。网关具有一定的计算逻辑,一般以集群方式部署。开源的网关组件有Netflix的Zuul,其特点是动态可部署的过滤器机制,其他如HAproxy、Nginx等都可以扩展为网关来使用。
3. 运行期配置管理
服务一般有很多依赖配置,如访问数据库有连接字符串配置、连接池大小和连接超时配置,这些配置在不同环境(开发/测试/生产)中一般不同,例如,生产环境需要配连接池,而开发测试环境则可能不配。另外,有些参数配置在运行期可能还要动态调整,例如,在运行时,根据流量状况动态调整限流和熔断阈值。目前比较常见的做法是搭建一个运行时配置中心支持微服务的动态配置。
动态配置存放在集中的配置服务器中,用户通过管理界面配置和调整服务配置,具体服务通过定期拉(Scheduled Pull)的方式或服务器推(Server-side Push)的方式更新动态配置,拉方式比较可靠,但会有延迟,同时有无效网络开销(假设配置不常更新);推方式能及时更新配置,但是实现较复杂,一般要在服务和配置服务器之间建立长连接。配置中心还要解决配置的版本控制和审计问题,对于大规模服务化环境,配置中心还要考虑分布式和高可用问题。
配置中心比较成熟的开源方案有百度的Disconf、360的QConf、Spring的Cloud Config和阿里的Diamond等。
4. 服务容错
当企业微服务化以后,服务之间会有错综复杂的依赖关系。在实际生产环境中,服务往往不是百分之百可靠的,服务可能会出错或者产生延迟,如果一个应用不能对其依赖的故障进行容错和隔离,那么该应用本身就处在被拖垮的风险中。在一个高流量的网站中,某个单一后端一旦发生延迟,可能在数秒内导致所有应用资源(线程、队列等)被耗尽,造成所谓的雪崩效应。雪崩效应指分布式系统中经常会出现的某个基础服务不可用造成的整个系统不可用的情况,严重时可致整个网站瘫痪。经过多年的探索和实践,业界在分布式服务容错方面探索出了一套有效的容错模式和最佳实践,主要包括以下几点。
(1)电路熔断器模式(Circuit Breaker Pattern)。
该模式的原理类似于家里的电路熔断器的原理,如果家里的电路发生短路,熔断器能够主动熔断电路,以避免灾难性损失。在分布式系统中,采用应用电路熔断器模式后,当目标服务慢或严重超时,调用方能够主动熔断,以防止服务被进一步拖垮;如果情况好转了,电路又能自动恢复,这就是所谓的弹性容错,系统有自恢复能力。
(2)舱壁隔离模式(Bulkhead Isolation Pattern)。
舱壁隔离模式,顾名思义,该模式像舱壁一样对资源或失败单元进行隔离,如果一个船舱破了,只损失一个船舱,其他船舱可以不受影响。线程隔离就是舱壁隔离模式的一个例子,假定一个应用程序A调用了Svc1、Svc2、Svc3三个服务,且部署A的容器一共有120个工作线程,采用线程隔离机制,可以给对Svc1、Svc2、Svc3的调用各分配40个线程,当Svc2慢了,给Svc2分配的40个线程因慢而阻塞并最终耗尽,线程隔离可以保证给Svc1、Svc3分配的80个线程不受影响。如果没有这种隔离机制,当Svc2慢的时候,120个工作线程会很快被对Svc2的调用吃光,整个应用程序会全部慢下来。
(3)限流(Rate Limiting/Load Shedder)。
服务总有容量限制,没有限流机制的服务很容易在突发流量(如“双十一”)时被冲垮。限流通常指对服务限定并发访问量,例如,在单位时间内只允许100个并发调用,对超过这个限制的请求要拒绝并回退。
(4)回退(Fallback)。
在熔断或者限流发生时,应用程序的后续处理逻辑是什么?回退是系统的弹性恢复能力,常见的处理策略是直接抛出异常,也称快速失败,也可以返回空值或缺省值,还可以返回备份数据,如果主服务熔断了,可以从备份服务中获取数据。
Netflix将上述容错模式和最佳实践集成到一个称为Hystrix的开源组件中,凡是需要容错的依赖点(服务、缓存、数据库访问等),开发人员只需要将调用封装在Hystrix Command中,则相关调用就自动置于Hystrix的弹性容错保护之下。Hystrix组件已经在Netflix经过多年运维验证,是Netflix微服务平台稳定性和弹性的基石,正逐渐被社区接受为标准容错组件。
5. 服务框架
在微服务化后,为了让业务开发人员专注于业务逻辑实现,避免冗余和重复劳动,规范研发提升效率,必然要将一些公共关注点推到框架层面,从而实现微服务构建的第四步。服务框架主要封装公共关注点逻辑,包括以下几点。
(1)服务注册、发现、负载均衡和健康检查。
假定采用进程内LB方案,那么服务自注册一般统一放在服务器端框架中,健康检查逻辑由具体业务服务定制,框架层提供调用健康检查逻辑的机制,服务发现和负载均衡则集成在服务客户端框架中。
(2)监控日志。
框架一方面要记录重要的框架层日志、metrics和调用链数据,还要将日志、metrics等接口暴露出来,让业务层能根据需要记录业务日志数据。在运行环境中,所有日志数据一般集中到企业后台日志系统中,做进一步分析和处理。
(3)REST、RPC和序列化。
框架层要支持将业务逻辑以HTTP/REST或RPC方式暴露出来,HTTP/REST是当前主流API暴露方式,在性能要求高的场合中,则可采用Binary/RPC方式。针对当前多样化的设备类型(浏览器、普通PC、无线设备等),框架层要支持可定制的序列化机制,例如,对于浏览器,框架支持输出对Ajax友好的JSON消息格式,而对于无线设备上的Native App,框架支持输出性能高的Binary消息格式。
(4)配置。
除了能够支持普通配置文件方式,框架层还可以集成动态运行时的配置,能够在运行时针对不同环境动态调整服务的参数和配置。
(5)限流和容错。
框架集成限流容错组件,能够在运行时自动限流和容错以保护服务,如果进一步和动态配置相结合,还可以实现动态限流和熔断。
(6)管理接口。
框架集成管理接口,既可以在线查看框架和服务的内部状态,还可以动态调整内部状态,对于调试、监控和管理,能提供快速反馈。Spring Boot微框架的Actuator模块就是一个强大的管理接口。
(7)统一错误处理。
对于框架层和服务的内部异常,如果框架层能够统一处理并记录日志,则对服务监控和快速问题定位有很大帮助。
(8)安全。
安全和访问控制逻辑可以在框架层统一进行封装,可做成插件形式,具体业务服务根据需要加载相关安全插件。
(9)文档自动生成。
文档的书写和同步一直是一个痛点,框架层如果能支持文档的自动生成和同步,会给使用API的开发和测试人员带来极大便利。
当前业界比较成熟的微服务框架有Netflix的Karyon/Ribbon、Spring的Spring Boot/Cloud、阿里的Dubbo等。