笔记-《Spring微服务实战》

image|500

简介 #

“本书以一个名为O-stock的项目为主线,介绍云、微服务等概念以及Spring Boot和Spring Cloud等诸多Spring项目,并介绍如何将O-stock项目一步一步地从单体架构重构成微服务架构,进而将这个项目拆分成众多微服务,让它们运行在各自的Docker容器中,实现持续集成/持续部署,并最终自动部署到云环境(AWS)的 Kubernetes集群中。针对在重构过程中遇到的各种微服务开发会面临的典型问题(包括开发、测试和运维等问题),本书介绍了解决这些问题的核心模式,以及在实战中如何选择特定Spring Cloud子项目或其他工具(如 KeyCloak、Zipkin、ELK技术栈)解决这些问题。本书适合拥有构建分布式应用程序的经验、拥有Spring的知识背景以及对学习构建基于微服务的应用程序感兴趣的Java开发人员阅读。对于希望使用微服务构建基于云的应用程序,以及希望了解如何将基于微服务的应用部署到云上的开发人员,本书也具有很好的学习参考价值。约翰·卡内尔(John Carnell)是一位资深云工程师,拥有二十多年的Java开发经验。他大部分时间都在使用AWS平台构建基于电话的微服务。他的日常工作主要是设计和构建跨Java、Clojure和Go等多种技术平台的微服务。伊拉里·华卢波·桑切斯(Illary Huaylupo Sánchez)是一名软件工程师,拥有十多年的Oracle认证开发经验,目前,Illary 在哥斯达黎加圣何塞的微软公司担任高级软件工程师,在那里她将大部分时间花在研究和开发各种流行的最新项目上。”

