微服务架构设计模式-第十一章
第11章 开发面向生产环境的微服务应用
11.1 开发安全的服务
应用程序开发人员主要负责实现安全性的四个不同方面:
- 身份验证
- 访问授权:基于角色的安全性和访问控制列表(ACL)的组合
- 审计:跟踪用户在应用中执行的所有操作,以便检测安全问题
- 安全的进程间通信:理想情况下,所有进出服务的通信都应该采用传输层安全性(TLS)加密
11.1.1 传统单体应用程序的安全性
Spring Security
Apache Shiro
Passport(Nodejs)
安全架构的一个关键部分是会话,它存储主题的ID和角色。
另一个是安全上下文,存储发出当前请求的用户的信息。
11.1.2 在微服务架构中实现安全性
在微服务应用程序中实现安全性的一个挑战是我们不能仅仅从单体应用程序借鉴设计思路。因为某些方面对微服务架构来说是不可以的,比如:
- 内存中的安全上下文:使用内存中的安全上下文(如 ThreadLocal)来传递用户身份。服务无法共享内存,因此它们无法使用内存中的安全上下文(如 ThreadLocal)来传递用户身份。
- 集中会话:因为内存中的安全上下文没有意义,内存会话也没有意义。我们需要在微服务架构中使用不同的会话机制
由API Gateway 处理身份验证
模式:访问令牌
API Gateway 将包含用户信息(例如其身份和角色)的令牌传递给它调用的服务。
API Gateway 调用的服务需要知道发出请求的主体(用户的身份)。他还必须验证请求是否已经通过身份验证。解决方案是让API Gateway 在每个服务请求中包含一个令牌。服务使用令牌验证请求,并获取有关主体的信息。API Gateway 还可以为面向会话的客户端提供相同的令牌,以用作会话令牌。
处理访问授权
在API Gateway中实现访问授权的一个弊端是,它有可能产生 API Gateway 与服务之间的耦合,要求它们以同步的方式进行代码更新。而且,API Gateway 通常只能实现对URL路径的基于角色的访问。由API Gateway 实现对单个领域对象的访问授权通常是不实际的,因为这需要详细了解服务的领域逻辑。
另一个实现访问授权的位置是服务。服务可以对URL和服务方法实现基于角色的访问授权。它还可以实现ACL来管理对聚合的访问。例如,在Order Service中可以实现基于角色和基于ACL的授权机制,以控制对Order的访问。FTGO应用程序中的其他服务也可以实现类似的访问授权逻辑。
使用 JWT 传递用户身份和角色
- 使用不透明(无可读性)的令牌,通常是一串UUID。缺点是它们会降低性能和可用性,并增加延迟。因为这种令牌的接收方必须对安全服务发起 RPC 调用,以验证令牌并检索用户信息。
- 透明令牌,一个流行的标准是 JSON Web 令牌。JWT是在访问双方之间安全地传递信息(例如用户身份和角色)的标准方式。JWT的内容包含一个JSON对象,其中有用户的信息,例如其身份和角色,以及其他元数据,如到期日期等。它使用仅为JWT的创建者所知的数字签名,例如APIGateway和JWT的接收者(服务)。该签名确保恶意第三方不能伪造或篡改JWT。
在微服务架构中使用 OAuth 2.0
OAuth 2.0是一种访问授权协议,最初旨在使公共云服务(如GitHub或Google)的用户能够授予第三方应用程序访问其信息的权限,而不必向第三方应用透露他们的密码。例如,OAuth 2.0使你能够安全地授予第三方基于云的持续集成(CI)服务,访问你的GitHub存储库。
OAuth 2.0 中关键概念如下:
-
授权服务器:提供用于验证用户身份以及获取访问令牌和刷新令牌的API。Spring OAuth是一个很好的用来构建OAuth 2.0授权服务器的框架。
-
访问令牌:授予对资源服务器的访问权限的令牌。访问令牌的格式取决于具体的实现技术。SpringOAuth的实现中采用了JWT格式的访问令牌。
-
刷新令牌:客户端用于获取新的AccessToken的长效但同时也可被可撤销的令牌。
-
资源服务器:使用访问令牌授权访问的服务。在微服务架构中,服务是资源服务器。
-
客户端:想要访问资源服务器的客户端。在微服务架构中,API Gateway是OAuth 2.0客户端。

