Please enable Javascript to view the contents

微服务

 ·   ·  ☕ 12 分钟 · 👀... 阅读

微服务定义

微服务架构即是采用一组小服务来构建应用的方法。
每个服务运行在独立的进程中,不同服务通过一些轻量级交互机制来通信, 例如 RPC、HTTP 等。
服务围绕业务能力来构建,并依赖自动部署机制来独立部署。

SOA的一种实践。
具有以下特点:

  • 遵循kiss原则,高度可维护和可测试
  • 低耦合
  • 可独立部署交付
  • 围绕业务能力进行组织
  • 由一个小团队拥有
  • 去中心化服务治理

单体/巨石架构到微服务的演进

生产率和复杂度关系

MonolithAndMicroservice
上图揭示了生产率和复杂度的一个关系。在复杂度较小时采用单体应用(Monolith)的生产率更高,复杂度到了一定规模时,单体应用的生产率开始急剧下降,这时对其进行微服务化的拆分才是合算的。

v1.0

v1.0
前端用户体验层主要是传统的服务端Web应用,库(library)是和应用一起运行在进程中,库的局部变化意味着整个应用的重新部署。

v2.0

v2.0
无线端接入,应用程序分出一部分专为无线端提供接口,BFF是Backend for Frontend的简称,中文翻译是为前端而开发的后端,它主要由前端团队开发(后端微服务一般由后端团队开发)。BFF可以认为是一种适配服务,将后端的微服务进行适配(主要包括聚合裁剪和格式适配等逻辑,dataset join),向无线端设备暴露友好和统一的API,方便无线设备接入访问后端服务。

同时服务组件化,将原有库和应用绑定一起的进行拆分。通过服务来实现组件,意味着将应用拆散为一系列的服务运行在不同的进程中,那么单一服务的局部变化只需重新部署对应的服务进程。

v3.0

v3.0

上个版本很成功,但若业务复杂度进一步提升,存在些问题:

  1. 刚开始只有一个Mobile BFF,是个单块,但是无线研发团队在不断增加,分别对应多条业务线。根据康威法则,单块的无线BFF和多团队之间就出现不匹配问题,团队之间沟通协调成本高,交付效率低下。
  2. Mobile BFF里头不仅有各个业务线的聚合/裁剪/适配和业务逻辑,还引入了很多跨横切面逻辑,比如安全认证,日志监控,限流熔断等。随着时间的推移,代码变得越来越复杂,技术债越堆越多,开发效率不断下降,缺陷数量不断增加。
  3. Mobile BFF集群是个失败单点(Single Point of Failure),严重代码缺陷或者流量洪峰可能引发集群宕机,所有无线应用都不可用。

为了解决上述问题,架构师经过思考决定在外部设备和内部BFF之间再引入一个新的角色~API Gateway,并进行如下调整:

  1. BFF按团队或业务线进行解耦拆分,拆分成若干个BFF微服务,每个业务线可以并行开发和交付各自负责的BFF微服务。
  2. API Gateway网关(一般由独立框架团队负责运维)专注跨横切面(Cross-Cutting Concerns)的功能,包括:
    • 路由,将来自无线设备的请求路由到后端的某个微服务BFF集群。
    • 认证,对涉及敏感数据的API访问进行集中认证鉴权。
    • 监控,对API调用进行性能监控。
    • 限流熔断,当出现流量洪峰,或者后端BFF/微服务出现延迟或故障,网关能够主动进行限流熔断,保护后端服务,并保持前端用户体验可以接受。
    • 安全防爬,收集访问日志,通过后台分析出恶意行为,并阻断恶意请求。

v4.0

v4.0

随着业务线继续增加,满足内部业务同时对外提供开放api,引入前后分离架构,前端采用H5单页等技术给用户提供更好的体验。

V4整体思路和V3类似,只是拓展了新的接入渠道:

  1. 引入面向第三方开放API的BFF层和配套的网关
  2. 引入面向H5应用的BFF层和配套的网关,支持前后分离和H5单页应用模式。