目录 #

  • 第1章 欢迎迈入云世界,Spring 1
  • 1.1 微服务架构的演进 1
  • 1.1.1 n 层架构 2
  • 1.1.2 什么是单体架构 2
  • 1.1.3 什么是微服务 3
  • 1.1.4 为什么要改变构建应用的方式 5
  • 1.2 使用Spring 开发微服务 6
  • 1.3 我们在构建什么 7
  • 1.4 本书涵盖什么内容 8
  • 1.4.1 在本书中你会学到什么 8
  • 1.4.2 为什么本书与你有关 9
  • 1.5 云和基于微服务的应用程序 9
  • 1.5.1 使用Spring Boot 来构建微服务 10
  • 1.5.2 云计算到底是什么 14
  • 1.5.3 为什么是云和微服务 16
  • 1.6 微服务不只是编写代码 17
  • 1.7 核心开发模式 18
  • 1.8 路由模式 19
  • 1.9 客户端弹性模式 21
  • 1.10 安全模式 22
  • 1.11 日志记录和跟踪模式 23
  • 1.12 应用程序度量模式 24
  • 1.13 构建/部署模式 25
  • 1.14 小结 26
  • 第2章 使用Spring Cloud 探索微服务世界 27
  • 2.1 什么是Spring Cloud 27
  • 2.1.1 Spring Cloud Config 28
  • 2.1.2 Spring Cloud 服务发现 29
  • 2.1.3 Spring Cloud LoadBalancer和Resilience4j 29
  • 2.1.4 Spring Cloud API Gateway 29
  • 2.1.5 Spring Cloud Stream 29
  • 2.1.6 Spring Cloud Sleuth 30
  • 2.1.7 Spring Cloud Security 30
  • 2.2 通过示例来介绍Spring Cloud 30
  • 2.3 如何构建云原生微服务 32
  • 2.3.1 代码库 34
  • 2.3.2 依赖 35
  • 2.3.3 配置 35
  • 2.3.4 后端服务 36
  • 2.3.5 构建、发布和运行 37
  • 2.3.6 进程 37
  • 2.3.7 端口绑定 38
  • 2.3.8 并发 38
  • 2.3.9 可任意处置 38
  • 2.3.10 开发环境/生产环境等同 38
  • 2.3.11 日志 39
  • 2.3.12 管理进程 39
  • 2.4 确保本书的示例是有意义的 40
  • 2.5 使用Spring Boot和Java来构建微服务 40
  • 2.5.1 设置环境 41
  • 2.5.2 从骨架项目开始 41
  • 2.5.3 引导Spring Boot 应用程序:编写引导类 45
  • 2.6 小结 46
  • 第3章 使用Spring Boot 构建微服务 48
  • 3.1 架构师的故事:设计微服务架构 49
  • 3.1.1 分解业务问题 49
  • 3.1.2 建立服务粒度 51
  • 3.1.3 定义服务接口 53
  • 3.2 何时不要使用微服务 53
  • 3.2.1 构建分布式系统时的复杂性 54
  • 3.2.2 服务器或容器散乱 54
  • 3.2.3 应用程序的类型 54
  • 3.2.4 数据事务和一致性 54
  • 3.3 开发人员的故事:用Spring Boot和Java构建微服务 55
  • 3.3.1 构建微服务的入口:Spring Boot 控制器 55
  • 3.3.2 将国际化添加到许可证服务 64
  • 3.3.3 实现Spring HATEOAS 来显示相关的链接 68
  • 3.4 DevOps 故事:构建运行时的严谨性 71
  • 3.4.1 服务装配:打包和部署微服务 72
  • 3.4.2 服务引导:管理微服务的配置 73
  • 3.4.3 服务注册和发现:客户端如何与微服务通信 74
  • 3.4.4 传达微服务的健康状况 75
  • 3.5 将视角综合起来 77
  • 3.6 小结 77
  • 第4章 欢迎来到Docker 79
  • 4.1 容器还是虚拟机 80
  • 4.2 Docker 是什么 81
  • 4.3 Dockerfile 83
  • 4.4 Docker Compose 84
  • 4.5 集成Docker 与微服务 86
  • 4.5.1 构建Docker 镜像 86
  • 4.5.2 使用Spring Boot 创建Docker镜像 91
  • 4.5.3 使用Docker Compose 启动服务 93
  • 4.6 小结 94
  • 第5章 使用Spring Cloud Config服务器端控制配置 96
  • 5.1 关于管理配置(和复杂性) 97
  • 5.1.1 配置管理架构 97
  • 5.1.2 实施选择 99
  • 5.2 构建Spring Cloud Config服务器端 100
  • 5.2.1 创建Spring Cloud Config引导类 104
  • 5.2.2 使用带有文件系统的Spring Cloud Config 服务器端 105
  • 5.2.3 创建服务的配置文件 106
  • 5.3 将Spring Cloud Config与Spring Boot客户端集成 110
  • 5.3.1 建立许可证服务的Spring Cloud Config 服务依赖项 111
  • 5.3.2 配置许可证服务以使用Spring Cloud Config 112
  • 5.3.3 使用Spring Cloud Config服务器端连接数据源 115
  • “5.3.4 使用@ConfigurationProperties直接读取属性 118”
  • 5.3.5 使用Spring Cloud Config服务器端刷新属性 119
  • 5.3.6 使用Spring Cloud Config服务器端和Git 120
  • 5.3.7 使用Spring Cloud Config服务集成Vault 122
  • 5.3.8 Vault UI 122
  • 5.4 保护敏感配置信息 125
  • 5.4.1 创建对称加密密钥 125
  • 5.4.2 加密和解密属性 126
  • 5.5 最后的想法 128
  • 5.6 小结 128
  • 第6章 关于服务发现 129
  • 6.1 我的服务在哪里 130
  • 6.2 云中的服务发现 132
  • 6.2.1 服务发现架构 132
  • 6.2.2 使用Spring 和Netflix Eureka进行服务发现实战 135
  • 6.3 构建Spring Eureka服务 136
  • 6.4 通过Spring Eureka注册服务 141
  • 6.4.1 Eureka 的REST API 144
  • 6.4.2 Eureka 仪表板 145
  • 6.5 使用服务发现来查找服务 146
  • 6.5.1 使用Spring Discovery Client查找服务实例 148
  • 6.5.2 使用带有Load Balancer功能的Spring Rest模板调用服务 150
  • 6.5.3 使用Netflix Feign 客户端调用服务 152
  • 6.6 小结 153
  • 第7章 当糟糕的事情发生时:使用Spring Cloud和Resilience4j的弹性模式 154
  • 7.1 什么是客户端弹性模式 155
  • 7.1.1 客户端负载均衡模式 156
  • 7.1.2 断路器模式 156
  • 7.1.3 后备模式 156
  • 7.1.4 舱壁模式 156
  • 7.2 为什么客户端弹性很重要 157
  • 7.3 实现Resilience4j 160
  • 7.4 设置许可证服务以使用Spring Cloud和Resilience4j 160
  • 7.5 实现断路器 162
  • 7.5.1 向组织服务添加断路器 166
  • 7.5.2 定制断路器 166
  • 7.6 后备处理 168
  • 7.7 实现舱壁模式 169
  • 7.8 实现重试模式 172
  • 7.9 实现限流器模式 174
  • 7.10 ThreadLocal和Resilience4j 176
  • 7.11 小结 180
  • 第8章 使用Spring Cloud Gateway进行服务路由 182
  • 8.1 什么是服务网关 183
  • 8.2 Spring Cloud Gateway简介 184
  • 8.2.1 建立Spring Cloud Gateway项目 185
  • 8.2.2 配置Spring Cloud Gateway与Eureka 进行通信 188
  • 8.3 在Spring Cloud Gateway 中配置路由 189
  • 8.3.1 通过服务发现自动映射路由 189
  • 8.3.2 使用服务发现手动映射路由 191
  • 8.3.3 动态重新加载路由配置 194
  • 8.4 Spring Cloud Gateway 的真正威力:断言和过滤器工厂 194
  • 8.4.1 内置的断言工厂 195
  • 8.4.2 内置的过滤器工厂 196
  • 8.4.3 自定义过滤器 197
  • 8.5 构建前置过滤器 200
  • 8.6 在服务中使用关联ID 203
  • 8.6.1 UserContextFilter:拦截传入的HTTP请求 205
  • 8.6.2 UserContext:使服务易于访问HTTP首部 206
  • 8.6.3 自定义RestTemplate和UserContextInteceptor:确保关联ID被传播 207
  • 8.7 构建接收关联ID的后置过滤器 208
  • 8.8 小结 210
  • 第9章 保护微服务 211
  • 9.1 OAuth2是什么 212
  • 9.2 Keycloak简介 213
  • 9.3 从小事做起:使用Spring和Keycloak来保护单个端点 214
  • 9.3.1 将Keycloak服务添加到Docker 214
  • 9.3.2 设置Keycloak 215
  • 9.3.3 注册客户端应用程序 218
  • “9.3.4 配置O-stock用户 222”
  • “9.3.5 对O-stock用户进行身份认证 224”
  • 9.4 使用Keycloak保护组织服务 227
  • 9.4.1 将Spring Security和Keycloak JAR添加到各个服务 228
  • 9.4.2 配置服务以指向Keycloak服务 228
  • 9.4.3 定义什么和谁可以访问服务 229
  • 9.4.4 传播访问令牌 233
  • 9.4.5 从JWT 中解析自定义字段 239
  • 9.5 关于微服务安全的一些总结 240
  • 9.5.1 对所有业务通信使用HTTPS/安全套接字层(SSL) 241
  • 9.5.2 使用服务网关访问微服务 241
  • 9.5.3 将服务划分到公共API和私有API 241
  • 9.5.4 通过封锁不需要的网络端口来限制微服务的攻击面 241
  • 9.6 小结 242
  • 第10章 使用Spring Cloud Stream的事件驱动架构 243
  • 10.1 消息传递、EDA和微服务的案例 244
  • “10.1.1 使用同步请求-响应方式来传达状态变化 244”
  • 10.1.2 使用消息传递在服务之间传达状态更改 246
  • 10.1.3 消息传递架构的缺点 248
  • 10.2 Spring Cloud Stream简介 249
  • 10.3 编写简单的消息生产者和消费者 251
  • 10.3.1 在Docker中配置Apache Kafka和Redis 252
  • 10.3.2 在组织服务中编写消息生产者 252
  • 10.3.3 在许可证服务中编写消息消费者 258
  • 10.3.4 在实际操作中查看消息服务 261
  • 10.4 Spring Cloud Stream用例:分布式缓存 262
  • 10.4.1 使用Redis来缓存查找 263
  • 10.4.2 定义自定义通道 269
  • 10.5 小结 271
  • 第11 章 使用Spring Cloud Sleuth和Zipkin进行分布式跟踪 272
  • 11.1 Spring Cloud Sleuth与关联ID 273
  • 11.1.1 将Spring Cloud Sleuth添加到许可证服务和组织服务中 274
  • 11.1.2 剖析Spring Cloud Sleuth跟踪 274
  • 11.2 日志聚合与Spring Cloud Sleuth 275
  • 11.2.1 Spring Cloud Sleuth/ELK技术栈实现实战 277
  • 11.2.2 在服务中配置Logback 278
  • 11.2.3 在Docker中定义和运行ELK技术栈应用程序 281
  • 11.2.4 配置Kibana 284
  • 11.2.5 在Kibana 中搜索Spring Cloud Sleuth的跟踪ID 287
  • 11.2.6 使用Spring Cloud Gateway将关联ID添加到HTTP响应 288
  • 11.3 使用Zipkin 进行分布式跟踪 290
  • 11.3.1 设置Spring Cloud Sleuth和Zipkin依赖项 291
  • 11.3.2 配置服务以指向Zipkin 291
  • 11.3.3 配置Zipkin服务器端 292
  • 11.3.4 设置跟踪级别 293
  • 11.3.5 使用Zipkin跟踪事务 293
  • 11.3.6 可视化更复杂的事务 296
  • 11.3.7 捕获消息传递踪迹 297
  • 11.3.8 添加自定义跨度 299
  • 11.4 小结 301
  • 第12章 部署微服务 303
  • 12.1 构建/部署管道的架构 304
  • “12.2 在云中设置O-stock的核心基础设施 307”
  • 12.2.1 使用亚马逊的RDS创建PostgreSQL数据库 309
  • 12.2.2 在AWS中创建Redis集群 312
  • “12.3 超越基础设施:部署O-stock和ELK 313”
  • 12.3.1 创建运行EKL的EC2实例 313
  • 12.3.2 在EC2实例中部署ELK技术栈 316
  • 12.3.3 创建一个EKS集群 317
  • 12.4 构建/部署管道实战 323
  • 12.5 创建构建/部署管道 324
  • 12.5.1 设置GitHub 325
  • 12.5.2 使服务能够在Jenkins中构建 326
  • 12.5.3 理解并生成管道脚本 330
  • 12.5.4 创建Kubernetes管道脚本 332
  • 12.6 关于构建/部署管道的总结 333
  • 12.7 小结 334
  • 附录A 微服务架构最佳实践 335
  • A.1 Richardson成熟度模型 335
  • A.2 Spring HATEOAS 337
  • A.3 外部化配置 337
  • A.4 持续集成和持续交付 338
  • A.5 监控 339
  • A.6 日志记录 339
  • A.7 API网关 340
  • 附录B OAuth2授权类型 341
  • B.1 密码授权类型 341
  • B.2 客户端凭据授权类型 343
  • B.3 授权码授权类型 344
  • B.4 隐式授权类型 345
  • B.5 如何刷新令牌 347
  • 附录C 监控微服务 349
  • C.1 引入Spring Boot Actuator进行监控 349
  • C.1.1 添加Spring Boot Actuator 349
  • C.1.2 启用Actuator端点 350
  • C.2 设置Micrometer和Prometheus 351
  • C.2.1 了解Micrometer和Prometheus 351
  • C.2.2 实现Micrometer和Prometheus 352
  • C.3 配置Grafana 354
  • C.4 小结 358