使用OAuth 2.0的一个重要好处是它是经过验证的安全标准。使用现成的OAuth 2.0身份验证服务器意味着你不必浪费时间重新发明轮子或者是没有开发不安全的设计的风险。但OAuth 2.0不是在微服务架构中实现安全性的唯一方法。无论你使用哪种方法,三个关键思想如下: -
API Gateway 负责验证客户端的身份
-
API Gateway 和服务使用透明令牌(如 JWT)来传递有关主体的信息
-
服务使用令牌获取主体的身份和角色
11.2 设计可配置的服务
模式:外部化配置
在运行时向服务提供配置属性值,例如数据库访问凭据和网络位置
- 推送模型:部署基础设施提供类似操作系统环境变量或配置文件,将配置属性传递给服务实例
- 拉取模型:服务实例从配置服务器读取它所需要的配置属性
11.2.1 使用基于推送的外部化配置

java ApplcationContext @Value
限制:
- 重新配置正在运行的服务可能很难,甚至不可能。
- 配置属性值存在分散的众多服务定义中的风险。
11.2.2 使用基于拉取的外部化配

有多种方法可以实现配置服务器,包括:
- 版本控制系统,如git
- SQL和NoSQL数据库
- 专用配置服务器,例如 spring cloud config server,Hashicorp Vault(用于存储敏感数据,如访问凭据)
使用配置服务器有几个好处:
- 集中配置
- 敏感数据的透明解密
- 动态重新配置:服务可能会提供轮询等方式检测更新的属性值,并重新配置自身
11.3 设计可观测的服务
在生产中管理应用程序的许多方面都超出了开发人员的职责范畴,例如监控硬件可用性和利用率。这些显然是运维的职责。但是,作为服务开发人员,你必须实现多种模式才能使你的服务更易于管理和排错。
- 健康检测API:公开返回服务运行状况的接口
- 日志聚合:记录服务活动并将日志写入集中式日志记录服务器,该服务器提供搜索和告警
- 分布式跟踪:为每一个在服务之间跳转的外部请求分配唯一ID,并跟踪请求
- 异常跟踪:向异常跟踪服务报告异常,该异常跟踪服务可以对异常进行重复数据删除,向开发人员发出警报并跟踪每个异常的解决方案
- 应用程序指标:服务运维指标,例如计数器和指标,并将它们公开给指标服务器
- 审核日志记录:记录用户操作

11.3.1 使用健康检查API模式
服务公开健康检查API接口,例如 GET/health,返回服务的健康情况
有时服务看上去正在运行,但它却无法处理请求。例如,新启动的服务实例可能尚未准备好接受请求。例如,FTGO的Consumer Service大约需要10秒钟来初始化消息和数据库适配器。在它们准备好之前,部署基础设施将HTTP请求路由到服务实例是没有意义的。
此外,服务实例可能会失败却不自动终止。例如,错误可能导致consumer Service的实例耗尽数据库连接并且无法访问数据库。部署基础设施不应将请求路由到已失败但仍在运行的服务实例。并且,如果服务实例无法恢复,则部署基础设施必须终止它并创建新实例。
服务实例需要能够告诉部署基础设施它是否能够处理请求。一个好的解决方案是服务实现健康检查接口。
spring boot autuator java 库实现了一个 get/actuator/health 接口

使用健康检查时需要考虑两个问题。第一个是接口的实现,它必须报告服务实例的健康状况。第二个问题是如何配置部署基础设施以调用健康检查接口。我们先来看看如何实现接口。
实现健康检查接口
实现健康检查接口的代码必须以某种方式确定服务实例的健康状况。一种简单的方法是验证服务实例是否可以访问其外部基础设施服务。具体做法取决于具体的基础设施服务,例如可以通过获取数据库连接,并执行测试查询来验证服务是否已连接到数据库。更复杂的方法是执行模拟客户端调用服务API的综合事务。这种健康检查更加彻底,但实现起来可能更耗时,执行时间也更长。
调用健康检查接口
配置服务注册表(netflix eureka)来调用健康检查接口,以确定是否应将流量路由到服务实例。
11.3.2 使用日志聚合模式
在支持搜索和告警的集中式数据库中聚合所有服务的日志
日志聚合流水线将所以服务实例的日志发送给集中式日志服务器。日志服务器存储日志后,可以查看、搜索和分析日志,还可以配置在日志中出现某些消息时触发的告警。