微服务架构并不是一蹴而就的。V4是一个比较完整的现代微服务架构,从外到内依次分为:端用户体验层->网关层->BFF层->微服务层。整个架构层次清晰,职责分明,是一种灵活的能够支持业务不断创新的演化式架构。

实施

  1. 组件服务化

    • 微服务基础库(kit)

    • 业务代码+kit依赖+第三方依赖

    • RPC + message queue 轻量级通讯机制

    本质上等同于,多个微服务组合完成了一个完整的用户场景

  2. 按业务组织服务

    服务提供的能力和业务功能对应

    you build it you run it,开发团队对软件在生产环境上运行负全部责任。

  3. 去中心化

    • 数据
    • 治理
    • 技术

    每个服务独享自身的数据存储设施(缓存、数据库等),不像传统应用共享一个缓存和数据库,这样有利于服务的独立性,隔离相关干扰

  4. 基础设施自动化

    • CICD gitlab + GitLag hooks + kubernetes
    • Testing: 测试环境、单元测试、api自动化测试
    • 在线运行时:kubernetes,以及一系列Prometheus、ELK、Control Panel
  5. 可用性&兼容性设计

    • 隔离
    • 超时控制
    • 限流
    • 降级
    • 重试
    • 负载均衡

设计模式

说到设计模式,大家一般会想到,工厂、单例等24种基本设计模式,当然也会想到并发型模式,生产-消费者模式,线程池模式等,但是微服务中用到什么设计模式?AzureCAT模式和实践团队在Azure架构中心发布了九种新的设计模式。在设计和实现微服务时,这九种模式特别有用。微服务越来越变的流行是记录这些模式的动机。
设计模式

  • Ambassador(代表模式) 可用于以一种与语言无关的方式卸载常见客户端连接任务,如监视、记录、路由、安全(如 TLS)。
  • Anti-corruption layer (防损层模式) 实现了新旧应用程序之间的外观,以确保新应用程序的设计不受遗留系统依赖性的限制。使用此模式可确保应用程序的设计不受限于对外部子系统的依赖。 此模式最先由 Eric Evans 在 Domain-Driven Design(域驱动的设计)中描述。
  • Backends for Frontends (用于前端的后端模式) 创建单独的后端服务,供特定的前端应用程序或接口使用。 要避免为多个接口自定义一个后端时,此模式十分有用。后端为不同类型的客户端(如桌面和移动设备)创建单独的后端服务。这样,单个后端服务不需要处理各种客户端类型的冲突要求。通过分离客户特定的问题,这种模式可以帮助保持每个微服务的简单性。
  • Bulkhead(隔舱模式)之所以称为“隔舱”(Bulkhead),是因为它类似于船体的分段区。 如果船体受到破坏,只有受损的分段才会进水,从而可以防止船只下沉。为每个工作负载或服务隔离关键资源,例如连接池,内存和CPU。通过使用隔板,单个工作负载(或服务)无法消耗所有资源,使其他资源匮乏。此模式通过防止由一个服务引起的级联故障来提高系统的弹性。
  • Gateway Aggregation(网关聚合模式) 使用网关可将多个单独请求聚合成一个请求。 当客户端必须向不同的后端系统发出多个调用来执行某项操作时,此模式非常有用使用网关可将多个单独请求聚合成一个请求。 当客户端必须向不同的后端系统发出多个调用来执行某项操作时,此模式非常有用。
  • Gateway Offloading(网关卸载方式) 将共享或专用服务功能卸载到网关代理。 此模式可以通过将共享服务功能(如 SSL 证书的使用)从应用程序的其他部分移动到网关,简化应用程序开发。
  • Gateway Routing(网关路由模式) 使用单个终结点将请求路由到多个服务。 如果希望在单个终结点上公开多个服务,并根据请求路由到适当的服务,则此模式非常有用。
  • Sidecar(挎斗模式 ) 将应用程序的帮助程序组件部署为单独的容器或进程,以提供隔离和封装。使用此模式还可以使用异构组件和技术来构建应用程序。
  • Strangler(绞杀者模式) 通过将特定的功能片断逐渐取代为新的应用程序和服务,逐步迁移旧系统。 随着旧系统的功能被替换,新系统最终将取代旧系统的所有功能,抑制旧系统并使其停用。通过逐步用新服务替换特定功能来支持增量迁移。