笔记 #

3.3.1 构建微服务的入口:Spring Boot控制器 #

为什么JSON用于微服务在基于HTTP的微服务之间来回发送数据时,其实有多种可选的协议。但是,由于以下几个原因,JSON已经成为事实上的标准。●与其他协议(如基于XML的SOAP(Simple Object Access Protocol,简单对象访问协议))相比,JSON是非常轻量级的。你可以在没有太多文本开销的情况下传递数据。●JSON易于人类阅读和使用。这在选择序列化协议时往往被低估。当出现问题时,开发人员必须查看一大堆JSON并快速处理其中的内容。JSON协议的简单性让这件事非常容易做到。●JSON是JavaScript中使用的默认序列化协议。由于JavaScript作为编程语言的急剧增长以及严重依赖JavaScript的单页互联网应用程序(Single Page Internet Application,SPIA)的同样快速增长,JSON已经天然适用于构建基于REST的应用程序,因为前端Web客户端用它来调用服务。

端点命名问题在深入编写微服务之前,要确保你(以及你组织中的其他可能的团队)为你想要通过你的服务公开的端点建立了标准。应该使用微服务的URL(Uniform Resource Locator,统一资源定位器)来明确传达服务的意图、服务管理的资源以及服务内管理的资源之间存在的关系。我们发现以下指导方针有助于命名服务端点。●使用明确的URL名称来确立服务所代表的资源。使用规范的格式定义URL将有助于API更直观,更易于使用,并且在你的命名约定中保持一致。●使用URL来确立资源之间的关系。通常,微服务中的资源之间会存在一种父子关系,其中子项不会存在于父项的上下文之外。因此,你可能没有针对该子项的单独的微服务。要使用URL来表达这些关系。如果你发现你的URL很长而且有嵌套,可能意味着你的微服务做的事情太多了。●尽早建立URL的版本控制方案。URL 及其对应的端点代表了服务的所有者和服务的消费者之间的契约。一种常见的模式是使用版本号作为前缀添加到所有端点上。要尽早建立版本控制方案,并且坚持下去。在若干消费者使用它们之后,再想将URL改造成版本控制(例如,在URL映射中使用/v1/)是非常困难的。

3.4 DevOps故事:构建运行时的严谨性 #

对DevOps工程师来说,微服务的设计就是在服务投入生产后如何管理服务。编写代码通常是很简单的,而保持代码运行却是困难的。我们将基于以下4条原则开始我们的微服务开发工作,并且在本书后面根据这些原则去构建。●微服务应该是独立的。它还应该是可独立部署的,多个服务实例可以使用单个软件制品进行启动和拆卸。●微服务应该是可配置的。当服务实例启动时,它应该从中央位置读取需要配置其自身的数据,或者让它的配置信息作为环境变量传递。配置服务应该无须人为干预。●微服务实例需要对客户端透明。客户端不应该知道服务的确切位置。相反,微服务客户端应该与服务发现代理通信。这允许应用程序定位微服务的实例,而不必知道微服务的物理位置。●微服务应该传达它的健康信息。这是云架构的关键部分。一旦微服务实例无法正常运行,发现代理需要绕过不良服务实例。在本书中,我们将使用Spring Boot Actuator来展示各个微服务的健康信息。

从DevOps的角度来看,必须预先解决微服务的运维需求,并将这4条原则转化为每次构建和部署微服务到环境中时发生的一套标准的生命周期事件。这4条原则可以映射到以下运维生命周期步骤。图3-9展示了这4个步骤是如何配合在一起的。●服务装配——如何打包和部署服务以保证可重复性和一致性,以便相同的服务代码和运行时被完全相同地部署。●服务引导——如何将应用程序和环境特定的配置代码与运行时代码分开,以便可以在任何环境中快速启动和部署微服务实例,而无须人为干预。●服务注册/发现——部署一个新的微服务实例时,如何让新的服务实例可以被其他应用程序客户端发现。●服务监控——在微服务环境中,由于高可用性需求,运行同一服务的多个实例非常常见。从DevOps的角度来看,需要监控微服务实例,并确保所有故障都围绕失败的服务实例进行路由,并且这些故障都已被记录。

4.1 容器还是虚拟机 #

虚拟机是一种软件环境,它允许我们在一台计算机中模拟另一台计算机的操作。虚拟机是基于虚拟机管理程序(hypervisor)的,它可以模拟一台完整的物理机器,分配所需的系统内存、处理器内核、磁盘存储和其他技术资源(如网络、PCI插件等)。在另一边,容器是包含虚拟操作系统(OS)的包,允许我们在隔离和独立的环境中运行一个应用程序及其依赖元素。这两种技术有相似之处,如存在允许执行这两种技术的虚拟机管理程序和容器引擎,但它们的实现方式使得虚拟机和容器截然不同。

通过在微服务中使用容器,我们将获得以下好处。● 容器可以在任何地方运行,这有助于开发和实现,并提高了可移植性。● 容器提供了创建可预测环境的能力,这些环境与其他应用程序完全隔离。● 容器可以比虚拟机更快地启动和停止,这使得它们成为云原生是可行的。● 容器是可伸缩的,可以主动调度和管理,以优化资源利用,提高在其中运行的应用程序的性能和可维护性。● 我们可以在最小数量的服务器上实现最大数量的应用程序。

5.1 关于管理配置(和复杂性) #

对于在云中运行的微服务,管理应用程序配置是至关重要的,因为微服务实例需要以最少的人为干预快速启动。每当人们需要手动配置或接触服务以实现部署时,都有可能出现配置漂移、意外中断以及应用程序响应可伸缩性挑战出现延迟的情况。下面我们通过建立要遵循的4条原则来开始有关应用程序配置管理的讨论。●分离——我们需要将服务配置信息与服务的实际物理部署完全分开。实际上,应用程序配置不应与服务实例一起部署。相反,配置信息应该作为环境变量传递给正在启动的服务,或者在服务启动时从集中式存储库中读取。●抽象——还需要将访问配置数据的功能抽象到一个服务接口中。我们应该使用基于REST的JSON服务来检索应用程序的配置数据,而不是编写直接读取服务存储库(无论是基于文件的还是JDBC数据库)的代码。●集中——因为基于云的应用程序实际可能会有数百个服务,所以最小化用于保存配置信息的不同存储库的数量至关重要。要将应用程序配置集中在尽可能少的存储库中。●稳定——因为应用程序的配置信息与部署的服务完全隔离并集中存放,所以至关重要的一点就是使用和实施的解决方案必须是高度可用和冗余的。

5.1.1 配置管理架构 #

(分离、抽象、集中和稳定),看看这4条原则在服务引导时是如何应用的。图5-2更详细地展示了引导过程,并展示了配置服务在此步骤中扮演的关键角色。