服务如何生产日志
- 确定要使用的日志
- 在哪里写日志
Logback、Log4j和JUL(java.util.logging),还有SLF4J,它是各种日志框架的外围(facade)API。
日志聚合的基础设施
:负责聚合日志、存储日志以及使用户能够搜索日志。一种流行的日志记录基础设施是ELK套件。
- elasticSearch:面向文本搜索的nosql数据库,用作日志记录服务器
- logstash:聚合服务日志并将其写入elasticSearch的日志流水线
- kibana:elasticSearch的可视化工具
11.3.3 使用分布式追踪模式
:为每个外部请求分配一个唯一的ID,并在提供可视化和分析的集中式服务器中记录它如何从一个服务流向下一个服务。
zipkin


分布式追踪包含两个部分:供每个服务使用的追踪工具类库和分布式追踪服务器。追踪工具类库管理追踪和跨度。它还会向出站请求添加追踪信息,例如当前追踪ID和父跨度ID。例如,传播追踪信息的一个通用标准是B 标准(https://github。com/openzipkin/b 3-propagation),它用X-B 3-TraceId和x-B 3-ParentSpanId之类的头部。追踪工具类库还会向分布式追踪服务器报告追踪。分布式追踪服务器存储追踪信息,并提供可用于可视化它们的用户界面。
使用追踪工具类库
追踪工具类库构建跨度树,并将它们发送到分布式追踪服务器。服务代码可以直接调用追踪工具类库,但这会将检测逻辑与业务和其他逻辑交织在一起。更简洁的方法是使用拦截器或面向切面编程(AOP)。
Spring Cloud Sleuth是基于AOP技术的一个优秀框架。它使用Spring Framework的AOP机制将分布式追踪自动集成到服务中。因此,你必须将SpringCloud Sleuth添加为项目依赖项。除了Spring Cloud Sleuth未处理的情况之外,服务不需要直接调用分布式追踪API。
关于分布式追踪服务器
追踪工具类库将跨度发送到分布式追踪服务器。分布式追踪服务器将跨度拼接在一起以形成完整的追踪并将它们存储在数据库中。一个流行的分布式追踪服务器是OpenZipkin。
11.3.4 使用应用程序指标模式
监控和告警功能是生产环境的关键部分。如图11-14所示,监控系统从技术栈的每个部分收集指标,这些指标提供有关应用程序健康状况的关键信息。指标涵盖的范围从基础设施相关的指标(如CPU、内存和磁盘利用率)到应用程序级别的指标(如服务请求延迟和执行的请求数)。例如,Order Service收集有关已下订单、已批准订单和已拒绝订单数量的指标。指标服务收集各类指标数据,提供可视化和告警功能。
服务将指标数据发送给负责聚合、可视化和告警的中央服务器
指标定期采样。指标样本具有以下三个属性:
- 名称:指标的名称,例如 jvm_memory_max_bytes 或 placed_orders
- 值:数值
- 时间戳:样本的时间

此外,一些健康系统支持维度的概念,维度是任意的名称与值的组合。例如,报告
jvm_memory_max_bytes的度:area=“heap”,id=“PS Eden Space” 和 area=“heap”,id=“PS OLD dGen”。维度通常用于提供其他信息,例如计算机名称、服务名称或服务实例标识符。监控系统通常沿一个或多个维度聚合(求和或平均)度量样本。
监控的许多方面都是运维人员的职责。但是服务开发人员需要负责指标的两个方面。首先,他们必须编写监测服务的代码,以便收集有关其行为的指标。其次,他们必须将这些服务指标以及来自JVM和应用程序框架的指标发送给指标服务器。
收集服务层面的指标
收集指标的工作量取决于应用程序使用的框架以及要收集的具体指标。例如,经过简单的配置,基于Spring Boot的服务可以使用Micrometer Metrics库作为依赖项来收集(并公布)基本指标,例如 JVM 指标。SpringBoot的自动配置功能负责配置指标库并对外公布指标。如果服务收集特定于应用程序的指标,则该服务仅需要直接使用Micrometer Metrics API。

把指标发送给指标服务
服务有两种方式向指标服务提供数据:推送或拉取。
- 推:服务实例提供调用API将指标发送到指标服务。
- 拉:metrics service(或其本地运行的代理)调用服务API,并从服务实例检索指标信息。Prometheus 是一种流行的开源监控和警报系统,它使用拉模型
Prometheus服务器会定期轮询此端口以检索指标。一旦指标被保存在Prometheus中,你就可以使用数据可视化工具Grafana( https://grafana.com )查看它们。你还可以为这些指标设置提醒,例如当placed_orders_total的更改率低于某个阈值时。
应用程序指标可为你的应用程序行为提供有价值的信息。通过指标触发的告警使你能够快速响应生产环境发生的问题,这些问题可能会影响用户。现在让我们看看如何观测和响应另一个警报源:异常。
11.3.5 使用异常追踪模式
服务很少记录异常,当它发生异常时,确定根本原因很重要。异常可能是失败或编程错误的结果。查看异常的传统方法是查看日志。你甚至可以配置日志记录服务器,以便在日志文件中出现异常时向你发出警报。但是,这种方法存在一些问题:
- 日志文件以单行日志条目为导向,而异常由多行组成。
- 没有机制来追踪日志文件中发生的异常的解决方案。你必须手动将异常复制粘贴到问题追踪器中。
- 可能存在重复的异常,但没有自动机制将它们视为异常。
服务把产生的异常报告给中央服务,该服务对异常进行重复数据删除、生成警报并管理异常的解决方案
有几种方法可以将异常追踪服务集成到你的应用程序中。服务可以直接调用异常追踪服务的API。更好的方法是使用异常追踪服务提供的客户端库。例如,HoneyBadger(www.honeybadger.io)的客户端库提供了几种易于使用的集成机制,包括捕获和报告异常的Servlet过滤器。
sentry.io
11.3.6 使用审计日志模式
审计日志记录的目的是记录每个用户的操作。审计日志通常用于帮助客户支持、确保合规性并检测可疑行为。每个审核日志条目都记录用户的身份、他们执行的操作以及业务对象。应用程序通常将审计日志存储在数据库表中。
记录数据库中的用户操作,以帮助客户支持、确保合规性,并检测可疑行为。
有几种不同的方法来实现审计日志记录:
- 将审计日志记录代码添加到业务逻辑中
- 使用面向切面编程
- 使用事件溯源
将审计日志记录代码添加到业务逻辑中
缺点:紧耦合;容易出错
使用面向切面编程
AOP,自动记录每个服务方法调用。
确定:只能记录调用的方法名称和参数
使用事件溯源
第三个也是最后一个选择是使用事件溯源来实现你的业务逻辑。如第6章所述,事件溯源自动为创建和更新操作提供审计日志。你需要在每个事件中记录用户的身份。但是,使用事件溯源的一个限制是它不记录查询。如果你的服务必须为查询创建日志条目,那么你还必须考虑其他选择。
11.4 使用微服务基底模式开发服务
异常追踪、日志记录、健康检测、外部化配置和分布式追踪是微服务架构需要解决的共性问题,我们需要在能够处理那些共性问题的框架或框架集合上构建服务。
开发服务的一种更快捷的方法是在微服务基底上构建服务。如图11-16所示,微服务基底是处理这些问题的框架或一组框架。使用微服务基底时,你只需编写很少的代码来处理这些问题。

11.4.1 使用微服务基底
微服务基底是一个框架或一组框架,可以处理许多问题,包括:
- 外部化配置。
- 健康检查。
- 应用程序指标。
- 服务发现。
- 断路器。
- 分布式追踪。
它可以显著减少你需要编写的代码量。你甚至可能不需要编写任何代码。相反,你可以配置微服务基底以满足你的要求。微服务基底使你能够专注于开发服务的业务逻辑。
使用微服务基底的一个弊端是,开发者必须需要确保正使用的编程语言/平台组合,有与之对应的微服务基底框架或类库。
11.4.2 从微服务基底到服务网格
把所有进出服务的网络流量通过一个网络层进行路由,这个网络层负责解决包括断路器、分布式追踪、服务发现、负载均衡和基于规则的流量路由等具有共性的需求。
避免此问题的新兴替代方案是在所谓的服务网格中实现服务之外的某些功能。服务网格是网络基础设施,它调和(mediate)服务与其他服务和外部应用程序之间的通信。
进出服务的所有网络流量都通过服务网格。它实现了各种共性的需求:包括断路器、分布式追踪、服务发现、负载均衡和基于规则的流量路由。服务网格还可以通过在服务之间使用基于TLS的机制来保护进程间通信。因此,你不再需要在服务中解决这些特定问题。

使用服务网格后,微服务基底需要担负的责任就少了很多。它只需要实现与应用程序代码紧密集成的问题,例如外部化配置和健康检查。微服务基底必须通过传播分布式追踪信息来支持分布式追踪
当前的服务网格实现
- Istio
- linkerd
- conduit
服务网格概念是一个非常有前途的想法。它使开发人员不必处理各种共性问题。此外,服务网格的智能流量路由使你可以将部署与发布分开。它使你能够将新版本的服务部署到生产中,但只将其发布给某些用户,例如内部测试用户。


浙公网安备 33010602011771号