微服务架构也带来了一些挑战,这些模式可以帮助缓解这些挑战。设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。当然微服务中的云设计模式也是对微服务中普遍存在的问题,所提出的解决方案。

不要低估了微服务带来的复杂性

拆分粒度的控制

其中一个跟他的名字类似,『微服务』强调了服务大小,实际上,有一些开发者鼓吹建立稍微大一些的,10-100 LOC服务组。尽管小服务更乐于被采用,但是不要忘了这只是终端的选择而不是最终的目的。微服务的目的是有效的拆分应用,实现敏捷开发和部署。

分布式系统

微服务应用是分布式系统,由此会带来固有的复杂性。开发者需要在RPC或者消息传递之间选择并完成进程间通讯机制。更甚于,他们必须写代码来处理消息传递中速度过慢或者不可用等局部失效问题。当然这并不是什么难事,但相对于单体式应用中通过语言层级的方法或者进程调用,微服务下这种技术显得更复杂一些。

分布式数据库

分区的数据库架构。商业交易中同时给多个业务分主体更新消息很普遍。这种交易对于单体式应用来说很容易,因为只有一个数据库。在微服务架构应用中,需要更新不同服务所使用的不同的数据库。使用分布式交易并不一定是好的选择,不仅仅是因为CAP理论,还因为今天高扩展性的NoSQL数据库和消息传递中间件并不支持这一需求。最终你不得不使用一个最终一致性的方法,从而对开发者提出了更高的要求和挑战。

测试微服务

测试一个基于微服务架构的应用也是很复杂的任务。比如,采用流行的Spring Boot架构,对一个单体式web应用,测试它的REST API,是很容易的事情。反过来,同样的服务测试需要启动和它有关的所有服务(至少需要这些服务的stubs)。

服务模块间的依赖,应用的升级有可能会波及多个服务模块的修改

微服务架构模式应用的改变将会波及多个服务。比如,假设你在完成一个案例,需要修改服务A、B、C,而A依赖B,B依赖C。在单体式应用中,你只需要改变相关模块,整合变化,部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,你需要更新服务C,然后是B,最后才是A,幸运的是,许多改变一般只影响一个服务,而需要协调多服务的改变很少。

对运维基础设施的挑战比较大

部署一个微服务应用也很复杂,一个分布式应用只需要简单在复杂均衡器后面部署各自的服务器就好了。每个应用实例是需要配置诸如数据库和消息中间件等基础服务。相对比,一个微服务应用一般由大批服务构成。例如,根据Adrian Cockcroft,Hailo有160个不同服务构成,NetFlix有大约600个服务。每个服务都有多个实例。这就造成许多需要配置、部署、扩展和监控的部分,除此之外,你还需要完成一个服务发现机制,以用来发现与它通讯服务的地址(包括服务器地址和端口)。传统的解决问题办法不能用于解决这么复杂的问题。接续而来,成功部署一个微服务应用需要开发者有足够的控制部署方法,并高度自动化。

服务间进程通信

微服务必须使用进程间通信机制来交互。当设计服务的通信模式时,你需要考虑几个问题:

  • 服务如何交互,网络延迟、消息格式、负载均衡和容错
  • 每个服务如何标识API
  • 如何升级API
  • 以及如何处理部分失败。

微服务架构有两类IPC机制可选,异步消息机制和同步请求/响应机制。