配置管理概念架构在图5-2中,发生了以下几件事情。下面是图中每个步骤的概要。(1)当一个微服务实例出现时,它调用一个服务端点来读取其所在环境的特定配置信息。配置管理的连接信息(连接凭据、服务端点等)将在微服务启动时被传递给微服务。(2)实际的配置信息驻留在存储库中。你可以基于配置存储库的实现,选择使用不同的方式来保存配置数据,这可以包括源代码控制下的文件、关系数据库或键值数据存储。(3)应用程序配置数据的实际管理与应用程序的部署方式无关。配置管理的更改通常通过构建和部署管道来处理,其中更改可以通过版本信息进行标记,并通过不同的环境(开发环境、交付准备环境、生产环境等)进行部署。(4)进行配置管理更改时,必须将变更通知给使用该应用程序配置数据的服务,并刷新应用程序数据的副本。

5.2 构建Spring Cloud Config服务器端 #

bootstrap文件是特定的Spring Cloud文件类型,它在application.properties或application. yml文件之前加载。bootstrap文件用于指定Spring应用程序名称、Spring Cloud配置的Git位置、加密/解密信息等。具体来说,bootstrap文件是由父Spring ApplicationContext加载的,并且该父ApplicationContext在加载使用应用程序属性或YAML文件的ApplicationContext之前加载。

5.3 将Spring Cloud Config与Spring Boot客户端集成 #

为什么使用PostgreSQL PostgreSQL,也称为 Postgres,被认为是企业级系统,是开源关系数据库管理系统(relational database management system,RDBMS)中最有趣和最先进的一种。与其他关系数据库相比,PostgreSQL拥有诸多优点,但最主要的是它提供了一个完全免费的开放给任何人使用的许可证。第二个优点是,就其能力和功能而言,它允许我们在不增加查询复杂性的情况下处理更大量的数据。下面是Postgres的一些主要特性。● Postgres 采用多版本并发控制,它将数据库状态的快照添加到每个事务中,从而产生具有更好性能优势的一致事务。● Postgres在执行事务时不使用读锁。● Postgres 有一个叫作热备份的功能,它允许客户端在服务器处于恢复或备用模式时在服务器中进行搜索。换句话说,Postgres在不完全锁定数据库的情况下执行维护。下面是PostgreSQL的一些主要特点。● 它支持C、C++、Java、PHP、Python等语言。

● 它能够为许多客户端提供服务,同时从其表中无阻塞地传递相同的信息。● 它支持使用视图,因此用户可以以不同于数据被存储的方式查询数据。● 它是对象关系数据库,允许我们像处理对象一样处理数据,从而提供面向对象的机制。● 它允许我们将JSON作为数据类型存储和查询。

5.3.5 使用Spring Cloud Config服务器端刷新属性 #

Spring Boot Actuator提供了一个@RefreshScope注解,允许开发团队访问/refresh端点,这会强制Spring Boot应用程序重新读取其应用程序配置。

我们需要注意一些有关@RefreshScope注解的事情。注解只会重新加载应用程序配置中的自定义Spring属性。Spring Data使用的数据库配置等不会被@RefreshScope注解重新加载。

第6章 关于服务发现 #

在任何分布式架构中,我们都需要找到机器所在的位置的主机名和IP地址。这个概念自分布式计算开始出现就已经存在,并且被正式称为“服务发现”。服务发现可以非常简单,只需要维护一个属性文件,这个属性文件包含应用程序使用的所有远程服务的地址,也可以很正式,像通用描述、发现与集成服务(Universal Description,Discovery, and Integration,UDDI)存储库一样。服务发现对于微服务和基于云的应用程序至关重要,主要原因有两个。●水平扩展或横向扩展——此模式通常需要在应用程序架构中进行调整,例如,在云服务中添加更多服务实例和更多容器。●弹性——此模式指的是在不影响业务的情况下吸收架构或服务中问题的影响的能力。微服务架构需要非常敏感,以防止单个服务(或服务实例)中的问题向上或向外级联影响到服务的使用者。

6.2 云中的服务发现 #

基于云的微服务环境的解决方案是使用服务发现机制,这一机制具有以下特点。●高可用——服务发现需要能够支持“热”集群环境,在这种环境中,服务查找可以在服务发现集群中跨多个节点共享。如果一个节点变得不可用,集群中的其他节点应该能够接管工作。集群可以定义为一组多服务器实例。此环境的所有实例都具有相同的配置,并协同工作,以提供高可用性、可靠性和可伸缩性。与负载均衡器相结合的集群可以提供故障切换以防止服务中断,还可以提供会话复制以存储会话数据。●点对点——服务发现集群中的每个节点共享一个服务实例的状态。●负载均衡——服务发现需要在所有服务实例之间动态地对请求进行负载均衡,这能确保服务调用分布在由它管理的所有服务实例上。在许多方面,服务发现取代了许多早期Web应用程序实现中使用的更静态的、手动管理的负载均衡器。●有弹性——服务发现的客户端应该在本地缓存服务信息。本地缓存允许服务发现功能逐步降级,这样,如果服务发现服务变得不可用,应用程序仍然可以基于其本地缓存中维护的信息来运行和定位服务。

●容错——服务发现需要检测出服务实例什么时候是不健康的,并从可以接收客户端请求的可用服务列表中移除该实例。服务发现应该在没有人为干预的情况下,对这些故障进行检测,并采取行动。

6.2.1 服务发现架构 #

服务发现架构,我们需要了解4个概念。这些一般概念在所有服务发现实现中是共通的。●服务注册——服务如何使用服务发现代理进行注册。●客户端查找服务地址——服务客户端如何查找服务信息。●信息共享——节点如何共享服务信息。●健康监测——服务如何将它们的健康信息传回服务发现代理。

当一个服务消费者需要调用一个服务时,会经过以下步骤。(1)它将联系发现服务,获取服务消费者(客户端)请求的所有实例,然后在服务消费者的机器上本地缓存数据。(2)每当客户端需要调用该服务时,服务消费者将从缓存中查找该服务的位置信息。通常,客户端缓存将使用简单的负载均衡算法,如“轮询”负载均衡算法,以确保服务调用分布在多个服务实例之间。(3)然后,客户端将定期与发现服务进行联系,并刷新其服务实例的缓存。客户端缓存最终是一致的,但是始终存在这样的风险:在客户端联系服务发现实例进行刷新和调用时,调用可能会被定向到不健康的服务实例上。如果在调用服务的过程中,服务调用失败,那么本地的服务发现缓存会失效,服务发现客户端将尝试从服务发现代理刷新它的数据。

6.2.2 使用Spring和Netflix Eureka进行服务发现实战 #

(1)随着服务的启动,许可证和组织服务将通过Eureka服务进行注册。这个注册过程将告诉Eureka每个服务实例的物理位置和端口号,以及正在启动的服务的服务ID。(2)当许可证服务调用组织服务时,它将使用Spring Cloud LoadBalancer来提供客户端负载均衡。Load Balancer将联系Eureka服务去检索服务位置信息,然后在本地进行缓存。(3)Spring Cloud LoadBalancer库将定期对Eureka服务进行ping操作,并刷新服务位置的本地缓存。

6.5 使用服务发现来查找服务 #

3个不同的Spring/Netflix客户端库,服务消费者可以使用它们来和Spring Cloud LoadBalancer进行交互。从最低级别到最高级别,这些库包含了不同的与Load Balancer进行交互的抽象层次。这里将要探讨的库包括:● Spring Discovery Client;● 启用了Discovery Client的REST模板;● Netflix Feign客户端。

6.5.1 使用Spring Discovery Client查找服务实例 #

Discovery Client与实际运用在服务需要查询Load Balancer以了解哪些服务和服务实例已经通过它注册时,应该只使用Discovery Client。代码清单6-12中的代码存在以下几个问题。●没有利用Spring Cloud客户端负载均衡。尽管通过直接调用Discovery Client可以获得服务列表,但是选择要调用哪个返回服务实例就成了开发人员的责任。●开发人员做了太多的工作。在代码中,开发人员必须构建一个用来调用服务的URL。尽管这是一件小事,但是编写的代码越少意味着需要调试的代码就越少。

6.5.2 使用带有Load Balancer功能的Spring Rest模板调用服务 #

启用Load Balancer的RestTemplate类将解析传递给它的URL,并使用传递的内容作为服务器名称,该服务器名称作为从Load Balancer查询服务实例的键。实际的服务位置和端口与开发人员完全抽象隔离。此外,通过使用RestTemplate类,Spring Cloud LoadBalancer将在所有服务实例之间轮询负载均衡所有请求。

第7章 当糟糕的事情发生时:使用Spring Cloud和Resilience4j的弹性模式 #

当服务崩溃时,很容易检测到该服务已经不在了,因此应用程序可以绕过它。然而,当服务运行缓慢时,检测到这个服务性能不佳并绕过它是非常困难的。让我们看看为什么会这样。●服务降级可能起初是间歇性的,然后形成势头。服务降级也可能只发生在很小的爆发中。故障的第一个迹象可能是一小部分用户抱怨某个问题,直到突然间应用程序容器耗尽了线程池并彻底崩溃。●对远程服务的调用通常是同步的,并且不会缩短长时间运行的服务的调用时间。应用程序开发人员通常调用一个服务来执行一个操作并等待服务返回。服务的调用者没有超时的概念来阻止服务调用挂起。●应用程序经常被设计为处理远程资源的彻底故障,而不是部分降级。通常,只要服务没有彻底失败,应用程序就会继续调用一个表现不佳的服务,并且不会快速失败。在这种情况下,该调用应用程序或服务可能会优雅地降级,但更有可能因为资源耗尽而崩溃。资源耗尽是指有限的资源(如线程池或数据库连接)消耗殆尽,而调用客户端必须等待该资源再次变得可用。

7.1 什么是客户端弹性模式 #

4种客户端弹性模式。图7-1演示了如何将这些模式用于微服务消费者和微服务之间。image.png|600 图7-1 这4种客户端弹性模式充当服务消费者和服务之间的保护缓冲区这些模式(客户端负载均衡、断路器、后备和舱壁)是在调用远程资源的客户端(微服务)中实现的。这些模式的实现在逻辑上位于消费远程资源的客户端和资源本身之间。

7.1.2 断路器模式 #

断路器模式以电路断路器为模型。在电气系统中,断路器检测是否有过多电流流过电线。如果断路器检测到问题,它将断开与电气系统的其余部分的连接,并保护下游部件不被烧毁。有了软件断路器,当远程服务被调用时,断路器将监控这个调用。如果调用时间太长,断路器将会介入并中断调用。此外,断路器模式将监控所有对远程资源的调用,如果调用失败次数足够多,那么断路器实现就会“出现”并采取快速失败,阻止将来调用失败的远程资源。

7.1.3 后备模式 #

后备模式,当远程服务调用失败时,服务消费者将执行替代代码路径,并尝试通过其他方式执行操作,而不是生成一个异常。这通常涉及从另一数据源查找数据或将用户的请求进行排队以供将来处理。用户的调用不会显示指示问题的异常,但用户可能会被告知,必须晚些时候再尝试他们的请求。例如,让我们假设你有一个电子商务网站,它可以监控你的用户的行为,并向用户推荐他们可能想买的其他产品。通常来说,你将调用微服务来对用户过去的行为进行分析,并返回针对特定用户的推荐列表。但是,如果这个偏好服务失败,那么你的后备可能是检索一个更通用的偏好列表,该列表基于所有用户购买,这是更普遍的。而且,这个数据可能来自完全不同的服务和数据源。

7.1.4 舱壁模式 #

舱壁模式是建立在造船的概念基础上的。一艘船被划分为完全隔离和防水的隔间,这称为舱壁。即使船的船体被击穿,舱壁也会将水限制在被击穿的船的区域内,防止整艘船灌满水并沉没。同样的概念可以应用于必须与多个远程资源交互的服务。通过使用舱壁模式,可以把远程资源的调用分到它们自己的线程池中,从而降低一个缓慢的远程资源调用拖垮整个应用程序的风险。线程池充当了服务的舱壁。每个远程资源都是隔离的,并分配给一个线程池。如果一个服务响应缓慢,那么这种服务调用的线程池就会饱和并停止处理请求。将服务分配给线程池有助于绕过这种类型的瓶颈,从而使其他服务不会饱和。

7.2 为什么客户端弹性很重要 #

在图7-3中,许可证服务永远不会直接调用组织服务。相反,在进行调用时,许可证服务把服务的实际调用委托给断路器,断路器将接管这个调用,并将它包装在独立于原始调用者的线程(通常由线程池管理)中。通过将调用包装在一个线程中,客户端不再直接等待调用完成。相反,断路器会监控线程,如果线程运行时间太长,断路器就会终止该调用。

image.png|700

图7-3 断路器跳闸,让表现不佳的服务调用迅速而优雅地失败

断路器模式为远程调用提供的主要好处。(1)快速失败——当远程服务处于降级状态时,应用程序将会快速失败,并防止通常会拖垮整个应用程序的资源耗尽问题的出现。在大多数中断情况下,最好是部分服务关闭而不是完全关闭。(2)优雅地失败——通过超时和快速失败,断路器模式使我们能够优雅地失败,或寻求替代机制来执行用户的意图。如果用户正尝试从一个数据源检索数据,并且该数据源正在经历服务降级,那么我们的服务可以尝试从其他地方检索该数据。(3)无缝恢复——有了断路器模式作为中介,断路器可以定期检查所请求的资源是否重新上线,并在没有人为干预的情况下重新允许对该资源进行访问。

7.3 实现Resilience4j #

Resilience4j是一个受Hystrix启发的容错库。它提供了以下模式,以提高网络问题或多个服务故障的容错能力。●断路器——当被调用的服务发生失败时,停止发出请求。●重试——在服务暂时失败时重试服务。●舱壁——限制传出的并发服务请求数以避免过载。●限流——限制一个服务在一定时间内接收的调用数。●后备——为失败的请求设置备用路径。

7.5 实现断路器 #

在Resilience4j中,断路器是通过有3个一般状态的有限状态机实现的。图7-4展示了不同的状态以及它们之间的交互。 image.png|500

图7-4 Resilience4j断路器的状态:闭合、断开和半断开最初,Resilience4j断路器以闭合状态启动并等待客户端请求。闭合状态使用一个环形比特缓冲区来存储请求的成功或失败状态。当请求成功时,断路器在环形比特缓冲区中保存一个0比特;如果它无法从被调用的服务接收响应,那么断路器在环形比特缓冲区中保存一个1比特。图7-5展示了一个包含12个结果的环形缓冲区。 image.png|300

图7-5 有12个结果的Resilience4j断路器环形比特缓冲区。要计算失败率,这个环必须是满的。例如,在前面的场景中,至少需要评估12次调用才能计算出失败率。如果只评估11次调用,即使所有11个调用都失败,断路器也不会变为断开状态。注意,只有当失败率高于可配置阈值时,断路器才会断开。