IPC(Inter-Process Communication)技术

现在有很多不同的IPC技术。

服务之间的通信可以使用同步的请求/响应模式,比如基于HTTP的REST或者Thrift

另外,也可以选择异步的、基于消息的通信模式,比如AMQP或者STOMP

除此之外,还有其它的消息格式供选择,比如JSONXML,它们都是可读的,基于文本的消息格式。

当然,也还有二进制格式(效率更高)的,比如AvroProtocol Buffer

这篇博客很好的比较了Thrift、Protocol Buffers、Avro三者的区别。

服务之间的交互必须通过进程间通信(IPC)来实现。客户端和服务器之间有很多的交互模式,我们可以从两个维度进行归类。

一对一 一对多
同步 请求/响应
异步 通知 发布/ 订阅模式
请求/异步响应 发布/异步响应模式

• 请求/响应:一个客户端向服务器端发起请求,等待响应。客户端期望此响应即时到达。在一个基于线程的应用中,等待过程可能造成线程阻塞。
• 通知(也就是常说的单向请求):一个客户端请求发送到服务端,但是并不期望服务端响应。
• 请求/异步响应:客户端发送请求到服务端,服务端异步响应请求。客户端不会阻塞,而且被设计成默认响应不会立刻到达。

• 发布/ 订阅模式:客户端发布通知消息,被零个或者多个感兴趣的服务消费。

• 发布/异步响应模式:客户端发布请求消息,然后等待从感兴趣服务发回的响应。

每个服务都是以上这些模式的组合,对某些服务,一个IPC机制就足够了;而对另外一些服务则需要多种IPC机制组合。

定义API

API是服务端和客户端之间的契约。不管选择了什么样的IPC机制,重要的是使用某种交互式定义语言(IDL)来精确定义一个服务的API。甚至有一些关于使用API first的方法(API-first approach)来定义服务的很好的理由。在开发之前,你需要先定义服务的接口,并与客户端开发者详细讨论确认。这样的讨论和设计会大幅度提到API的可用度以及满意度。

API定义实质上依赖于选择哪种IPC。如果使用消息机制,API则由消息频道(channel)和消息类型构成;如果选择使用HTTP机制,API则由URL和请求、响应格式构成。

兼容性

一旦采用了微服务架构模式,那么在服务需要变更时我们要特别小心,服务提供者的变更可能引发服务消费者的兼容性破坏,时刻谨记保持服务契约(接口)的兼容性。一条普适的健壮性原则(伯斯塔尔法则,参考[6])给出了很好的建议:

Be conservative in what you send, be liberal in what you accept.

发送时要保守,接收时要开放。 按照伯斯塔尔法则的思想来设计和实现服务时,发送的数据要更保守,意味着最小化的传送必要的信息,接收时更开放意味着要最大限度的容忍冗余数据,保证兼容性。

容错

Netfilix提供了一个比较好的解决方案,具体的应对措施包括:

• 网络超时:当等待响应时,不要无限期的阻塞,而是采用超时策略。使用超时策略可以确保资源不会无限期的占用。
• 限制请求的次数:可以为客户端对某特定服务的请求设置一个访问上限。如果请求已达上限,就要立刻终止请求服务。
断路器模式(Circuit Breaker Pattern):记录成功和失败请求的数量。如果失效率超过一个阈值,触发断路器使得后续的请求立刻失败。如果大量的请求失败,就可能是这个服务不可用,再发请求也无意义。在一个失效期后,客户端可以再试,如果成功,关闭此断路器。
• 提供回滚:当一个请求失败后可以进行回滚逻辑。例如,返回缓存数据或者一个系统默认值。

Netflix Hystrix是一个实现相关模式的开源库。如果使用JVM,推荐考虑使用Hystrix。而如果使用非JVM环境,你可以使用类似功能的库。

参考

分享
您的鼓励是我最大的动力

Jason Tan
作者
Jason Tan
Developer