在半断开状态下,断路器使用另一个可配置的环形比特缓冲区来评估失败率。如果这个新的失败率大于配置的阈值,则断路器变回断开状态;如果小于或等于配置的阈值,则断路器变回闭合状态。这可能有些混乱,但请记住,在断开状态下断路器拒绝请求,而在闭合状态下,断路器接受所有请求。此外,在Resilience4j断路器模式中,你可以定义以下额外状态。需要注意的是,退出以下状态的唯一方法是重置断路器或触发状态转换。●无效(DISABLED)——始终允许访问。●强制断开(FORCED_OPEN)——始终拒绝访问。

7.7 实现舱壁模式 #

舱壁模式将远程资源调用隔离在它们自己的线程池中,以便可以控制单个表现不佳的服务,而不会使该容器崩溃。Resilience4j提供了舱壁模式的两种不同实现。你可以使用这些实现来限制并发执行的数目。●信号量舱壁——使用信号量隔离方法,限制服务的并发请求数量。一旦达到限制,它就开始拒绝请求。●线程池舱壁——使用有界队列和固定线程池。这种方法只在线程池和队列都满时才会拒绝请求。

7.8 实现重试模式 #

重试模式负责在服务最初失败时重新尝试与服务通信。此模式背后的关键概念是提供一种获得预期响应的方法,即尽管出现故障(如网络中断),仍然尝试调用相同服务一次或多次。对于此模式,必须指定给定服务实例的重试次数以及每次重试之间的时间间隔。

7.9 实现限流器模式 #

舱壁模式和限流器模式之间的主要区别在于,舱壁模式负责限制并发调用的数量(例如,它一次只允许X个并发调用),而限流器模式负责限制给定时帧内的总调用数(例如,每Y秒允许X个调用数)。为了选择适合你的模式,请仔细检查你的需求是什么。如果你想阻塞并发时间,那么最佳选择是舱壁模式,但如果你想限制特定时间段内的总调用数,那么最佳选择是限流器模式。如果你同时考虑这两种场景,还可以将它们组合起来。

7.10 ThreadLocal和Resilience4j #

在基于REST的环境中,我们通常希望将上下文信息传递给服务调用,这将有助于在运维上管理该服务。例如,可以在REST调用的HTTP首部中传递关联ID(correlation ID)或验证令牌,然后将其传播到下游服务调用。关联ID允许我们有一个唯一的标识符,该标识符可用于在单个事务中跨多个服务调用进行跟踪。为了让关联ID在服务调用中的任何地方都可用,我们可以使用Spring Filter类来拦截REST服务中的每个调用,并从传入的HTTP请求中检索关联ID,然后将此上下文信息存储在自定义的UserContext对象中。之后,每当我们的代码需要在我们的REST服务调用中访问这个关联ID时,就可以从ThreadLocal存储变量中检索UserContext并读取该值。

7.11 小结 #

● 3种核心客户端弹性模式分别是断路器模式、后备模式和舱壁模式。● 断路器模式试图杀死运行缓慢和降级的系统调用,这样调用就会快速失败,并防止资源耗尽。● 后备模式让我们可以在远程服务调用失败或调用的断路器失败的情况下,定义替代代码路径。● 舱壁模式通过将对远程服务的调用隔离到它们自己的线程池中,使远程资源调用彼此分离。就算一组服务调用失败,这些失败也不会导致应用程序容器中的所有资源耗尽。● 限流器模式限制给定时间段内的总调用数。● Resilience4j允许我们同时堆叠和使用多个模式。● 重试模式负责在服务暂时失败时进行尝试。● 舱壁模式和限流器模式之间的主要区别在于,舱壁模式负责限制一次并发调用的数量,而限流器模式负责限制给定时间内的总调用数。● Spring Cloud和Resilience4j库提供断路器模式、后备模式、舱壁模式、重试模式、限流器模式的实现。● Resilience4j库是高度可配置的,可以在全局、类和线程池级别设置。

第8章 使用Spring Cloud Gateway进行服务路由 #

在像微服务这样的分布式架构中,需要确保跨多个服务调用的关键行为(如安全、日志记录和用户跟踪)的正常运行。要实现此功能,我们需要在所有服务中始终如一地强制这些特性,而不需要每个开发团队都构建自己的解决方案。虽然可以使用公共库或框架来帮助在单个服务中直接构建这些功能,但这样做会造成3个影响。●在每个服务中很难一致地实现这些功能。开发人员专注于交付功能,除非工作在需要受监管的行业,否则在每日的快速开发工作中,开发人员很容易忘记实现服务日志记录或跟踪。●将实现横切关注点(如安全性和日志记录)的责任推给各个开发团队,会大大增加有人没有正确实现或忘记实现这些功能的可能性。横切关注点是指程序设计中用于整个应用程序并可能影响应用程序的其他部分的部分或功能。●可能会在所有服务中创建一个顽固的依赖。在所有服务中共享的公共框架中构建的功能越多,在通用代码中无须重新编译和重新部署所有服务就能更改或添加功能就越困难。突然间,共享库中内置的核心功能的升级就变成了一个漫长的迁移过程。

为了解决这个问题,需要将这些横切关注点抽象成一个服务,该服务可以独立存在并充当我们的架构中所有微服务调用的过滤器和路由器。我们把这个服务称为网关(gateway)。我们的服务客户端不再直接调用微服务。取而代之的是,服务网关作为单个策略执行点(Policy Enforcement Point,PEP),所有调用都通过服务网关进行路由,然后被路由到最终目的地。

8.1 什么是服务网关 #

服务网关充当应用程序内所有微服务调用的入站流量的守门人。有了服务网关,服务客户端永远不会直接调用单个服务的URL,而是将所有调用都放到服务网关上。由于服务网关位于客户端到各个服务的所有调用之间,因此它还充当服务调用的中央策略执行点。使用集中式策略执行点意味着横切服务关注点可以在一个地方实现,而无须各个开发团队来实现这些关注点。举例来说,可以在服务网关中实现的横切关注点包括以下几个。●静态路由——服务网关将所有的服务调用放置在单个URL和API路由的后面。这简化了开发,因为我们只需要知道所有服务的一个服务端点就可以了。●动态路由——服务网关可以检查传入的服务请求,根据来自传入请求的数据,为服务调用者执行智能路由。例如,参与测试版程序的客户可能会将所有对服务的调用路由到一个特定的服务集群,这些服务集群运行的代码版本与其他人使用的代码版本不同。

●验证和授权——由于所有服务调用都经过服务网关进行路由,因此服务网关是检查服务调用者是否已经对自己进行了验证的自然场所。●度量数据收集和日志记录——当服务调用通过服务网关时,可以使用服务网关来收集度量数据和日志信息,还可以使用服务网关确保在用户请求上提供关键信息以确保日志统一。这并不意味着你不应该从单个服务中收集度量数据。相反,通过服务网关你可以集中收集许多基本度量数据,如服务调用次数和服务响应时间。

等等——难道服务网关不是单点故障和潜在瓶颈吗在第6章中介绍Eureka时,我们讨论了集中式负载均衡器是如何成为服务的单点故障和服务瓶颈的。如果一个服务网关没有正确地实现,它可能会带来同样的风险。在构建服务网关实现时,要牢记以下几点。●在单独的服务组前面,负载均衡器很有用。在这种情况下,将负载均衡器放到多个服务网关实例的前面是一个恰当的设计,可确保服务网关实现可以根据需要伸缩。但是,将负载均衡器置于所有服务实例的前面并不是一个好主意,因为它会成为瓶颈。●要保持为服务网关编写的代码是无状态的。不要在内存中为服务网关存储任何信息。如果你不小心,就有可能限制网关的可伸缩性。然后,你需要确保数据在所有服务网关实例中被复制。●要保持为服务网关编写的代码是轻量的。服务网关是服务调用的“阻塞点”。具有多个数据库调用的复杂代码可能是服务网关中难以追踪的性能问题的根源。

8.2 Spring Cloud Gateway简介 #

Spring Cloud Gateway是建立在Spring 5、Project Reactor和Spring Boot 2.0上的API网关实现。这个网关是一个非阻塞网关。何为非阻塞?非阻塞应用程序的编写方式使得主线程永远不会被阻塞。相反,这些线程始终可用于服务请求,并在后台异步处理它们,以便在处理完成后返回响应。

Spring Cloud Gateway提供了一些功能,具体包括以下几个。●将应用程序中的所有服务的路由映射到单个URL。但是,Spring Cloud Gateway不局限于单个URL。实际上,我们可以使用Spring Cloud Gateway定义多个路由入口点,使路由映射非常细粒度(每个服务端点都有自己的路由映射)。然而,第一个也是最常见的用例是构建一个单一的入口点,所有服务客户端调用都将经过这个入口点。●构建可以对通过网关的请求和响应进行检查和操作的过滤器。这些过滤器允许我们在代码中注入策略执行点,以一致的方式对所有服务调用执行大量操作。换句话说,这些过滤器允许我们修改传入的HTTP请求和传出的HTTP响应。●构建断言(predicate),断言是让我们可以在执行或处理请求之前检查请求是否满足一组给定条件的对象。Spring Cloud Gateway包括一组内置的路由断言工厂(Route Predicate Factory)。

8.4 Spring Cloud Gateway的真正威力:断言和过滤器工厂 #

Spring Cloud Gateway在请求通过网关时,应用的断言和过滤器的架构。首先,网关客户端(如浏览器、应用程序等)向Spring Cloud Gateway发送请求。一旦接收到请求,它将直接转到网关处理器(Gateway Handler),该处理器负责验证所请求的路径是否与它试图访问的特定路由的配置相匹配。如果所有内容都匹配,则它进入网关Web处理器(Gateway Web Handler),该处理器负责读取过滤器并将请求发送给这些过滤器进行进一步处理。一旦请求通过了所有过滤器,它就被转发到路由配置:一个微服务。 image.png

8.4.3 自定义过滤器 #

Spring Cloud Gateway允许我们使用网关中的过滤器来构建自定义逻辑。请记住,过滤器允许我们实现一条业务逻辑链,在过滤器实施时,每个服务请求都会经过这条业务逻辑链。Spring Cloud Gateway支持以下两种类型的过滤器。图8-10显示了在处理服务客户端的请求时,前置过滤器和后置过滤器是如何组合在一起的。●前置过滤器——前置过滤器在实际请求发送到目的地之前被调用。前置过滤器通常执行确保服务具有一致的消息格式(例如,关键的HTTP首部是否设置妥当)的任务,或者充当“看门人”,确保调用该服务的用户已通过验证(他们的身份与他们声称的一致)。●后置过滤器——后置过滤器在目标服务被调用并将响应发送回客户端后被调用。通常,我们实现后置过滤器来记录从目标服务返回的响应、处理错误或审核对敏感信息的响应。 image.png

8.6 在服务中使用关联ID #

重复代码与共享库对比是否应该在微服务中使用公共库的话题是微服务设计中的一个灰色地带。微服务纯粹主义者会告诉你,不应该在服务中使用自定义框架,因为它会在服务中引入人为的依赖。业务逻辑的更改或bug修正可能会对所有服务造成大规模的重构。另外,其他微服务实践者会指出,纯粹主义者的方法是不切实际的,因为会存在这样一些情况(如前面的UserContextFilter示例),在这些情况下构建公共库并在服务之间共享它是有意义的。我们认为这里存在一个中间地带。在处理基础设施风格的任务时,是很适合使用公共库的。但是,如果开始共享面向业务的类,就是在自找麻烦,因为这样是在打破服务之间的界限。然而,在本章的代码示例中,我们似乎违背了自己的建议。如果你查看本章中的所有服务,就会发现它们都有自己的UserContextFilter、UserContext和UserContextInterceptor类的副本。image.png|500

第9章 保护微服务 #

保护微服务架构是一项复杂且费力的任务,涉及多个保护层,包括:●应用程序层——确保有正确的用户控制,以便可以确认用户是他们所说的人,并且他们有权执行正在尝试执行的操作;●基础设施层——使服务保持运行、打补丁和更新,以最大限度地降低漏洞风险;●网络层——实现网络访问控制,使服务只能通过定义良好的端口进行访问,并且只让少量已授权的服务器访问。

9.1 OAuth2是什么 #

OAuth2是一个基于令牌的安全框架,它描述了授权的模式,但没有定义如何实际执行身份认证。相反,它允许用户通过第三方认证服务对自己进行身份认证,这种第三方认证服务称为身份供应商(identity provider,IdP)。如果用户成功通过身份认证,他们会收到一个必须随每个请求一起发送的令牌。然后令牌可以被传回身份认证服务进行身份认证。OAuth2背后的主要目标是,当调用多个服务来满足一个用户的请求时,每个服务都可以对这个用户进行身份认证,而这个用户则无须向处理请求的每个服务提供凭据。OAuth2允许我们使用称为授权(grant)的身份认证方案,在不同的场景中保护我们基于REST的服务。OAuth2规范具有以下4种类型的授权:● 密码(password);● 客户端凭据(client credential);● 授权码(authorization code);● 隐式(implicit)。

9.2 Keycloak简介 #

Keycloak是为服务和应用提供的一个开源身份和访问管理的解决方案。Keycloak的主要目标是在很少或没有代码的情况下,促进对服务和应用程序的保护。Keycloak的一些特征包括:● 它集中身份认证并支持单点登录(single sign-on,SSO)身份认证;● 它让开发人员可以专注于业务功能,而不必担心授权和身份认证等安全方面;● 它支持双因子身份认证;● 它兼容LDAP;● 它提供了几个适配器来轻松地保护应用程序和服务器端;● 它支持自定义密码策略。Keycloak安全性可以分解为4个组件:受保护的资源、资源所有者、应用程序和身份认证/授权服务器。

9.5 关于微服务安全的一些总结 #

在构建用于生产用途的微服务时,应该围绕以下实践构建微服务安全。(1)对所有服务通信使用HTTPS/安全套接字层(Secure Sockets Layer,SSL)。(2)对所有服务调用使用一个API网关。(3)为你的服务提供区域(如公共API和私有API)。(4)通过封锁不需要的网络端口来限制微服务的攻击面。图9-28展示了这些不同的实践如何配合起来工作。上述列表中的每个编号都与图9-28中的编号对应。我们将在接下来的几节中更详细地审查前面列表和图9-28中列出的每个主题领域。 image.png

9.6 小结 #

● OAuth2是一个基于令牌的身份认证框架,它为保护Web服务调用提供了不同的机制,这些机制称为授权(grant)。● OpenID Connect(OIDC)是OAuth2框架之上的一层,它提供关于谁登录到应用程序(身份)的身份认证和简介信息。● Keycloak是用于微服务和应用程序的开源身份和访问管理解决方案。Keycloak的主要目标是在很少或没有代码的情况下,促进对服务和应用程序的保护。● 每个应用程序可以拥有自己的Keycloak应用程序名称和密钥。● 每个服务必须定义角色可以采取的动作。● Spring Cloud Security支持JSON Web Token(JWT)规范。使用JWT,可以将自定义字段注入规范中。● 保护微服务涉及的不仅仅是使用身份认证和授权。● 在生产环境中,我们应该使用HTTPS来对服务间的所有调用进行加密。● 使用服务网关来缩小可以到达服务的访问点的数量。● 通过限制运行服务的操作系统上的入站端口和出站端口数来限制服务的攻击面。

10.2 Spring Cloud Stream简介 #

Spring Cloud Stream,有4个涉及发布消息和消费消息的组件:● 发射器(source);● 通道(channel);● 绑定器(binder);● 接收器(sink)。当一个服务准备发布消息时,它将使用一个发射器发布消息。发射器是一个Spring注解接口,它接收一个普通Java对象(POJO),该对象代表要发布的消息。发射器接收消息,然后序列化它(默认的序列化是JSON)并将消息发布到通道。通道是对队列的一个抽象,它将在消息生产者发布消息或消息消费者消费消息后保留该消息。换句话说,我们可以将通道描述为发送和接收消息的队列。通道名称始终与目标队列名称相关联,但队列名称永远不会直接公开给代码。相反,通道名称会在代码中使用,这意味着我们可以通过更改应用程序的配置而不是应用程序的代码来切换通道读取或写入的队列。

image.png

绑定器是Spring Cloud Stream框架的一部分,它是与特定消息平台对话的Spring代码。Spring Cloud Stream框架的绑定器部分允许我们处理消息,而不必依赖于特定于平台的库和API来发布和消费消息。在Spring Cloud Stream中,服务通过一个接收器从队列中接收消息。接收器监听传入消息的通道,并将消息反序列化为POJO对象。从这里开始,消息就可以按照Spring服务的业务逻辑来进行处理。

第11章 使用Spring Cloud Sleuth和Zipkin进行分布式跟踪 #

● Spring Cloud Sleuth——Spring Cloud Sleuth项目将跟踪ID(也称为关联ID)装备到传入的HTTP调用上。Spring Cloud Sleuth通过添加过滤器并与其他Spring组件进行交互,将生成的关联ID传递到所有系统调用。● Zipkin——Zipkin是一种开源数据可视化工具,可以显示一个跨多个服务的事务流。使用Zipkin 我们可以将一个事务分解到它的组件块中,并可视化地识别可能存在性能热点的位置。●ELK技术栈——ELK技术栈结合了3个开源工具:Elasticsearch、Logstash和Kibana。通过这些工具的集合我们可以实时分析、搜索和可视化日志。❏ Elasticsearch是一个分布式分析引擎,适用于所有类型的数据(结构化的、非结构化的、数字的、基于文本的等)。❏ Logstash是一个服务器端数据处理管道,它允许我们添加多个数据源,支持同时从多个数据源获取数据,并在Elasticsearch将数据编入索引之前对数据进行转换。❏ Kibana是Elasticsearch的可视化和数据管理工具。它提供图表、地图和实时直方图。

11.1.2 剖析Spring Cloud Sleuth跟踪 #

Spring Cloud Sleuth向每个日志条目添加了以下4条信息(下列编号与图11-1中的编号相对应)。(1)输入日志条目的服务的应用程序的名称。在默认情况下,Spring Cloud Sleuth使用应用程序的名称(spring.application.name)作为在跟踪中写入的名称。(2)跟踪ID(trace ID),它是关联ID的等价术语。它是表示整个事务的唯一编号。(3)跨度ID(span ID),它是表示整个事务中某一部分的唯一ID。参与事务的每个服务都将具有自己的跨度ID。如果你集成Zipkin来可视化事务,那么跨度ID尤其重要。(4)输出一个true/false指示器,用于确定是否将跟踪信息发送到Zipkin。在大容量服务中,生成的跟踪数据量可能是海量的,并且不会增加大量的价值。Spring Cloud Sleuth让我们确定何时以及如何将事务发送给Zipkin。

11.2.3 在Docker中定义和运行ELK技术栈应用程序 #

Logstash管道有两个必需元素和一个可选元素,其中,必需元素是input(输入)和output(输出)。●输入元素允许Logstash读取特定的事件源。Logstash支持各种输入插件,如GitHub、HTTP、TCP和Kafka等。●输出元素负责将事件数据发送到特定的目的地。Logstash支持各种输出插件,如CSV、Elasticsearch、email、file、MongoDB、Redis和stdout等。Logstash配置中的可选元素是filter(过滤器)插件。这些过滤器负责对事件执行中间处理,如翻译、添加新信息、解析日期、截断字段等。请记住,Logstash接收并转换接收的日志数据。图11-6描述了Logstash的流程。 image.png

第12章 部署微服务 #

用于构建和部署代码的机制应该具有下列特征。●自动的——在构建代码时,构建/部署过程不应该有人为干预。构建软件、供应机器镜像以及部署服务的过程应该是自动的,并且应该通过将代码提交到源代码存储库的行为来启动。●可重复的——用来构建和部署软件的过程应该是可重复的,以便每次构建和部署启动时都会发生同样的事情。过程中的可变性常常是难以跟踪和解决的微小bug的根源。●完整的——部署的软件制品的成果应该是一个完整的虚拟机或容器镜像(如Docker),其中包含该服务的“完整的”运行时环境。这是我们思考基础设施方式的一个重要转变。我们的机器镜像的供应需要通过脚本实现完全自动化,并且这个脚本与服务源代码一起处于源代码控制之下。在微服务环境中,这种职责通常会从运维团队转移到拥有该服务的开发团队。请记住,微服务开发的核心原则之一是将服务的全部运维责任推给开发人员。

●不可变的——包含服务的机器镜像一旦构建,在镜像部署完后,镜像的运行时配置就不应该被触碰或更改。如果需要进行更改,则需要在源代码控制下的脚本中进行配置,并且服务和基础设施必须再次经历构建过程。运行时配置(垃圾回收设置、使用的Spring profile等)的更改应该作为环境变量传递给镜像,而应用程序配置应该与容器隔离(Spring Cloud Config)。

12.1 构建/部署管道的架构 #

关于凤凰服务器的不变性与重生有了不可变服务器的概念,我们应该始终保证服务器的配置与服务器机器镜像的完全一致。服务器应该可以选择在不改变服务或微服务行为的情况下被杀死,并从机器镜像中重新启动。这种死亡和复活的新服务器被Martin Fowler称为“凤凰服务器”,因为当旧服务器被杀死时,新服务器会从毁灭中再生。更多详细信息请参阅Martin Fowler个人网站。凤凰服务器模式有两个基本的优点。首先,它暴露配置漂移并将配置漂移驱逐出你的环境。如果你不断地拆除并建立新服务器,那么你很有可能会提前暴露配置漂移。这对确保一致性有很大的帮助。其次,通过允许你发现服务器或服务在被杀死并重新启动后不能完全恢复的状况,凤凰服务器模式有助于提高弹性。请记住,在微服务架构中,服务应该是无状态的,服务器的死亡应该是一个微不足道的小插曲。随机地杀死和重新启动服务器可以很快暴露你在服务或基础设施中具有状态的情况。最好是在部署管道中尽早发现这些情况和依赖关系,而不是在收到客户或公司的紧急电话时再发现。

C.2.1 了解Micrometer和Prometheus #

可以用Micrometer获取的一些指标:● 与垃圾收集相关的统计信息;● CPU利用率;● 内存利用率;● 线程利用状态;● 数据源利用状态;● Spring MVC请求延迟;● Kafka连接工厂;● 缓存;● Logback中记录的事件数;● 正常运行时间。