- Spring Cloud入门
- 1 微服务简介
- 2 Spring Cloud简介
- 4 开发框架Spring Boot
- 服务注册和发现Eureka">5 服务注册和发现Eureka
- 负载均衡Ribbon">6 负载均衡Ribbon
- 声明式调用Feign">7 声明式调用Feign
- 熔断器Hystrix">8 熔断器Hystrix
- 路由网关Spring Cloud Zuul">9 路由网关Spring Cloud Zuul
- 服务网关Spring Cloud Gateway">10 服务网关Spring Cloud Gateway
- 服务注册和发现Consul">11 服务注册和发现Consul
- 配置中心Spring Cloud Config">12 配置中心Spring Cloud Config
- 服务链路追踪Spring Cloud Sleuth">13 服务链路追踪Spring Cloud Sleuth
- 微服务监控SpringBoot Admin">14 微服务监控SpringBoot Admin
- Spring Boot Security详解">15 Spring Boot Security详解
- 使用Spring Cloud OAuth2保护微服务系统">16 使用Spring Cloud OAuth2保护微服务系统
- 使用Spring Security OAuth2和JWT保护微服务系统">17 使用Spring Security OAuth2和JWT保护微服务系统
- 使用Spring Cloud构建微服务综合案例">18 使用Spring Cloud构建微服务综合案例
- Spring Cloud源码:https://github.com/superHeroJLS/spring-cloud ,与《深入理解SpringCloud与微服务构建》配套。
- 书籍:《深入理解SpringCloud与微服务构建》 《重新定义Spring Cloud实战》 《Spring Cloud微服务架构进阶》《Spring Cloud微服务:入门、实践与进阶》
- 优秀网站:Spring Cloud中国社区 Spring Cloud中国社区docs Spring Cloud中国社区博客 Spring Cloud中国社区GitHub 许进 许进Github 实战Spring Cloud从青铜到王者
- Spring Cloud组件选型参考,来自实战Spring Cloud从青铜到王者
- 绿色图标:文档中会做详细的讲解,基本上是Spring Cloud第二代新组件及一些核心组件。
- 黄色图标:Spring Cloud 第一代组件,我们也还是会详细的去讲,因为很多的公司还在用,或者其很有学习的价值。但这部分内容随着时间的推移,不远的将来会退出社区核心生态圈!
- 红色图标:我们就不再去讲了,基本是一些被淘汰的、没有价值的组件或者新版本研发搁浅状态的一些组件。
- 蓝色部分:社区新推出的新版或新特性,但是在国内比较少有应用,或者因为太新不是很成熟,我们暂且不讲!根据应用形式逐步补充!
- 梳理汇总参考,来源于网络:
Spring Cloud入门
1 微服务简介
随着互联网技术的飞速发展,目前全球超过一半的人口在使用互联网,人们的生活随着互联网的发展,发生了翻天覆地的变化。各行各业都在应用互联网, 国家政策也在大力支持互联网的发展。随着越来越多的用户参与,业务场景越来越复杂,传统的单体架构己经很难满足互联网技术的发展要求。这主要体现在两方面, 一是随着业务复杂度的提高,代码的可维护性、扩展性和可读性在降低; 二是维护系统的成本、修改系统的成本在提高。所以,改变单体应用架构己经势在必行。另外,随着云计算、大数据、人工智能的飞速发展,对系统架构也提出了越来越高的要求。
微服务,是著名的OO(面向对象, Object Oriented )专家Martin Fowler 提出来的,它是用来描述将软件应用程序设计为独立部署的服务的一种特殊方式。最近两年,微服务在各大技术会议、文章、书籍上出现的频率已经让人们意识到它对于软件领域所带来的影响力。微服务架构的系统是一个分布式系统,按业务领域划分为独立的服务单元,有自动化运维、容错、快速演进的特点,它能够解决传统单体架构系统的痛点,同时也能满足越来越复杂的业务需求。
1.1 单体架构及其存在的不足
1.1.2 单体架构简介
在软件设计巾,经常提及和使用经典的3 层模型,即表示层、业务逻辑层和数据访问层。
- 表示层: 用于直接和用户交互,也称为交互层,通常是网页、U I等。
- 业务逻辑层:即业务逻辑处理层,例如用户输入的信息要经过业务逻辑层的处理后,才能展砚给用户。
- 数据访问层: 用于操作数据库,用户在表示层会产生大量的数据,通过数据访问层对数据库进行读写操作。
虽然在软件设计中划分了经典的3 层模型,但是对业务场景没有划分。一个典型的单体应用就是将所有的业务场景的表示层、业务逻辑层和数据访问层放在一个工程中,最终经过编译、打包,部署在一台服务器上。例如典型的J2EE 工程,它是将表示层的JSP 、业务逻辑层的Service 、Controller 和数据访问层的Dao ,打成war 包,部署在Tomcat 、Jetty 或者其他Serviet容器中运行。经典的单体应用如图1-1 所示。
在一个小型应用的初始阶段,访问量较小,应用只需要一台服务器就能够部署所有的资源,例如将应用程序、数据库、文件资源等部署在同一台服务器上。最典型的就是LAMP 系统,即服务器采用Linux 系统,开发应用程序的语言为PHP ,部署在Apache 服务器上,采用MySQL数据库。在应用程序的初始阶段,采用这种架构的性价比是非常高的,开发速度快,开发成本低,只需要一台廉价的服务器。此时的服务器架构如图1-2 所示。
1.1.2 单体架构存在的不足
在应用的初始阶段,单体架构无论是在开发速度、运维难度上,还是服务器的成本上都有着显著的优势。在一个产品的前景不明确的初始阶段,用单体架构是非常明智的选择。随着应用业务的发展和业务复杂度的提高,这种架构明显存在很多的不足,主要体现在以下3 个方面。
- 业务越来越复杂,单体应用的代码量越来越大,代码的可读性、可维护性和可扩展性下降,新人接手代码所需的时间成倍增加,业务扩展带来的代价越来越大。
- 随着用户越来越多,程序承受的并发越来越高,单体应用的并发能力有限。
测试的难度越来越大,单体应用的业务都在同一个程序中,随着业务的扩张、复杂度的增加,单体应用修改业务或者增加业务或许会给其他业务带来一定的影响,导致测试难度增加。
1.1.3 单体架构使用服务器集群及存在的不足
随着业务的发展,大多数公司会将单体应用进行集群部署,井增加负载均衡服务器(例如Nginx 等〉。另外,还需要增加集群部署的缓存服务器和文件服务器,并将数据库读写分离,以应对用户量的增加而带来的高并发访问量。此时的系统架构如图1-3 所示。
用负载均衡服务器分发高并发的网络请求,用户的访问被分派到不同的应用服务器,应用服务器的负载不再成为瓶颈,用户量增加时,添加应用服务器即可。通过添加缓存服务器来缓解数据库的数据以及数据库读取数据的压力。大多数的读取操作是由缓存完成的,但是仍然有少数读操作是从数据库读取的,例如缓存失效、实时数据等。当有大量的读写操作时,将数据库进行读写分离是一个不错的选择,例如MySQL 的主从热备份,通过相关配置可以将主数据库服务器的数据同步到从数据库服务器, 实现数据库的读写分离, 读写分离能够改善数据库的
负载能力。
这种架构有一定的处理高并发的能力, 也能应对一定复杂的业务需求,改善了系统的性能, 但是依然没有改变系统为单体架构的事实,此时存在的不足之处如下。系统仍然为单体应用, 大量的业务必然会有大量的代码,代码的可读性和可维护性依然很差。
- 面对海量的用户,数据库将会成为瓶颈,解决方案将使用分布式数据库,也就是将数据库进行分库分表。
- 持续交付能力差,业务越复杂,代码越多,修改代码和添加代码所需的时间越长。新人熟悉代码的时间长、成本高。
由此看见,在应用初期,单体应用从成本、开发时间和运维等方面都有明显的优势。但是随着业务盐和用户聋的增加,它所暴露出来的缺点也显而易见。单体架构己经不能满足复杂的业务和海量的用户系统,改变单体架构势在必行。
1.2 微服务
微服务是最近一两年才出现的新名词,它在各大技术社区、博客、论坛和新闻报道中经常被提及,是程序员和l 架构师经常讨论的话题。的确,微服务己经是技术圈的热门话题,那么到底什么是微服务呢?微服务产生的意义又是什么呢?微服务有哪些优势和缺点?另外,微服务与SOA 架构有什么关系?下面让我来为你逐一阐述。
1.2.1 什么是微服务
“微服务”最初是由Martin Fowler 在2014 年写的一篇文章《MicroServices 》中提出来的。关于Martin Fowler 的介绍,维基百科上是这样描述的:
Martin Fowler ,软件工程师,也是一个软件开发方面的著作者和国际知名演说家,专注于丽向对象分析与设
计、统一建模语言、领域建模,以及敏捷软件开发方法,包括极限编程。主要著作有〈可重用对象模型〉〈重构一一改
善既有代码的设计〉〈企业应用架构模式〉〈规划极限编程〉等。
对于微服务,业界没有一个严格统一的定义,但是作为“微服务”这一名词的发明人,Martin Fowler 对微服务的定义似乎更具有权威性和指导意义。他的理解如下:
简而言之,微服务架构的风格,就是将单一程序开发成一个微服务,每个微服务运行在自己的进程中,并使用轻
量级机制通信,通常是HTTP RESTFUL API 。这些服务围绕业务能力来划分构建的,并通过完全自动化部署机制来
独立部署。这些服务可以使用不同的编程语言,以及不同数据存储技术,以保证最低限度的集中式管理。
以我个人对这段话的理解,总结微服务具有如下特点。
- 按业务划分为一个独立运行的程序,即服务单元。
- 服务之间通过HTTP 协议相互通信。
- 自动化部署。
- 可以用不同的编程语言。
- 可以用不同的存储技术。
- 服务集中化管理。
- 微服务是一个分布式系统。
根据这些特点,下面来进一步阐述微服务。
- 微服务单元按业务来划分
微服务的“微”到底需要定义到什么样的程度,这是一个非常难以界定的概念,可以从以下3 个方面来界定: 一是根据代码量来定义,根据代码的多少来判断程序的大小: 二是根据开发时间的长短来判断: 三是根据业务的大小来划分。
根据Martin Fowler 的定义,微服务的“微”是按照业务来划分的。一个大的业务可以拆分成若干小的业务, 一个小的业务又可以拆分成若干更小的业务,业务到底怎么拆分才算合适,这需要开发人员自己去决定。例如微博最常见的功能是微博内容、关注和粉丝,而其中微博内容又有点赞、评论等,如何将微博这个复杂的程序划分为单个的服务,需要由开发团队去决定。
按业务划分的微服务单元独立部署,运行在独立的进程中。这些微服务单元是高度组件化的模块,并提供稳定的模块边界,服务与服务之间没有任何的相合, 有非常好的扩展性和复用性。
传统的软件开发模式通常由UI 团队、服务端团队、数据库和运维团队构成,相应地将软件按照职能划分为U、服务端、数据库和运维等模块。通常这些开发人员各司其职, 很少有人跨职能去工作。如果按照业务来划分服务,每个服务都需要独立的UI 、服务端、数据库和运维。也就是说, 一个小的业务的微服务需要动用一个团队的人去协作,这显然增加了团队与团队之间交流协作的成本。所以产生了跨职能团队,这个团队负责一个服务的所有工作,包括UI、服务端和数据库。当这个团队只有l ~2 个人的时候,就对开发人员提出了更高的要求。
- 微服务通过HTTP 来互相通信
按照业务划分的微服务单元独立部署, 并运行在各自的进程中。微服务单元之间的通信方
式一般倾向于使用HTTP 这种简单的通信机制,更多的时候是使用阻STfulAPI 的。这种接受
请求、处理业务逻辑、返回数据的HTTP 模式非常高效,并且这种通信机制与平台和语言无关。例如用Java 写的服务可以消费用Go 语言写的服务,用Go 写的服务又可以消费用Ruby 写的服务。不同的服务采用不同的语言去实现,不同的平台去部署,它们之间使用HTTP 进行通信,如图1-4 所示。
服务与服务之间也可以通过轻量级的消息总线来通信,例如RabbitMQ 、Kafaka 等。通过发送消息或者订阅
消息来达到服务与服务之间通信的目的。
服务与服务通信的数据格式, 一般为JSON 、XML,这两种数据格式与语言、平台、通信协议无关。一般来说, JSON 格式的数据比XML 轻量,井且可读性也比X1在L 要好。另外一种就是用Protobuf 进行数据序列化, A 图1-4 不同语言、不同的平台的微服务经过序列化的数据为二进制数据,它比JSON 更轻量。相互调用
用Protobuf 序列化的数据为二进制数据,可读性非常差,需要反序列化才能够读懂。由于用Protobuf 序列化的数据更为轻量,所以Protobuf 在通信协议和数据存储上十分受欢迎。
服务与服务之间通过HTTP 或者消息总线的方式进行通信,这种方式存在弊端,其通信机制是不可靠的,虽然成功率很高,但还是会有失败的时候。
- 微服务的数据库独立
在单体架构中,所有的业务都共用一个数据库。随着业务量的增加,数据库的表的数量越来越多,难以管理和维护,并且数据量的增加会导致查询速度越来越慢。例如, 一个应用有这样几个业务:用户的信息、用户的账户、用户的购物车、数据报表服务等。典型的单体架构如图1-5 所示。
微服务的一个特点就是按业务划分服务,服务与服务之间无稠合,就连数据库也是独立的。一个典型的微服务的架构就是每个微服务都有自己独立的数据库,数据库之间没有任何联系。这样做的好处在于,随着业务的不断扩张,服务与服务不需要提供数据库集成,而是提供API接口相互调用:还有一个好处是数据库独立,单业务的数据盆少,易于维护,数据库性能有着明显的优势,数据库的迁移也很方便。另外,随着存储技术的发展,数据库的存储方式不再仅仅是关系型数据库,非关系数据库的应用也非常广泛,例如Mo n gDB 、R edis ,它们有着良好的读写性能,因此越来越受欢迎。
一个典型的微服务的系统,可能每一个服务的数据库都不相同,每个服务所使用的数据存储技术需要根据业务需求来选择,如图1- 6 所示。
- 微服务的自动化部署
在微服务架构中,系统会被拆分为若干个微服务, 每个微服务又是一个独立的应用程序。单体架构的应用程序只需要部署一次,而微服务架构有多少个服务就需要部署多少次。随着服务数量的增加,如果微服务按照单体架构的部署方式,部署的难度会呈指数增加。业务的粒度划分得越细,微服务的数量就越多,这时需要更稳定的部署机制。随着技术的发展,尤其是Docker 容器技术的推进,以及自动化部署工具(例如开源组件J e n kin s )的出现,自动化部署变得越来越简单。
自动化部署可以提高部署的效率,减少人为的控制,部署过程中出现错误的概率降低,部署过程的每一步自动化,提高软件的质量。构建一个自动化部署的系统,虽然在前期需要开发人员或者运维人员的学习,但是对于整个软件系统来说是一个全新的概念。在软件系统的整个生命周期之中,每一步是由程序控制的,而不是人为控制,软件的质量提高到了一个新的高度。随着D ev Op s 这种全新概念的推进,自动化部署必然会成为微服务部署的一种方式。
- 服务集中化管理
微服务系统是按业务单元来划分服务的,服务数量越多, 管理起来就越复杂,因此微服务必须使用集中化管理。目前流行的微服务框架中,例如Spring Cloud 采用Eureka 来注册服务和发现服务,另外, Zookeeper 、Consul 等都是非常优秀的服务集中化管理框架。
- 分布式架构
分布式系统是集群部署的,由很多计算机相互协作共同构成,它能够处理海量的用户请求。当分布式系统对外提供服务时,用户是毫不知情的,还以为是一台服务器在提供服务。
分布式系统的复杂任务通过计算机之间的相互协作来完成,当然简单的任务也可以在一台计算机上完成。
分布式系统通过网络协议来通信,所以分布式系统在空间上没有任何限制,即分布式服务器可以部署不同的机房和不同的地区。
微服务架构是分布式架构, 分布式系统比单体系统更加复杂,主要体现在服务的独立性和服务相互调用的可靠性,以及分布式事务、全局锁、全局Id 等, 而单体系统不需要考虑这些复杂性。
另外,分布式系统的应用都是集群化部署, 会给数据一致性带来困难。分布式系统中的服务通信依赖于网络, 网络不好,必然会对分布式系统带来很大的影响。在分布式系统中,服务之间相互依赖,如果一个服务出现了故障或者是网络延迟,在高并发的情况下,会导致线程阻塞,在很短的时间内该服务的线程资源会消耗殆尽,最终使得该服务不可用。由于服务的相互依赖,可能会导致整个系统的不可用,这就是“雪崩效应”。为了防止此类事件的发生,分布式系统必然要采取相应的措施,例如“熔断机制”。
- 熔断机制
为了防止“雪崩效应”事件的发生,分布式系统采用了熔断机制。在用Spring Cloud 构建的微服务系统中,采用了熔断器(即Hystrix组件的C ircuit Breaker )去做熔断。
例如在微服务系统中,有a、b 、c 、d、e、f、g 、h 等多个服务,用户的请求通过网关后,再到具体的服务,服务之间相互依赖,例如服务b 依赖于服务f, 一个对外暴露的API 接口需要服务b 和服务f 相互协作才能完成。服务之间相五依赖的架构图如图1-7 所示。
如果此时服务b 出现故障或者网络延迟,在高并发的情况下,服务b 会出现大量的线程阻塞,有可能在很短的时间内线程资源就被消耗完了,导致服务b 的不可用。如果服务b 为较底层的服务,会影响到其他服务,导致其他服务会一直等待服务b 的处理。如果服务b 迟迟不处理,大量的网络请求不仅仅堆积在服务b ,而且会堆积到依赖于服务b 的其他服务。而因服务b 出现故障影响的服务,也会影响到依赖于因服务b 出现故障影响的服务的其他服务,从而由b 开始,影响到整个系统,导致整个系统的不可用。这是一件非常可怕的事,因为服务器运营商的不可靠,必然会导致服务的不可靠,而网络服务商的不可靠性,也会导致服务的不可靠。在高并发的场景下,稍微有点不可靠,由于故障的传播性,会导致大量的服务不可用,甚至导致整个系统崩溃。
为了解决这一难题,微服务架构引入了熔断机制。当服务b 出现故障,请求失败次数超过设定的阀值之后,服务b 就会开启熔断器,之后服务b 不进行任何的业务逻辑操作,执行快速失败,直接返回请求失败的信息。其他依赖于b 的服务就不会因为得不到响应而线程阻塞,这时除了服务b 和依赖于服务b 的部分功能不可用外, 其他功能正常。熔断服务b 如图1-8 所示。
熔断器还有另外一个机制, ~p 自我修复的机制。当服务b 熔断后,经过一段时间,半打开熔断器。半打开的熔断器会检查一部分请求是否正常,其他请求执行快边失败,检查的请求如果响应成功,则可以判定服务b 正常了,就会关闭服务b 的熔断器;如果服务b 还不正常,则继续打开熔断器。这利1 自我熔断机制和自我修复机制在微服务架构中有精重要的意义, 一方面,它使程序更加健壮,另一方面,为开发和运维减少很多不必要的工作。
最后,熔断组件往往会提供一系列的监控,A 图1-8 将服务b 熔断例如服务可用与否、熔断器是否被打开、目前的吞吐量、网络延迟状态的监控等,从而很容易让开发人员和运维人员实时地了解服务的状况。
1.2.2 微服务的优势
相对于单体服务来说,微服务具有很多的优势,主要体现在以下方面。
- 将一个复杂的业务分解成若干小的业务,每个业务拆分成一个服务,服务的边界明确,将复杂的问题简单化。服务按照业务拆分, 编码也是按照业务来拆分,代码的可读性和可扩展性增加。新人加入团队,不需要了解所有的业务代码,只需要了解他所接管的服务的代码,新人学习时间成本减少。
- 由于微服务系统是分布式系统,服务与服务之间没有任何的祸合。随着业务的增加,可以根据业务再拆分服务, 具有极强的横向扩展能力。随着应用的用户量的增加,井发量增加,可以将微服务集群化部署,从而增加系统的负载能力。简而言之,微服务系统的微服务单元具有很强的横向扩展能力。
- 服务与服务之问通过HTTP 网络通信协议来通信,单个微服务内部高度祸合,服务与服务之间完全独立,无调合。这使得微服务可以采用任何的开发语言和技术来实现。开发人员不再被强迫使用公司以前的技术或者已经过时的技术,而是可以自由选择最适合业务场景的或者最适合自己的开发语言和技术,提高开发效率、降低开发成本。
- 如果是一个单体的应用,由于业务的复杂性、代码的祸合性,以及可能存在的历史问题。在重写一个单体应用时,要求重写的应用的人员了解所有的业务,所以重写单体应用是非常困难的,并且重写风险也较高。如果是微服务系统,由于微服务系统是按照业务的进行拆分的,并且有坚实的服务边界,所以重写某个服务就相当于重写某一个业务的代码,非常简单。
- 微服务的每个服务单元都是独立部署的,即独立运行在某个进程里。微服务的修改和部署对其他服务没有影响。试想,假设一个应用只有一个简单的修改,如果是单体架构,需要测试和部署整个应用;而如果采用微服务架构,只需要测试并部署被修改的那个服务,这就大大减少了测试和部署的时间。
微服务在CAP 理论中采用的是AP 架构,即具有高可用和分区容错的特点。高可用主要体现在系统7 x 24 小时不间断的服务,它要求系统有大量的服务器集群,从而提高了系统的负载能力。另外,分区容错也使得系统更加健壮。
1.3 微服务的不足
凡事都有两面性,微服务也不例外,微服务相对于单体应用来说具有很多的优势,当然也
有它的不足, 主要体现在如下方面。微服务的复杂度。
- 分布式事务。
- 服务的划分。
-
1.3.1 微服务的复杂度
构建一个微服务系统并不是一件容易的事,微服务系统是分布式系统,构建的复杂度远远超过单体系统,开发人员需要付出一定的学习成本去掌握更多的架构知识和框架知识。服务与服务之间通过HTTP 协议或者消息传递机制通信,开发者需要选出最佳的通信机制,并解决网络服务较差时带来的风险。
另外服务与服务之间相互依赖,如果修改某一个服务,会对另外一个服务产生影响,如果掌控不好,会产生不必要的麻烦。由于服务的依赖性,测试也会变得复杂,比如修改一个比较基础的服务,可能需要重启所有的服务才能完成测试。1.3.2 分布式事务
微服务架构所设计的系统是分布式系统。分布式系统有一个著名的CAP 理论,即同时满足“ 一致性”“可用性”和“分区容错”是一件不可能的事。CAP 理论是由Eric Brewer在2 000 年PODC 会议上提出的,该理论在两年后被证明成立。CAP 理论告诉架构师不要妄想设计出同时满足三者的系统,应该有所取舍,设计出适合业务的系统。CAP 理论如图1-9 所示。
Consistency:指数据的强一致性。如果写入某个数据成功,之后读取,读到的都是新写入的数据:如果写入失败,之后读取的都不是写入失败的数据。
- Availability :指服务的可用性。
- Partition-tolerance:指分区容错。
在分布式系统中, P 是基本要求,而单体服务是CA 系统。微服务系统通常是一个AP 系统,即同时满足了可用性和分区容错。这就有了一个难题:在分布式系统中如何保证数据的一致性?这就是大家经常讨论的分布式事务。
在微服务系统中,每个服务都是独立的进程单元,每个服务都有自己的数据库。通常情况下,只有关系型数据库在特定的数据引擎下才支持事务,而大多数非关系型数据库是不支持事务的,例如MongDB 是不支持事务的,而Redis 是支持事务的。在微服务架构中,分布式事务一直都是一个难以解决的问题,业界给出的解决办法通常是两阶段提交。
网上购物在日常生活中是一个非常普通的场最,假设我在淘宝上购买了一部手机,需要从我的账户中扣除1000 元钱,同时手机的库存数量需要减1 。当然需要在卖方的账户中加1000 元钱,为了使案例简单化,暂时不用考虑。
如果这是一个单体应用,并且使用支持事务的MySQL 数据库ClnnoDB 数据库引擎才支持事务),我们可能这样写代码:
@Transactional
public void update () throws RuntimeException {
updateAccountTable(); //更新账户表
updateGoodsTable(); //更新商品表
}
如果是微服务架构,账户是一个服务,而商品是一个服务,这时不能用数据库自带的事务,因为这两个数据表不在一个数据库中。因此常常用到两阶段提交,两阶段提交的过程如图1-10所示。
第一阶段, service-account 发起一个分布式事务,交给事务协调器TC 处理,事务协调器TC 向所有参与的事务的节点发送处理事务操作的准备操作。所有的参与节点执行准备操作,将Undo 和Redo 信息写进日志,并向事务管理器返回准备操作是否成功。
第二阶段,事务管理器收集所有节点的准备操作是否成功,如果都成功,则通知所有的节点执行提交操作;如果有一个失败, 则执行回滚操作。
两阶段提交,将事务分成两部分能够大大提高分布式事务成功的概率。如果在第一阶段都成功了, 而执行第二阶段的某一个节点失败, 仍然导致数据的不准确,这时一般需要人工去处理,这就是当初在第一步记录日志的原因。另外,如果分布式事务涉及的节点很多,某一个节点的网络出现异常会导致整个事务处于阻塞状态, 大大降低数据库的性能。所以一般情况下,尽量少用分布式事务。
1.3.3 服务的划分
将一个完整的系统拆分成很多个服务,是一件非常困难的事,因为这涉及了具体的业务场景,比命名一个类更加困难。对于微服务的拆分原则,Martin Fowler给出的建议是:服务是可以被替换和更新的。也就是服务和服务之间无祸合,任何一个服务都可以被替换,服务有自己严格的边界。当然这个原则很抽象,根据具体的业务场景来拆分服务,需要依靠团队人员对业务的熟悉程度和理解,程度,并考虑与己有架构的冲突、业务的扩展性、开发的风险和未来业务的发展等诸多因素。领域驱动设计是一个全新的概念,也是一个比较理想的微服务拆分的理念。领域驱动设计通过代码和数据分析找到合理的切分点, 并通过数据分析来判断服务的划分边界和划分粒度。目前来说,在中罔几乎没有公司去落地领域驱动设计这个理念,随着微服务的发展,这一理念在以后有可能会落地。
1.3.4 服务的部署
一个简单的单体系统可能只需要将程序集群部署并配置负载均衡服务器即可,而部署一个复杂的微服务架构的系统就复杂得多。因为每一个微服务可能还涉及比较底层的组件,例如数据库、消息中问件等。微服务系统往往由数量众多的服务构成, 例如Netflix公司有大约600个服务,而每个服务又有大量的实例。微服务系统需要对每个服务进行治理、监控和l管理等,而每个服务有大量的配置,还需要考虑服务的启动顺序和启动时机等。部署微服务系统,需要开发人员或者运维人员对微服务系统有足够强的控制力。随着云计算和云服务器的发展,部署微服务系统并不是一件难事,例如使用PaaS 服务、使用Docker 编排等。这就是人们往往提到微服务,就会想到Docker 、DevOps 的原因。其中,微服务是核心: Docker 为容器技术,是微服务最佳部署的容器: DevOps 是一种部署手段或理念。它们的关系如图1-11 所示。
1.4 微服务和SOA的关系
SOA 即面向服务的架构,这种架构在2 0 年前就已经被提出了。SOA 往往与企业服务总线(ESB )联系在一起,主要原因在于SOA 的实施思路是根据ESB 模式来整合集成大量单一庞大的系统,这是SOA 主要的落地方式。然而, SOA 在过去20 年并没有取得成功。在谈到微服务时,人们很容易联想到它是一个面向服务的架构。的确,微服务的概念提出者Martin Fowler没有否认这一层关系。
微服务相对于和ESB 联系在一起的SOA 显然轻便敏捷得多,微服务将复杂的业务组件化,实际也是一种面向服务思想的体现。对于微服务来说,它是SOA 的一种实现,但是它比ESB实现的SOA 更加轻便、敏捷和简单。
1.5 微服务的设计原则
软件设计就好比建筑设计。Architect 这个词在建筑学中是“建筑师”的意思,而在软件领域里则是“架构师”的意思,可见它们确实有相似之处。无论是建筑师还是架构师,他们都希望把作品设计出自己的特色,并且更愿意把创造出的东西被称为艺术品。然而现实却是,建筑设计和软件设计有非常大的区别。建筑师设计并建造出来的建筑往往很难有变化,除非拆了重建。而架构师设计出来的软件系统,为了满足产品的业务发展,在它的整个生命周期中,每一个版本都有很多的变化。
软件设计每一个版本都在变化,所以软件设计应该是渐进式发展。软件从一开始就不应该被设计成微服务架构,微服务架构固然有优势,但是它需要更多的资源,包括服务器资源、技术人员等。追求大公司所带来的技术解决方案,刻意地追求某个新技术,企图使用技术解决所有的问题,这些都是软件设计的误区。
技术应该是随着业务的发展而发展的,任何脱离业务的技术是不能产生价值的。在初创公司,业务很单一时,如果在LAMP 单体构架够用的情况下,就应该用LAMP ,因为它开发速度快,性价比高。随着业务的发展,用户量的增加,可以考虑将数据库读写分离、加缓存、加复杂均衡服务器、将应用程序集群化部署等。如果业务还在不断发展,这时可以考虑使用分布式系统,例如微服务架构的系统。不管使用什么样的架构,驱动架构的发展一定是业务的发展,只有当前架构不再适合当前业务的发展,才考虑更换架构。
在微服务架构中,有三大难题,那就是服务故障的传播性、服务的划分和分布式事务。在微服务设计时, 一定要考虑清楚这三个难题,从而选择合适的框架。目前比较流行的微服务框架有Spring社区的Spring Cloud 、Google 公司的Kubemetes 等。不管使用哪一种框架或者工具,都需要考虑这三大难题。为了解决服务故障的传播性, 一般的微服务框架都有熔断机制组件。另外,服务的划分没有具体的划分方法, 一般来说根据业务来划分服务, 领域驱动设计具有指导作用。最后,分布式事务一般的解决办法就是两阶段提交或者三阶段提交,不管使用哪一种都存在事务失败,导致数据不一致的情况,关键时刻还得人工去恢复数据。总之,微服务的设计一定是渐进式的,并且是随着业务的发展而发展的。
三大难题的解决方案:
- 服务故障的传播性:使用Netflix Hystrix/Alibaba Sentinel对服务进行熔断、降级、限流控制。
- 服务的划分:根据业务划分服务,划分出来的微服务粒度尽量达到高可用,低耦合的效果。
-
2 Spring Cloud简介
Spring Cloud作为Java语言的微服务框架,它依赖于Spring Boot,有快速开发、持续交付和容易部署等特点。Spring Cloud的组件非常多,涉及微服务的方方面面,并在开源社区Spring和Netflix、Pivotal两大公司的推动下越来越完善。本章主要介绍Spring Cloud,将从以下方面来讲解。
微服务应该具备的功能。
Spring Cloud介绍。
Dubbo介绍。
Kubernetes介绍。
Spring Cloud与Dubbo比较。
Spring Cloud与Kubernetes比较。2.1 微服务应该具备的功能
微服务,可以拆分为“微”和“服务”二字。“微”即小的意思,那到底多小才算“微”呢?可能不同的团队有不同的答案。从参与微服务的人数来讲,单个微服务从架构设计、代码开发、测试、运维的人数加起来是8~10人才算“微”。那么何为“服务”呢?按照“微服务”概念提出者Martin Fowler给出的定义:“服务”是一个独立运行的单元组件,每个单元组件运行在独立的进程中,组件与组件之间通常使用HTTP这种轻量级的通信机制进行通信。微服务具有以下的特点。
按照业务来划分服务,单个服务代码量小,业务单一,易于维护。
- 每个微服务都有自己独立的基础组件,例如数据库、缓存等,且运行在独立的进程中。
- 微服务之间的通信是通过HTTP协议或者消息组件,且具有容错能力。
- 微服务有一套服务治理的解决方案,服务之间不耦合,可以随时加入和剔除服务。
- 单个微服务能够集群化部署,并且有负载均衡的能力。
- 整个微服务系统应该有一个完整的安全机制,包括用户验证、权限验证、资源保护等。
- 整个微服务系统有链路追踪的能力。
- 有一套完整的实时日志系统。
微服务具有以上这些特点,那么微服务需要具备一些什么样的功能呢?微服务的功能主要体现在以下几个方面。
- 服务的注册和发现。
- 服务的负载均衡。
- 服务的容错。
- 服务网关。
- 服务配置的统一管理。
- 链路追踪。
-
2.1.1 服务注册与发现
由于微服务系统的服务粒度较小,服务数量众多,服务之间的相互依赖成网状,所以微服务系统需要服务注册中心来统一管理微服务实例,方便查看每一个微服务实例的健康状态。
服务注册是指向服务注册中心注册一个服务实例,服务提供者将自己的服务信息(如服务名、IP地址等)告知服务注册中心。服务发现是指当服务消费者需要消费另外一个服务时,服务注册中心能够告知服务消费者它所要消费服务的实例信息(如服务名、IP地址等)。通常情况下,一个服务既是服务提供者,也是服务消费者。服务消费者一般使用HTTP协议或者消息组件这种轻量级的通信机制来进行服务消费。服务的注册与发现如图2-1所示。
服务注册中心会提供服务的健康检查方案,检查被注册的服务是否可用。通常一个服务实例注册后,会定时向服务注册中心提供“心跳”,以表明自己还处于可用的状态。当一个服务实例停止向服务注册中心提供心跳一段时间后,服务注册中心会认为该服务实例不可用,会将该服务实例从服务注册列表中剔除。如果这个被剔除掉的服务实例过一段时间后继续向注册中心提供心跳,那么服务注册中心会将该服务实例重新加入服务注册中心的列表中。另外,微服务的服务注册组件都会提供服务的健康状况查看的UI界面,开发人员或者运维人员只需要登录相关的界面就可以知道服务的健康状态。
服务注册中心一般可以选择Spring Cloud Netflix Eureka/Apache Nacos/Spring Cloud Consul/Zookeeper,注意Apache Nacos即使服务注册中心也是服务配置中心,Eureka2已经不在开源,基于以上原因,建议首选Apache Nacos。2.1.2 服务负载均衡
在微服务架构中,服务之间的相互调用一般是通过HTTP通信协议来实现的。网络往往具有不可靠性,为了保证服务的高可用(High Availability),服务单元往往是集群化部署的。例如将服务提供者进行集群化部署,那么服务消费者该调用哪个服务提供者的实例呢?这就涉及了服务的负载均衡。
服务的负载均衡一般最流行的做法如图2-2所示,所有的服务都向服务注册中心注册,服务注册中心持有每个服务的应用名和IP地址等信息,同时每个服务也会获取所有服务注册列表信息。服务消费者集成负载均衡组件,该组件会向服务消费者获取服务注册列表信息,并每隔一段时间重新刷新获取该列表。当服务消费者消费服务时,负载均衡组件获取服务提供者所有实例的注册信息,并通过一定的负载均衡策略(开发者可以配置),选择一个服务提供者的实例,向该实例进行服务消费,这样就实现了负载均衡。
服务注册中心不但需要定时接收每个服务的心跳(用来检查服务是否可用),而且每个服务会定期获取服务注册列表的信息,当服务实例数量很多时,服务注册中心承担了非常大的负载。由于服务注册中心在微服务系统中起到了至关重要的作用,所以必须实现高可用。一般的做法是将服务注册中心集群化,每个服务注册中心的数据实时同步,如图2-3所示。
服务负载均衡一般选择Spring Cloud Netflix Ribbon。2.1.3 服务容错
微服务落地到实际项目中,服务的数量往往非常多,服务之间的相互依赖性也是错综复杂的,一个网络请求通常需要调用多个服务才能完成。如果一个服务不可用,例如网络延迟或故障,会影响到依赖于这个不可用的服务的其他服务。如图2-4所示,一个微服务系统有很多个服务,当服务F因某些原因导致了服务的不可用,来自于用户的网络请求需要调用服务F。由于服务F无响应,用户的请求都处于阻塞状态,在高并发的场景下,短时间内会导致服务器的线程资源消耗殆尽。另外,依赖于服务F的其他的服务,例如图中的服务E、服务G、服务J,也会等待服务F的响应,处于阻塞状态,导致这些服务的线程资源消耗殆尽,进而导致它们的不可用,以及依赖于它们的服务的不可用,最后导致整个系统处于瘫痪的状态也就是1.2.1节中提到的雪崩效应。
为了解决分布式系统的雪崩效应,分布式系统引进了熔断器机制。熔断器(CircuitBreaker)一词来源于物理学中的电路知识,它的作用是当电路中出现故障时迅速切断电路,从而保护电路,熔断器机制如图2-5所示。当一个服务的处理用户请求的失败次数在一定时间内小于设定的阀值时,熔断器处于关闭状态,服务正常;当服务处理用户请求的失败次数大于设定的阀值时,说明服务出现了故障,打开熔断器,这时所有的请求会执行快速失败,不执行业务逻辑。当处于打开状态的熔断器时,一段时间后会处于半打开状态,并执行一定数量的请求,剩余的请求会执行快速失败,若执行的请求失败了,则继续打开熔断器;若成功了,则将熔断器关闭。
这种机制有着非常重要的意义,它不仅能够有效地防止系统的“雪崩”效应,还具有以下作用。
将资源进行隔离,如果某个服务里的某个API接口出现了故障,只会隔离该API接口,不会影响到其他API接口。被隔离的API接口会执行快速失败的逻辑,不会等待,请求不会阻塞。如果不进行这种隔离,请求会一直处于阻塞状态,直到超时。若有大量的请求同时涌入,都处于阻塞的状态,服务器的线程资源,迅速被消耗完。
服务降级的功能。当服务处于正常的状态时,大量的请求在短时间内同时涌入,超过了服务的处理能力,这时熔断器会被打开,将服务降级,以免服务器因负载过高而出现故障。
自我修复能力。当因某个微小的故障(例如网络服务商的问题),导致网络在短时间内不可用,熔断器被打开。如果不能自我监控、自我检测和自我修复,那么需要开发人员手动地去关闭熔断器,无疑会增加开发人员的工作量。
Netflix的Hystrix熔断器开源组件功能非常强大,不仅有熔断器的功能,还有熔断器的状态监测,并提供界面友好的UI,开发人员或者运维人员通过UI界面能够直观地看到熔断器的状态和各种性能指标。
服务熔断组件一般可选Spring Cloud Netflix Hystrix/Alibaba Sentinel,虽然AlibabaSentinel功能比Hystrix更加完善一些,但是使用Spring Cloud作为微服务开发框架时建议选择Hystrix。2.1.4 服务网关
微服务系统通过将资源以API接口的形式暴露给外界来提供服务。在微服务系统中,API接口资源通常是由服务网关(也称API网关)统一暴露,内部服务不直接对外提供API资源的暴露。这样做的好处是将内部服务隐藏起来,外界还以为是一个服务在提供服务,在一定程度上保护了微服务系统的安全。API网关通常有请求转发的作用,另外它可能需要负责一定的安全验证,例如判断某个请求是否合法,该请求对某一个资源是否具有操作权限等。通常情况下,网关层以集群的形式存在。在服务网关层之前,有可能需要加上负载均衡层,通常为Nginx双机热备,通过一定的路由策略,将请求转发到网关层。到达网关层后,经过一系列的用户身份验证、权限判断,最终转发到具体的服务。具体的服务经过一系列的逻辑运算和数据操作,最终将结果返回给用户,此时的架构如图2-6所示。
网关层具有很重要的意义,具体体现在以下方面。
网关将所有服务的API接口资源统一聚合,对外统一暴露,外界系统调用的API接口都是网关对外暴露的API接口。外界系统不需要知道微服务架构中各服务相互调用的复杂性,微服务系统也保护了其内部微服务单元的API接口,防止被外界直接调用以及服务的敏感信息对外暴露。
网关可以做一些用户身份认证、权限认证,防止非法请求操作API接口,对内部服务起到保护作用。
网关可以实现监控功能,实时日志输出,对请求进行记录。
网关可以用来做流量监控,在高流量的情况下,对服务进行降级。
API接口从内部服务分离出来,方便做测试。
当然,网关实现这些功能,需要做高可用,否则网关很可能成为架构中的瓶颈。最常用的网关组件有Zuul/Spring Cloud Gateway和Nginx等。
Spring Cloud gateway是Spring Cloud第二代网关,目的在于取代Zuul,建议网关选用Spring Cloud Gateway。2.1.5 服务配置的统一管理
在实际开发过程中,每个服务都有大量的配置文件,例如数据库的配置、日志输出级别的配置等,而往往这些配置在不同的环境中也是不一样的。随着服务数量的增加,配置文件的管理也是一件非常复杂的事。
在微服务架构中,需要有统一管理配置文件的组件,例如Spring Cloud 的Spring CloudConfig组件、阿里巴巴的Diamond、百度的Disconf、携程的Apollo等。这些配置组件所实现的功能大体相同,但是又有些差别,下面以Spring Cloud Config为例来阐述服务配置的统一管理。
如图2-7所示,大致过程如下。
首先,Config Server(配置服务)读取配置文件仓库的配置信息,其中配置文件仓库可以存放在配置服务的本地仓库,也可以放在远程的Git仓库(例如GitHub、Coding等)。
配置服务启动后,读取配置文件信息,读取完成的配置信息存放在配置服务的内存中。
当启动服务A、B时,由于服务A、B指定了向配置服务读取配置信息,服务A、B向配置服务读取配置信息。
当服务的配置信息需要修改且修改完成后,向配置服务发送Post请求进行刷新,这时服务A、B会向配置服务重写读取配置文件。
服务配置中心建议选用Alibaba Nacos/Apollo,Spring Cloud Config + Bus属于Spring Cloud第一代服务配置中心,将来可能会被淘汰。2.1.6 服务链路追踪
微服务系统是一个分布式架构的系统,微服务系统按业务划分服务单元,一个微服务系统往往有很多个服务单元。由于服务单元数量很多且业务复杂,服务与服务之间的调用有可能非常复杂,一旦出现了异常和错误,就会很难去定位。所以在微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而使每个请求链路清晰可见,出了问题很快就能定位。
举个例子,如图2-8所示,在微服务系统中,一个来自用户的请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E。后端经过一系列的业务逻辑计算,最后将数据返回给用户。对于这样一个请求,经历了这么多服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。
Google开源了链路追踪组件Dapper,并在2010年发表了论文《Dapper, a Large-ScaleDistributed Systems Tracing Infrastructure》,这篇文章是业内实现链路追踪的标杆和理论基础,具有非常高的参考价值。
目前,常见的链路追踪组件有Spring Cloud Sleuth,Google的Dapper、Twitter 的Zipkin,以及阿里的Eagleeye(鹰眼)等,Apache SkyWalking,都是非常优秀的链路追踪开源组件。
服务链路追踪组件建议选用Apache SkyWalking。2.2 Spring Cloud
2.2.1 简介
Spring Cloud是基于Spring Boot的。Spring Boot是由Pivotal团队提供的全新Web框架,它主要的特点就是简化了开发和部署的过程,简化了Spring 复杂的配置和依赖管理,通过起步依赖和内置Servlet容器能够使开发者迅速搭起一个Web工程。所以Spring Cloud在开发部署上继承了Spring Boot的一些优点,提高了在开发和部署上的效率。
Spring Cloud的首要目标就是通过提供一系列开发组件和框架,帮助开发者迅速搭建一个分布式的微服务系统。Spring Cloud是通过包装其他技术框架来实现的,例如包装开源的NetflixOSS 组件,实现了一套通过基于注解、Java配置和基于模版开发的微服务框架。Spring Cloud框架来自于Spring Resource社区,由Pivotal和Netflix两大公司和一些其他的开发者提供技术上的更新迭代。Spring Cloud提供了开发分布式微服务系统的一些常用组件,例如服务注册和发现、配置中心、熔断器、智能路由、微代理、控制总线、全局锁、分布式会话等。2.2.2 常用组件
(1)服务注册和发现组件Eureka
利用Eureka组件可以很轻松地实现服务的注册和发现的功能。Eureka组件提供了服务的健康监测,以及界面友好的UI。通过Eureka组件提供的UI,Eureka组件可以让开发人员随时了解服务单元的运行情况。另外Spring Cloud也支持Consul和Zookeeper,用于注册和发现服务。
可选组件:Eureka、Apache Nacos、Zoo、Zookeeper。
(2)熔断组件Hystrix
Hystrix是一个熔断组件,它除了有一些基本的熔断器功能外,还能够实现服务降级、服务限流的功能。另外Hystrix提供了熔断器的健康监测,以及熔断器健康数据的API接口。HystrixDashboard组件提供了单个服务熔断器的健康状态数据的界面展示功能,Hystrix Turbine组件提供了多个服务的熔断器的健康状态数据的界面展示功能。
可选组件:Hystrix、Alibaba Sentinel。
(3)负载均衡组件Ribbon
Ribbon是一个负载均衡组件,它通常和Eureka、Zuul、RestTemplate、Feign配合使用。Ribbon和Zuul配合,很容易做到负载均衡,将请求根据负载均衡策略分配到不同的服务实例中。Ribbon和RestTemplate、Feign配合,在消费服务时能够做到负载均衡。
可选组件:Ribbon
(4)路由网关Zuul
路由网关Zuul有智能路由和过滤的功能。内部服务的API接口通过Zuul网关统一对外暴露,内部服务的API接口不直接暴露,防止了内部服务敏感信息对外暴露。在默认的情况下,Zuul和Ribbon相结合,能够做到负载均衡、智能路由。Zuul的过滤功能是通过拦截请求来实现的,可以对一些用户的角色和权限进行判断,起到安全验证的作用,同时也可以用于输出实时的请求日志。
可选组件:Zuul、Spring Cloud Gateway。
上述的4个组件都来自于Netflix的公司,统一称为Spring Cloud Netflix。
(5)Spring Cloud Config
Spring Cloud Config组件提供了配置文件统一管理的功能。Spring Cloud Config包括Server端和Client端,Server 端读取本地仓库或者远程仓库的配置文件,所有的Client向Server读取配置信息,从而达到配置文件统一管理的目的。通常情况下,Spring Cloud Config和SpringCloud Bus相互配合刷新指定Client或所有Client的配置文件。
可选组件:Spring Cloud Config、Apache Nacos、Apollo、Disconfig。
(6)Spring Cloud Security
Spring Cloud Security是对Spring Security组件的封装,Spring Cloud Security向服务单元提供了用户验证和权限认证。一般来说,单独在微服务系统中使用Spring Cloud Security是很少见的,一般它会配合 Spring Security OAuth2 组件一起使用,通过搭建授权服务,验证Token或者JWT这种形式对整个微服务系统进行安全验证。
(7)Spring Cloud Sleuth
Spring Cloud Sleuth是一个分布式链路追踪组件,它封装了Dapper、Zipkin和Kibana等组件,通过它可以知道服务之间的相互依赖关系,并实时观察链路的调用情况。
可选组件:Spring Cloud Sleuth、Alibaba Eagleeye、Apache SkyWalking。
(8)Spring Cloud Stream
Spring Cloud Stream是Spring Cloud框架的数据流操作包,可以封装RabbitMQ、ActiveMQ、Kafka、Redis等消息组件,利用Spring Cloud Stream可以实现消息的接收和发送。
上述列举了一些常用的Spring Cloud组件。一个简单的由Spring Cloud构建的微服务系统,通常由服务注册中心Eureka、网关Zuul、配置中心Config和授权服务Auth构成,架构如图2-9所示。
本书项目组件一览:
Spring Cloud Config:服务配置中心,将所有的服务的配置文件放到本地仓库或者远程仓库,配置中心负责读取仓库的配置文件,其他服务向配置中心读取配置。Spring Cloud Config将服务的配置统一管理,并可以在不人为重启服务的情况下进行服务配置的刷新。
Spring Cloud Netflix:它是通过包装了Netflix公司的微服务组件实现的,也是SpringCloud核心的核心组件,包括Eureka、Hystrix、Zuul和Archaius等。
Eureka:服务注册和发现组件。
Hystrix:熔断器组件。Hystrix通过控制服务的API接口的熔断来转移故障,防止微服务系统发生雪崩效应。另外,Hystrix能够起到服务限流和服务降级的作用。使用Hystrix Dashboard组件监控单个服务的熔断器的状态,使用Turbine组件可以聚合多个服务的熔断器的状态。
Zuul:智能路由网关组件。Netflix Zuul能够起到智能路由和请求过滤的作用,是服务接口统一暴露的关键模块,也是安全验证、权限控制的一道门。
Feign:声明式远程调度组件。
Ribbon:负载均衡组件。
Archaius:配置管理API的组件,一个基于Java的配置管理库,主要用于多配置的动态获取。
Spring Cloud Bus:消息总线组件,常和Spring Cloud Config配合使用,用于动态刷新服务的配置。
Spring Cloud Sleuth:服务链路追踪组件,封装了Dapper、Zipkin、Kibina等组件,可以实时监控服务的链路调用情况。
Spring Cloud Data Flow:大数据操作组件,Spring Cloud Data Flow是Spring XD的替代品,也是一个混合计算的模型,可以通过命令行的方式操作数据流。
Spring Cloud Security:安全模块组件,是对Spring Security的封装,通常配合OAuth2使用来保护微服务系统的安全。
Spring Cloud Consul:该组件是Spring Cloud对Consul的封装,和Eureka类似,它是另一个服务注册和发现组件。
Spring Cloud Zookeeper:该组件是Spring Cloud对Zookeeper的封装,和Eureka、Consul类似,用于服务的注册和发现。
Spring Cloud Stream:数据流操作组件,可以封装Redis、RabbitMQ、Kafka等组件,实现发送和接收消息等。
Spring Cloud CLI:该组件是Spring Cloud对Spring Boot CLI的封装,可以让用户以命令行方式快速运行和搭建容器。
Spring Cloud Task:该组件基于Spring Task,提供了任务调度和任务管理的功能。
Spring Cloud Connectors:用于PaaS云平台连接到后端。2.3 Dubbo简介
Dubbo是阿里巴巴开源的一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务冶理方案。Dubbo广泛用于阿里巴巴的各大站点,有很多互联网公司也在使用这个框架,它包含如下核心内容。
RPC远程调用:封装了长连接NIO框架,如Netty和Mina等,使用的是多线程模式。
集群容错:提供了基于接口方法的远程调用的功能,并实现了负载均衡策略、失败容错等功能。
服务发现:集成了Apache的Zookeeper组件,用于服务的注册和发现。
Dubbo框架的架构图如图2-10所示。Dubbo架构的流程如下。
(1)服务提供者向服务中心注册服务。
(2)服务消费者订阅服务。
(3)服务消费者发现服务。
(4)服务消费者远程调度服务提供者进行服务消费,在调度过程中,使用了负载均衡策略、失败容错的功能。
(5)服务消费者和提供者,在内存中记录服务的调用次数和调用时间,并定时每分钟发送一次统计数据到监控中心。
Dubbo是一个非常优秀的服务治理框架,在国内互联网公司应用广泛,它具有以下特性。 连通性:注册中心负责服务的注册;监控中心负责收集调用次数、调用时间;注册中心、服务提供者、服务消费者为长连接。
- 健壮性:监控中心宕机不影响其他服务的使用;注册中心集群,任意一个实例宕机自动切换到另一个注册中心实例;服务实例集群,任意一个实例宕机,自动切换到另一个可用的实例。
- 伸缩性:可以动态增减注册中心和服务的实例数量。
- 升级性:服务集群升级,不会对现有架构造成压力。
2.4 Spring Cloud与Dubbo比较
Spring Cloud拥有很多的项目模块,包含了微服务系统的方方面面。Dubbo是一个非常优秀的服务治理和服务调用框架,但缺少很多功能模块,例如网关、链路追踪等。在项目模块上,Spring Cloud更具优势。
Spring Cloud的更新速度非常快,Camden.SR5版本发布于2017年2月,Camden.SR6版本发布于2017年3月,Dalston版本发布于2017年4月,Finchley版本发布于2018年6月,Greenwich版本发布于2019年2月,基本每年发布1~2次大版本,每月会发一次版本的迭代。从GitHub的代码仓库来看,Spring Cloud几乎每天都有更新。阿里巴巴于2011年10月开源了Dubbo,开源后的Dubbo发展迅速,大概每2~3个月有一次版本更新。然而,从在2013年3月开始,Dubbo暂停了版本更新,并只在2014年10月发布了一个小版本,修复了一个bug,之后长期处于版本停止更新的状态。直到2017年9月,阿里巴巴中间件部门重新组建了Dubbo团队,把Dubbo列为重点开源项目,并在2017年9~11月期间,一直保持每月一次版本更新的频率。
从学习成本上考虑,Dubbo的版本趋于稳定,文档完善,可以即学即用,没有太大难度。Spring Cloud 基于Spring Boot开发,需要开发者先学会Spring Boot。另外,Spring Cloud版本迭代快,需要快速跟进学习。Spring Cloud文档大多是英文的,要求学习者有一定的英文阅读能力。此外,Spring Cloud文档很多,不容易快速找到相应的文档。
从开发风格上来讲,Dubbo 更倾向于Spring Xml的配置方式,Dubbo官方也推荐这种方式。Spring Cloud基于Spring Boot,Spring Boot采用的是基于注解和JavaBean配置方式的敏捷开发。从开发速度上讲,Spring Cloud具有更高的开发和部署速度。
最后,Spring Cloud 的通信方式大多数是基于HTTP Restful风格的,服务与服务之间完全无关、无耦合。由于采用的是HTTP Rest,因此服务无关乎语言和平台,只需要提供相应API接口,就可以相互调用。Dubbo 的通信方式基于远程调用,对接口、平台和语言有强依赖性。如果需要实现跨平台调用服务,需要写额外的中间件,这也是Dubbox(支持HTTP协议)存在的原因。
Dubbo和Spring Cloud拥有各自的优缺点。Dubbo更易上手,并且广泛使用于阿里巴巴的各大站点,经历了“双11”期间高并发、大流量的检验,Dubbo框架非常成熟和稳定。SpringCloud服务框架严格遵守Martin Fowler 提出的微服务规范,社区异常活跃,它很可能成为微服务架构的标准。2.5 Kubernetes简介
Kubernetes是一个容器集群管理系统,为容器化的应用程序提供部署运行、维护、扩展、资源调度、服务发现等功能。
Kubernetes是Google运行Borg大规模系统达15年之久的一个经验总结。Kubernetes结合了社区的最佳创意和实践,旨在帮助开发人员将容器打包、动态编排,同时帮助各大公司向微服务方向进行技术演进。
它具有以下特点。
Planet Scale(大容量):使用Kubernetes的各大公司(包括Google)每周运行了数十亿个容器,这些容器的平台采用同样的设计原则。这些平台在不增加DevOps团队成员的情况下,可以让容器数量增加,节省了人力成本,达到了复用性。
Never Outgrow(永不过时):无论容器是运行在一个小公司的测试环境中,还是运行在一个全球化企业的大型系统里,Kubernetes都能灵活地满足复杂的需求。同时,无论业务多么复杂,Kubernetes都能稳定地提供服务。
Run Anywhere(随时随地运行):Kubernetes是开源的,可以自由地利用内部、混合或公共云的基础组件进行部署,让开发者可以将更多的时间和精力投入在业务上,而不是服务部署上。
Kubernetes开源免费,是Google在过去15年时间里部署、管理微服务的经验结晶,所以目前Kubernetes在技术社区也是十分火热。下面来看它提供的功能。
Automatic Binpacking(自动包装):根据程序自身的资源需求和一些其他方面的需求自动配置容器。Kubernetes能够最大化地利用机器的工作负载,提高资源的利用率。
Self-healing(自我修复):容器失败自动重启,当节点处于“死机”的状态时,它会被替代并重新编排;当容器达到用户设定的无响应的阀值时,它会被剔除,并且不让其他容器调用它,直到它恢复服务。
Horizontal scaling(横向扩展):可以根据机器的CPU的使用率来调整容器的数量,只需开发人员在管理界面上输入几个命令即可。
Service discovery and load balancing(服务发现和负载均衡):在不需要修改现有的应用程序代码的情况下,便可使用服务的发现机制。Kubernetes为容器提供了一个虚拟网络环境,每个容器拥有独立的IP地址和DNS名称,容器之间实现了负载均衡。
Automated rollouts and rollbacks(自动部署或回滚):Kubernetes支撑滚动更新模式,能逐步替换掉当前环境的应用程序和配置,同时监视应用程序运行状况,以确保不会同时杀死所有实例。如果出现问题,Kubernetes支持回滚更改。
Secret and configuration management(配置管理):部署和更新应用程序的配置,不需要重新打镜像,并且不需要在堆栈中暴露配置。
Storage orchestration(存储编排):自动安装所选择的存储系统,无论是本地存储、公共云提供商(如GCP或AWS),还是网络存储系统(如NFS、iSCSI、Gluster、Ceph、Cinder或Flocker)。
Batch execution(批量处理):除了服务之外,Kubernetes还可以管理批量处理和CI的工作负载,如果需要,可以替换容器,如NFS、iSCSI、Gluster、Ceph、Cinder或Flocker等。
从Kubernetes提供的功能来看,Kubernetes完全可以成为构建和部署微服务的一个工具,它是从服务编排上实现的,而不是代码实现的。目前国外有很多知名的公司在使用Kubernetes,如Google、eBay、Pearson等。由于它的开源免费,Microsoft、VMWare、RedHat、CoreOS等公司纷纷加入并贡献代码。Kubernetes技术吸引了一大批公司和技术爱好者,它已经成为容器管理的领导者。2.6 Spring Cloud与Kubernetes的比较
Spring Cloud是一个构建微服务的框架,而Kubernetes是通过对运行的容器的编排来实现构建微服务的。两者从构建微服务的角度和实现方式有很大的不同,但它们提供了构建微服务所需的全部功能。从提供的微服务所需的功能上看,两者不分上下,如表2-2所示。
Spring Cloud通过众多的类库来实现微服务系统所需的各个组件,同时不断集成优秀的组件,所以Spring Cloud组件是非常完善的。Spring Cloud基于Spring Boot框架,有快速开发、快速部署的优点。对于Java开发者来说,学习Spring Cloud的成本不高。
Kubernetes在编排上解决微服务的各个功能,例如服务发现、配置管理、负载均衡、容错等。Kubernetes不局限于Java平台,也不局限于语言,开发者可以自由选择开发语言进行项目开发。
与Kubernetes相比,Spring Cloud具有以下优点。
1 采用Java语言开发,基于Spring平台,继承了Spring Boot快速开发的优势,是Java程序员实现微服务的最佳实践。
2 Spring Cloud有大量的类库和资源,基本上能解决所有可能出现的问题。
与Kubernetes比较,Spring Cloud具有以下缺点。
1 依赖于Java语言,不支持跨语言。
2 Spring Cloud 需要在代码中关注微服务的功能点,例如服务发现、负载均衡等。Kubernetes则不需要关注这些。
下面介绍Kubernetes的优点和缺点,优点如下。
1 Kubernetes支持多种语言,并且是一个容器管理平台。Kubernetes使程序容器化,并在容器管理上提供了微服务的功能,例如配置管理、服务发现、负载均衡等。Kubernetes能够被应用于多种场合,例如程序开发、测试环境、创建环境等。
2 Kubernetes除了提供基本的构建微服务的功能外,还提供了环境、资源限制、管理应用程序的生命周期的功能。Kubernetes更像是一个平台,而Spring Cloud 是一个框架。
Kubernetes的缺点如下。
1 Kubernetes面向DevOps人员,普通的开发人员需要学习很多这方面的知识,学习成本非常高。
2 Kubernetes仍然是一个相对较新的平台,发展十分迅速。新特性更新得快,所以需要DevOps人员跟进,不断地学习。
Spring Cloud尝试从Java类库来实现微服务的所有功能,而Kubernetes尝试从容器编排上实现所有的微服务功能,两者的实现角度和方式不一样。个人觉得,两者最终的实现功能和效果上不分胜负,但从实现的方式上来讲,Kubernetes略胜一筹。Kubernetes面向DevOps人员,学习成本高。Spring Cloud有很多的类库,以Spring为基础,继承了Spring Boot快速开发的优点,为Java程序员开发微服务提供了很好的体验,学习成本也较低。所以二者比较,各有优势。没有最好的框架,也没有最好的工具,关键是要适合业务需求和满足业务场景。4 开发框架Spring Boot
4.1 Spring Boot简介
Spring Boot是由Pivotal公司开发的Spring框架,采用了生产就绪的观点,旨在简化配置,致力于快速开发。Spring Boot框架提供了自动装配和起步依赖,使开发人员不需要配置各种配置文件(比如xml文件),这种方式极大地提高了程序的开发速度。因此,Spring Boot框架已经成为新一代的Java Web开发框架。
在过去的Spring开发中,需要引入大量的xml文件来做配置。为了简化配置,Spring框架持续不断地做优化,比如在Spring 2.5版本中引入了包扫描,消除了显式的配置Bean;Spring 3.0又引入了基于JavaBean的配置,这种方式可以取代xml文件。尽管如此,在实际的开发中还是需要配置xml文件,例如配置Spring MVC、事务管理器、过滤器、切面等。
此外,在项目的开发过程中,会引入大量的第三方依赖,选择依赖是一件不容易的事,解决依赖与依赖之间的冲突也很耗费精力。所以,在以前的Spring 开发中,依赖管理也是一件棘手的事情。
Pivotal公司提供的Spring Boot框架解决了以前Spring 应用程序开发中的上述两个痛点,简化了应用的配置和依赖管理。4.1.1 Spring Boot的特点
对比传统的Spring框架,Spring Boot有三大特点:自动配置、起步依赖和Actuator 对运行期间状态的监控。
自动配置就是程序需要什么,Spring Boot就会装配什么。例如,当程序的pom文件引入了Feign的起步依赖,Spring Boot就会在程序中自动引入默认的Feign的配置Bean。再例如配置Feign的Decoder时,如果开发人员配置了Decoder Bean,Spring Boot就不会引入默认的Dec1
Spring Boot简介
Spring Boot是由Pivotal公司开发的Spring框架,采用了生产就绪的观点,旨在简化配置,致力于快速开发。Spring Boot框架提供了自动装配和起步依赖,使开发人员不需要配置各种配置文件(比如xml文件),这种方式极大地提高了程序的开发速度。因此,Spring Boot框架已经成为新一代的Java Web开发框架。
在过去的Spring开发中,需要引入大量的xml文件来做配置。为了简化配置,Spring框架持续不断地做优化,比如在Spring 2.5版本中引入了包扫描,消除了显式的配置Bean;Spring 3.0又引入了基于JavaBean的配置,这种方式可以取代xml文件。尽管如此,在实际的开发中还是需要配置xml文件,例如配置Spring MVC、事务管理器、过滤器、切面等。
此外,在项目的开发过程中,会引入大量的第三方依赖,选择依赖是一件不容易的事,解决依赖与依赖之间的冲突也很耗费精力。所以,在以前的Spring 开发中,依赖管理也是一件棘手的事情。
Pivotal公司提供的Spring Boot框架解决了以前Spring 应用程序开发中的上述两个痛点,简化了应用的配置和依赖管理。4.1.1 Spring Boot的特点
对比传统的Spring框架,Spring Boot有三大特点:自动配置、起步依赖和Actuator 对运行期间状态的监控。
自动配置就是程序需要什么,Spring Boot就会装配什么。例如,当程序的pom文件引入了Feign的起步依赖,Spring Boot就会在程序中自动引入默认的Feign的配置Bean。再例如配置Feign的Decoder时,如果开发人员配置了Decoder Bean,Spring Boot就不会引入默认的Decoder Bean。自动装配使得程序开发变得非常便捷、智能化。
在以前开发过程中,向项目添加依赖是一件非常有挑战的事情。选择版本,解决版本冲突,十分耗费精力。例如,程序需要Spring MVC的功能,那么需要引入spring-core、spring-web和spring-webmvc等依赖,但是如果程序使用Spring Boot的起步依赖,只需要加入spring-boot-starter-web的依赖,它会自动引入所有Spring MVC 功能的相关依赖。
Spring Boot提供了自动装配和起步依赖,解决了以前重量级的xml配置和依赖管理的各种问题,为应用程序提供了很好的便捷性。使用Spring Boot开发应用程序,有高效、敏捷和智能等优点,但是却带来了一系列的其他问题:开发者该怎么知道应用程序中注入了哪些Bean?应用程序的运行状态是怎么样的?为了解决这些问题,Spring Boot提供了Actuator组件,对应用程序的运行状态提供了监控功能。4.2 用IDEA构建Spring Boot工程
5 服务注册和发现Eureka
Eureka基本架构
Eureka的基本架构如第2章的图2-1所示,其中主要包括以下3种角色。
Register Service:服务注册中心,它是一个Eureka Server,提供服务注册和发现的功能。
Provider Service:服务提供者,它是一个Eureka Client,提供服务。
Consumer Service:服务消费者,它是一个Eureka Client,消费服务。
6 负载均衡Ribbon
Ribbon简介
负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式:一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如Ngnix;另一种是将负载均衡逻辑以代码的形式封装到服务消费者的客户端上,服务消费者客户端维护了一份服务提供者的信息列表,有了信息列表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。
Ribbon是Netflix公司开源的一个负载均衡的组件属于上述的第二种方式,是将负载均衡逻辑封装在客户端中,并且运行在客户端的进程里。Ribbon是一个经过了云端测试的IPC库,可以很好地控制HTTP和TCP客户端的负载均衡行为。
在Spring Cloud构建的微服务系统中,Ribbon作为服务消费者的负载均衡器,有两种使用方式,一种是和RestTemplate相结合,另一种是和 Feign相结合。Feign已经默认集成了Ribbon,关于Feign的内容将会在下一章进行详细讲解。
Ribbon有很多子模块,但很多模块没有用于生产环境,目前Netflix公司用于生产环境的Ribbon子模块如下。
ribbon-loadbalancer:可以独立使用或与其他模块一起使用的负载均衡器API。
ribbon-eureka:Ribbon结合Eureka客户端的API,为负载均衡器提供动态服务注册列表信息。
ribbon-core:Ribbon的核心API
7 声明式调用Feign
Feign是以前的名称,自从Feign移交给开源社区管理之后改名了OpenFeign,现在我们一般都是使用的Spring Cloud OpenFeign,虽然名称变了但是核心代码未改变。
Feign受Retrofit、JAXRS-2.0和WebSocket的影响,采用了声明式API接口的风格,将JavaHttp客户端绑定到它的内部。Feign的首要目标是将Java Http 客户端的书写过程变得简单。Feign的源码地址:https://github.com/OpenFeign/feign。
在Feign中使用HttpClient和OkHttp
在Feign中,Client是一个非常重要的组件,Feign最终发送Request请求以及接收Response响应都是由Client组件完成的。Client在Feign源码中是一个接口,在默认情况下,Client的实现类是Client.Default,Client.Default是由HttpURLConnnection来实现网络请求的。另外,Client还支持HttpClient和OkHttp来进行网络请求。
8 熔断器Hystrix
8.1 Hystrix简介
在分布式系统中,服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务会出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞。Hystrix是Netflix公司开源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。Hystrix是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。
8.2 Hystrix解决的问题
在复杂的分布式系统中,可能有几十个服务相互依赖,这些服务由于某些原因,例如机房的不可靠性、网络服务商的不可靠性等,导致某个服务不可用。如果系统不隔离该不可用的服务,可能会导致整个系统不可用。
例如,对于依赖30个服务的应用程序,每个服务的正常运行时间为99.99%,对于单个服务来说,99.99%的可用是非常完美的。
有99.9930 = 99.7%的可正常运行时间和0.3%的不可用时间,那么10亿次请求中有3000000次失败,实际的情况可能比这更糟糕。
如果不设计整个系统的韧性,即使所有依赖关系表现良好,单个服务只有0.01%的不可用,由于整个系统的服务相互依赖,最终对整个系统的影响是非常大的。
在微服务系统中,一个用户请求可能需要调用几个服务才能完成。如图8-1所示,在所有的服务都处于可用状态时,一个用户请求需要调用A、H、I和P服务。
当某一个服务,例如服务I,出现网络延迟或者故障时,即使服务A、H和P可用,由于服务I的不可用,整个用户请求会处于阻塞状态,并等待服务I的响应,如图8-2所示。
在高并发的情况下,单个服务的延迟会导致整个请求都处于延迟状态,可能在几秒钟就使整个服务处于线程负载饱和的状态。
某个服务的单个点的请求故障会导致用户的请求处于阻塞状态,最终的结果就是整个服务的线程资源消耗殆尽。服务的依赖性会导致依赖于该故障服务的其他服务也处于线程阻塞状态,最终导致这些服务的线程资源消耗殆尽,直到不可用,从而导致整个微服务系统都不可用,即雪崩效应。
为了防止雪崩效应,因而产生了熔断器模型。Hystrix是在业界表现非常好的一个熔断器模型实现的开源组件,它是Spring Cloud 组件不可缺少的一部分。
8.3 Hystrix设计原则
总的来说,Hystrix的设计原则如下。
防止单个服务的故障耗尽整个服务的Servlet容器(例如Tomcat)的线程资源。
快速失败机制,如果某个服务出现了故障,则调用该服务的请求快速失败,而不是线程等待。
提供回退(fallback)方案,在请求发生故障时,提供设定好的回退方案。
使用熔断机制,防止故障扩散到其他服务。
提供熔断器的监控组件Hystrix Dashboard,可以实时监控熔断器的状态。
8.4 Hystrix工作机制
第2章的图2-5展示了Hystrix的工作机制。首先,当服务的某个API接口的失败次数在一定时间内小于设定的阀值时,熔断器处于关闭状态,该API接口正常提供服务。当该API接口处理请求的失败次数大于设定的阀值时,Hystrix判定该API接口出现了故障,打开熔断器,这时请求该API接口会执行快速失败的逻辑(即fallback回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态。处于打开状态的熔断器,一段时间后会处于半打开状态,并将一定数量的请求执行正常逻辑。剩余的请求会执行快速失败,若执行正常逻辑的请求失败了,则熔断器继续打开;若成功了,则将熔断器关闭。这样熔断器就具有了自我修复的能力。
9 路由网关Spring Cloud Zuul
Zuul作为微服务系统的网关组件,用于构建边界服务(EdgeService),致力于动态路由、过滤、监控、弹性伸缩和安全。
Zuul作为Spring Cloud第一代网关现在不建议使用,建议使用Spring Cloud Gateway,但是Zuul的设置原理还是非常值得参考和学习的。
9.1 为什么需要Zuul
Zuul作为路由网关组件,在微服务架构中有着非常重要的作用,主要体现在以下6个方面。
Zuul、Ribbon以及Eureka相结合,可以实现智能路由和负载均衡的功能,Zuul能够将请求流量按某种策略分发到集群状态的多个服务实例。
网关将所有服务的API接口统一聚合,并统一对外暴露。外界系统调用API接口时,都是由网关对外暴露的API接口,外界系统不需要知道微服务系统中各服务相互调用的复杂性。微服务系统也保护了其内部微服务单元的API接口,防止其被外界直接调用,导致服务的敏感信息对外暴露。
网关服务可以做用户身份认证和权限认证,防止非法请求操作API接口,对服务器起到保护作用。
网关可以实现监控功能,实时日志输出,对请求进行记录。
网关可以用来实现流量监控,在高流量的情况下,对服务进行降级。
API接口从内部服务分离出来,方便做测试。
9.2 Zuul的工作原理
Zuul是通过Servlet来实现的,Zuul通过自定义的ZuulServlet(类似于Spring MVC的DispatcServlet)来对请求进行控制。Zuul的核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列的过滤器。Zuul包括以下4种过滤器。
PRE过滤器:它是在请求路由到具体的服务之前执行的,这种类型的过滤器可以做安全验证,例如身份验证、参数验证等。
ROUTING过滤器:它用于将请求路由到具体的微服务实例。在默认情况下,它使用HttpClient进行网络请求。
POST过滤器:它是在请求已被路由到微服务后执行的。一般情况下,用作收集统计信息、指标,以及将响应传输到客户端。
ERROR过滤器:它是在其他过滤器发生错误时执行的。
Zuul采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接相互通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。Zuul过滤器具有以下关键特性。
Type(类型):Zuul过滤器的类型,这个类型决定了过滤器在请求的哪个阶段起作用,例如Pre、Post阶段 等。
Execution Order(执行顺序):规定了过滤器的执行顺序,Order的值越小,越先执行。
Criteria(标准):过滤器执行所需的条件。
Action(行动):如果符合执行条件,则执行Action(即逻辑代码)。
Zuul请求的生命周期如图9-1所示,该图来自Zuul的官方文档。
当一个客户端Request请求进入Zuul网关服务时,网关先进入“pre filter”,进行一系列的验证、操作或者判断。然后交给“routing filter”进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完后,最后由“post filter”进行处理,该类型的处理器处理完之后,将Response信息返回给客户端。
ZuulServlet是Zuul的核心Servlet。ZuulServlet的作用是初始化ZuulFilter,并编排这些ZuulFilter的执行顺序。该类中有一个service()方法,执行了过滤器执行的逻辑。
@Override
public void service() throws ServletException, IOException {
try {
try {
preRoute();
}
catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
}
catch (ZuulException e) {
error(e);postRoute();
return;
}
try {
postRoute();
}
catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
从上面的代码可知,首先执行preRoute()方法,这个方法执行的是PRE类型的过滤器的逻辑。如果执行这个方法时出错了,那么会执行error(e)和postRoute()。然后执行route()方法,该方法是执行ROUTING类型过滤器的逻辑。最后执行postRoute(),该方法执行了POST类型过滤器的逻辑。
10 服务网关Spring Cloud Gateway
服务网关(Spring Cloud Gateway)是Spring Cloud官方推出的第二代网关框架,用于替代第一代网关Netflix Zuul,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关的基本功能。服务网关建立在Spring Framework 5之上,使用非阻塞模式,并且支持长连接Websocket。Netflix Zuul是基于Servlet的,采用HttpClient进行请求转发,使用阻塞模式。在性能上,服务网关优于Netflix Zuul,并且服务网关几乎实现了Netflix Zuul的全部功能。在使用和功能上,用服务网关替换掉Netflix Zuul的成本上是非常低的,几乎可以实现无缝切换。
服务网关作为整个分布式系统的流量入口,有着举足轻重的作用,列举如下。
- 协议转换,路由转发。
- 流量聚合,对流量进行监控,日志输出。
- 作为整个系统的前端工程,对流量进行控制,有限流的作用。
- 作为系统的前端边界,外部流量只能通过网关才能访问系统。
- 可以在网关层做权限判断。
- 可以在网关层做缓存。
10.1 服务网关的实现原理
Spring Cloud 的第一代网关Netflix Zuul有两大核心组件,分别为路由(Router)和过滤器(Filter)。和Netflix Zuul一样,服务网关的核心组件也有路由和过滤器,不同之处在于多了一个断言(Predicate),用来判断请求到底交给哪一个Gateway Web Handler处理。如图10-1所示,当客户端向服务网关服务网关发出请求时,首先将请求交给Gateway Handler Mapping处理,如果请求与路由匹配(这时就会用到断言),则将其发送到相应的Gateway Web Handler处理。Gateway Web Handler处理请求时会经过一系列的过滤器链。在图 10-1中,过滤器链被虚线划分,左半部分是过滤器链在发送代理请求之前处理,右半部分是发送代理请求之后处理。类似于Netflix Zuul,先执行所有的“pre”过滤器逻辑,然后进行代理请求。在发出代理请求后且收到代理服务的响应后,执行“post”过滤器逻辑。这与Netflix Zuul的处理过程十分相似。
在执行所有的“pre”过滤器逻辑时,一般可以实现鉴权、限流、日志输出、更改请求头和转换协议等功能;
在收到响应之后,会执行所有“post”过滤器的逻辑,这里可以对响应数据进行修改,比如更改响应头和转换协议等。
在上述处理过程中,有一个重要的点是将请求和路由进行匹配,这时需要用到断言来决定请求应该走哪一个路由。10.2 断言工厂
断言(Predicate)来自于Java 8的接口。该接口接受一个输入参数,返回一个布尔值结果,包含多种默认方法将断言组合成其他复杂的逻辑(比如:与、或、非)。
当一个请求到来时,需要首先将其交给断言工厂去处理。根据配置的断言规则进行,如果匹配成功,则进行下一步处理;如果没有匹配成功,则返回错误信息。服务网关内置了许多断言工厂(Predicate Factory),能够满足大部分的业务场景,当然用户也可以自己实现断言工厂。内置的断言工厂的源码在org.springframework.cloud.gateway.handler.predicate包中,有兴趣的读者请自行阅读。服务网关内置的断言工厂如图10-2所示。
到目前为止,很多读者可能并不了解断言的概念,下面以案例的形式来讲解服务网关内置的断言工厂。10.2.1 After路由断言工厂
After路由断言工厂可配置一个时间,只有请求的时间在配置时间之后,才交给路由去处理;否则报错,不通过路由。10.3 过滤器
上一节详细讲解了服务网关的断言,断言决定了请求由哪一个路由处理。在路由处理之前,需要经过“pre”类型的过滤器处理,处理返回响应后,可以由“post”类型的过滤器处理。10.3.1 过滤器的作用
由过滤器工作流程点可以知道过滤器有着非常重要的作用,在“pre”类型的过滤器可以实现参数校验、权限校验、流量监控、日志输出、协议转换等功能,在“post”类型的过滤器中可以做响应内容、响应头的修改、日志输出、流量监控等功能。要弄清楚为什么需要网关这一层,就不得不提到过滤器的作用了。
当我们有很多个服务时,以图10-3中的user-service、goods-service、sales-service等服务为例,客户端在请求各个服务的API时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
对于这样重复的工作,有没有办法做到更好呢?答案是肯定的。在微服务的上一层加一个全局的权限控制、限流、日志输出的API网关服务,然后将请求转发到具体的业务服务层。这个API网关服务起到服务边界的作用,外接的请求访问系统,必须先通过网关层,如图10-4所示。10.3.2 过滤器的生命周期
服务网关与Zuul类似,有“pre”和“post”两种方式的过滤器。客户端的请求先经过“pre”类型的过滤器,然后将请求转发到具体的业务服务,比如图10-5中的user-service,收到业务服务的响应之后,再经过“post”类型的过滤器处理,最后返回响应到客户端。
与Zuul不同的是,过滤器除了分为“pre”和“post”两种方式外,在服务网关中,从作用范围可将过滤器分为另外两种,一种是针对单个路由的网关过滤器(Gateway Filter),它在配置文件中的写法与断言类似;另一种是针对所有路由的全局网关过滤器(Global Gateway Filter)。下面从作用范围划分的角度来讲解这两种过滤器。10.3.3 网关过滤器
网关过滤器允许以某种方式修改传入的HTTP请求报文或传出的HTTP响应报文。过滤器可以限定作用在某些特定请求路径上。服务网关包含许多内置的网关过滤器工厂(Gateway FilterFactory)。
网关过滤器工厂同10.2节中介绍的断言工厂类似,都是在配置文件application.yml中配置,遵循“约定大于配置”的规则,只需要在配置文件中配置网关过滤器工厂的类名的前缀,而不需要写全类名。比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全类名。在配置文件中配置的网关过滤器工厂会由它对应的过滤器工厂类处理。
服务网关内置的过滤器工厂如表10-1所示。10.3.4 全局过滤器
服务网关根据作用范围划分为网关过滤器(GatewayFilter)和全局过滤器(GlobalFilter),二者区别如下。
GatewayFilter:需要通过spring.cloud.routes.filters配置在具体路由下,只作用在当前路由上;或通过spring.cloud.default-filters配置在全局中,作用在所有路由上。
GlobalFilter:不需要在配置文件中配置,作用在所有路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器。它是将请求业务以及路由的Url转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
服务网关内置的GlobalFilter如图10-7所示。
图10-7中的每一个GlobalFilter都作用在每一个路由上,能够满足大多数需求。如果遇到定制业务,可以编写满足特定需求的GlobalFilter。在下面的案例中,将讲述如何编写自己的GlobalFilter,该GlobalFilter会校验请求中是否包含请求参数“token”,如果不包含请求参数“token”,则不转发路由;否则,执行正常的逻辑。代码如下:
上述的TokenFilter需要实现GlobalFilter接口和Ordered接口,这和实现GatewayFilter很相似。根据ServerWebExchange获取ServerHttpRequest,然后判断ServerHttpRequest中是否含有参数token,如果没有,则完成请求,终止转发;否则,执行正常的逻辑。
接着需要将TokenFilter以Bean的形式注入Spring Ioc容器中,代码如下:
启动工程,使用如下的curl命令请求:curl localhost:8081/get可以看到请求没有被转发,而是被终止了,并在控制台打印了如下日志:@Bean
public TokenFilter tokenFilter(){
return new TokenFilter();
}
2018-11-16 15:30:13.543 INFO 19372 —- [ctor-http-nio-2] gateway.TokenFilter
上述日志显示了请求进入了没有传“token”的逻辑。10.4 限流
高并发系统中往往需要做限流,一方面是为了防止流量突发使服务器过载,另一方面是为了防止流量攻击。
常见的限流方式有Hystrix适用线程池隔离,当超过线程池的负载时,走熔断的逻辑;在一般应用服务器中,比如Tomcat容器是通过限制它的线程数来控制并发的;也可以通过时间窗口的平均速度来控制流量。常见的限流纬度有通过IP限流、通过请求的Url限流、通过用户访问频次限流。
一般限流都发生在网关层,比如Nginx、Openresty、Kong、Zuul和服务网关等,也可以在应用层通过AOP方式去做限流。10.4.1 常见的限流算法
1 计数器算法
计数器算法是使用计数器实现的限流算法,实现简单。比如,限流策略为在1秒内只允许有100个请求通过,算法的实现思路是第一个请求进来时计数为1,后面每通过一个请求计数加1。当计数满100后,后面的请求全部被拒绝。这种技术算法非常简单,当流量突发时,它只允许前面的请求通过,一旦计数满了,拒绝所有后续请求,这种现象称为“突刺现象”。
2 漏桶算法
漏桶算法可以消除“突刺现象”,其内部有一个容器,类似漏斗,当请求进来时,相当于水倒入漏斗,然后从容器中均匀地取出请求进行处理,处理速率是固定的。不管上面流量多大,都全部装进容器,下面流出的速率始终保持不变。当容器中的请求数装满了,就直接拒绝请求。
3 令牌桶算法
令牌桶算法是对漏桶算法的改进,漏桶算法只能均匀地处理请求。令牌桶算法能够在均匀处理请求的情况下,应对一定程度上的突发流量。令牌桶算法需要一个容器来存储令牌,令牌以一定的速率均匀地向桶中存放,当超过桶的容量时,桶会丢弃多余的令牌。当一个请求进来时,需要从令牌桶获取令牌,获取令牌成功,则请求通过;如果令牌桶中的令牌消耗完了,则获取令牌失败,拒绝请求。10.4.2 服务网关的限流
服务网关中过滤器,因此可以在“pre”类型的过滤器中自行实现上述3种限流算法。但限流作为网关最基本的功能,服务网关官方只提供了RequestRateLimiterGatewayFilterFactory这个类,使用Redis和lua脚本实现令牌桶算法进行限流。具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中,lua脚本在如图10-8所示的文件夹中。11 服务注册和发现Consul
第5章已全面讲解了服务注册和发现组件Eureka,本章讲解另一个服务注册和发现组件Consul。11.1 什么是Consul
Consul是HashiCorp公司推出的开源软件,使用Go语言编写,提供了分布式系统的服务注册和发现、配置等功能,这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格。Consul不仅具有服务治理的功能,而且使用分布式一致协议RAFT算法实现,有多数据中心的高可用方案,并且很容易和Spring Cloud等微服务框架集成,具有简单、易用、可插排等特点。简而言之,Consul提供了一种完整的服务网格解决方案。11.1.1 基本术语
代理(Agent):是一直运行在Consul集群中每个节点上的守护进程,通过运行consulagent命令来启动。代理可以以客户端或服务端模式运行。无论是客户端节点,还是服务端节点,都必须运行代理,因此将节点称为客户端或服务器更容易理解。所有代理都可以通过DNS或HTTP接口来检查服务,并保持服务同步。
客户端(Client):客户端是所有RPC转发到服务端的代理。这个客户端是相对无状态的。客户端唯一执行的后台活动是加入LAN gossip池,资源开销很小。
服务端(Server):服务端是具有扩展责任的代理,包括参与Raft选举、维护集群状态、响应RPC查询、与其他数据中心交换WAN,以及将查询转发给领导者(Leader)或远程数据中心。
数据中心(Data Center):是一个私有的、低延迟且高带宽的网络环境,由多个客户端和服务端构成。
共识(Consensus):在文档中,我们使用共识来表示对当选领导者的协议以及交易顺序的协议。由于这些事务应用于有限状态机,因此我们对共识的定义意味着复制状态机的一致性。
Gossip:Consul建立在Serf的基础上,它提供了一个完整的Gossip协议。
LAN Gossip:是指局域网Gossip,包含位于同一个数据中心的所有节点。
WAN Gossip:是指仅包含服务端的WAN Gossip池。这些服务端位于不同的数据中心,通常通过互联网或广域网进行通信。
远程调用(RPC):是一个允许客户端请求服务端的请求-响应机制。11.1.2 Consul的特点和功能
服务发现:Consul客户端可以向Consul注册服务,例如API服务或者MySQL服务,其他客户端可以使用Consul来发现服务的提供者。Consul支持使用DNS或HTTP来注册和发现服务。
运行时健康检查:Consul客户端可以提供任意数量的运行状况检查机制,这些检查机制可以与给定服务(Web服务器返回200 OK)或本地节点(内存利用率低于90%)相关联。这些信息可以用来监控群集的运行状况,服务发现组件可以使用监控信息来路由流量,使流量远离不健康的服务。
KV存储:应用程序可以将Consul的键/值存储起来用于任何需求,包括动态配置、功能标记、协调、领导者选举等。KV存储采用HTTP API,因此易于使用。
安全服务通信:Consul可以为服务生成和分发TLS证书,以建立相互的TLS连接。
多数据中心:Consul支持多个数据中心,这意味着Consul用户不必担心构建额外的抽象层来扩展到多个区域。11.1.3 Consul的原理
每个提供服务的节点都运行了Consul的代理(Agent),运行代理时不需要服务发现和获取配置的KV键值对,代理只负责监控检查。代理节点可以和一个或多个Consul服务端通信。Consul服务端是存储和复制数据的节点,采用RAFT算法保证了数据一致性,并选出了领导者,建议使用3台或5台Consul服务端,以避免发生数据丢失的情况。Consul支持多个数据中心,建议每个数据中心使用多个Consul Server作为一个集群。
如果微服务组件需要发现服务,可以查询任何数据中心的Consul服务端或Consul客户端,Consul客户端会自动将查询转发给Consul 服务端。
需要发现其他服务或节点的基础架构组件可以查询任何Consul服务端或任何Consul代理。如果是查询Consul Agent,Consul Agent会自动将查询请求转发给Consul服务端。发生跨数据中心服务发现或配置请求时,本地Consul服务端会将请求转发到远程数据中心,并返回结果。11.1.4 Consul的基本架构
Consul的基本架构如图11-1所示,其中有两个数据中心,标记为“数据中心1”和“数据中心2”。在大型分布式系统中,多数据中心是十分常见的,Consul对多数据中心有着非常强大的支持。
在每个数据中心中,客户端和服务端是混合的。一般建议使用奇数个服务端,比如3台或5台。这是基于有故障情况下的可用性和性能之间的权衡结果,因为越多的机器加入,则达成共识的速度越慢。但这并不是限制客户端的数量,客户端可以很容易地扩展到数千台或数万台。
同一个数据中心的所有节点都加入了Gossip协议,意味着Gossip协议池包含该数据中心的所有节点,这样做有如下3个目的。
不需要在客户端上配置服务端地址,服务发现都是自动完成的。
检测节点故障的工作不放在服务端上,而是分布式的,使得故障检测比心跳机制有更高的可扩展性。
数据中心可被用作消息传递层,比如发生领导者选举等重要事件时起到通知作用。
数据中心中的每个服务端节点都是Raft节点集合的一部分。它们共同选举一个领导者,即一个具有额外职责的选定服务端。领导者负责处理所有查询和事务,作为共识协议的一部分,还必须将事务复制到所有对等体。因此当非领导者的服务端收到RPC请求时,它会将请求转发给群集的领导者。
服务端节点也作为WAN Gossip协议池的一部分,不同于LAN Gossip协议池的是,它对较高的网络延迟进行了优化,并且只包含其他Consul服务端节点。这个协议池的作用是允许数据中心能够以低接触的方式发现彼此,使得一个新数据中心可以很容易地加入现存的WAN Gossip协议池。因为服务端都运行在这个协议池中,支持跨数据中心请求。当一个服务端收到来自另一个数据中心的请求时,它会将其转发到正确数据中心的随机服务端。该服务端再转发给本地领导者。
数据中心之间的耦合度非常低,但由于故障检测、连接缓存和多路复用等机制,跨数据中心请求相对快速且可靠。11.1.5 Consul服务注册发现流程
Consul最广泛的用途之一就是作为服务注册中心,同时也可以作为配置中心。作为服务注册中心时,Consul同Eureka类似,其注册和发现过程如图11-2所示。
在图11-2所示的过程中有3个角色,分别是服务注册中心、服务提供者、服务消费者。
服务提供者(Provider)在启动时,会向Consul发送一个请求,将其Host、IP、应用名、健康检查等元数据信息发送给Consul。
Consul接收到服务提供者的注册后,定期向其发送健康检查的请求,检验服务提供者是否健康。
服务消费者(Consumer)会从注册中心Consul中获取服务注册列表,当服务消费者消费服务时,会根据服务名从服务注册列表中获取具体服务的实例(一个或者多个)信息,再根据负载均衡策略完成服务的调用。11.2 Consul与Eureka比较
Eureka是Netflix公司开源的服务注册中心组件,是Spring Cloud官方比较推荐的服务注册中心解决方案。目前大部分企业级应用都在使用Consul作为服务注册中心。
在Eureka的架构体系中,分为Eureka服务端和Eureka客户端。Eureka服务端可以支持多数据中心,每个数据中心有一组Eureka服务端。通常,Eureka客户端使用嵌入式SDK来注册和发现服务。
Eureka使用注册列表复制的方式提供弱一致的服务视图。当Eureka客户端向Eureka服务端注册时,该Eureka服务端将尝试复制到其他Eureka服务端,但不提供强一致性保证。服务注册成功后,要求Eureka客户端对Eureka服务端进行心跳健康检测。不健康的服务或节点将停止心跳,从而导致它们的监控检查超时,并从注册表中删除。服务注册的请求可以路由到任何Eureka服务端,Eureka服务端可以提供过时或丢失的数据。这种简化的模型能够轻松地搭建Eureka服务端集群的高可用和强扩展性。
相比Eureka,Consul提供了更多高级功能,包括更丰富的运行时健康检查、键值存储和多数据中心的相互感知。Consul提供了强一致性的保证,因为Consul服务端使用Raft协议进行状态的复制。Consul支持丰富的运行时健康检查,包括TCP、HTTP、Nagios、Sensu兼容脚本等。Consul客户端基于Gossip的健康检查。服务注册请求会被路由到Consul领导者的节点。
Consul的强一致性意味着它需要用领导者选举和集群协调来锁定服务。Eureka采用的是P2P的复制模式,不保证复制操作一定能成功;Eureka不提供强制性的保证,而是提供一个最终一致性的服务实例视图。Eureka客户端在Eureka服务端的注册信息有一个带期限的服务续约,一旦Eureka服务端在指定时间内没有收到Eureka客户端发送的心跳,则Eureka服务端会认定Eureka客户端注册的服务是不健康的,将会其从注册表中删除定时任务。Consul与Eureka不同的是,Consul采用Raft算法,可以提供强一致性的保证,Consul的代理(Agent)相当于Netflix Ribbon +Netflix Eureka客户端,而且对应用来说相对透明。同时,相比Eureka这种集中式的心跳检测机制,Consul的代理可以参与到基于Goosip协议的健康检查中,分散了服务端的心跳检测压力。除此之外,Consul为多数据中心提供了开箱即用的原生支持。11.5 使用Spring Cloud Consul Config做服务配置中心
Consul不仅能用来服务注册和发现,而且Consul支持键值对的存储,可以用来做分布式配置中心。Spring Cloud提供了Spring Cloud Consul Config依赖去和Consul相集成,用来做分布式配置中心。12 配置中心Spring Cloud Config
前面的章节详细讲解了Spring Cloud Netflix组件,包括服务注册和发现组件Eureka、负载均衡组件Ribbon、声明式调用Feign、熔断器组件Hystrix和路由网关组件Zuul。本章讲述SpringCloud的另一组件——分布式配置中心Spring Cloud Config。
微服务配置中心选择:Apache Nacos,Apollo,Baidu DisConfig,Spring Cloud Config;Spring Cloud Config作为Spring Cloud第一代服务配置中心,不再建议使用。13 服务链路追踪Spring Cloud Sleuth
Spring Cloud Sleuth是Spring Cloud的一个组件,它的主要功能是在分布式系统中提供服务链路追踪的解决方案。13.1 为什么需要Spring Cloud Sleuth
微服务架构是一个分布式架构,微服务系统按业务划分服务单元,一个微服务系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性较高,如果出现了错误和异常,很难去定位。主要体现在一个请求可能需要调用很多个服务,而内部服务的调用复杂性决定了问题难以定位。所以在微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出现问题能够快速定位的目的。
以第2章的图2-8为例来说明,在微服务系统中,一个来自用户的请求先到达前端A(如前端界面),然后通过远程调用,到达系统的中间件B、C(如负载均衡、网关等),最后到达后端服务D、E,后端经过一系列的业务逻辑计算,最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程用数据记录下来呢?这就需要用到服务链路追踪。
Google开源了Dapper链路追踪组件,并在2010年发表了论文《Dapper, a Large-ScaleDistributed Systems Tracing Infrastructure》,这篇论文是业内实现链路追踪的标杆和理论基础,具有很高的参考价值。
目前,常见的链路追踪组件有Google的Dapper、Twitter的Zipkin,以及阿里的Eagleeye(鹰眼),Apache SkyWalking等,它们都是非常优秀的链路追踪开源组件。
可以根据需求选择合适的链路追踪组件,比如Apache SkyWalking可能是一个很好的选择。13.2 基本术语
Spring Cloud Sleuth采用了Google的开源项目Dapper的专业术语。
(1)Span:基本工作单元,发送一个远程调度任务就会产生一个Span,Span是用一个64位ID唯一标识的,Trace是用另一个64位ID唯一标识的。Span还包含了其他的信息,例如摘要、时间戳事件、Span的ID以及进程ID。
(2)Trace:由一系列Span组成的,呈树状结构。请求一个微服务系统的API接口,这个API接口需要调用多个微服务单元,调用每个微服务单元都会产生一个新的Span,所有由这个请求产生的Span组成了这个Trace。
(3)Annotation:用于记录一个事件,一些核心注解用于定义一个请求的开始和结束,这些注解如下。
cs-Client Sent:客户端发送一个请求,这个注解描述了Span的开始。
sr-Server Received:服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳,便可得到网络传输的时间。
ss-Server Sent:服务端发送响应,该注解表明请求处理的完成(当请求返回客户端),用ss的时间戳减去sr时间戳,便可以得到服务器请求的时间。
cr-Client Received:客户端接收响应,此时Span结束,如果cr的时间戳减去cs时间戳,便可以得到整个请求所消耗的时间。
Spring Cloud Sleuth提供了一套完整的链路解决方案,它可以结合Zipkin,将链路数据发送到Zipkin,并利用Zipkin来存储链路信息,也可以利用Zipkin UI来展示数据。
那么什么是Zipkin呢?
Zipkin是Twitter的一个开源项目,它基于Google的Dapper实现,被业界广泛使用。Zipkin致力于收集分布式系统的链路数据,提供了数据持久化策略,也提供面向开发者的API接口,用于查询数据,还提供了UI组件帮助我们查看具体的链路信息。
Zipkin提供了可插拔式的数据存储方式,目前支持的数据存储有In-Memory、MySQL、Cassandra和ElasticSearch。
Zipkin的架构图如图13-1所示,它主要由4个核心组件构成。
Collector:链路数据收集器,主要用于处理从链路客户端发送过来的链路数据,将这些数据转换为Zipkin内部处理的Span格式,以支持后续的存储、分析和展示等功能。
Storage:存储组件,用来存储接收到的链路数据,默认会将这些数据存储在内存中,同时支持多种存储策略,比如将链路数据存储在 MySQL、Cassandra和ElasticSearch中。
RESTful API:API组件,它是面向开发者的,提供外部访问API接口,可以通过这些API接口来自定义展示界面。
Web UI:UI组件,基于API接口实现的上层应用,用户利用UI组件可以很方便地查询和分析链路数据。
14 微服务监控SpringBoot Admin
SpringBoot Admin 用于管理和监控一个或者多个SpringBoot 程序。SpringBoot Admin分为Server 端和Client 端, Client 端可以通过Http 向Server 端注册,也可以结合SpringCloud的服务注册组件Eureka/Consul进行注册。SpringBoot Admin 提供了用React编写的Ul 界面,用于管理和监控。其中监控内容包括SpringBoot 的监控组件Actuator 的各个Http Endpoint(端点),也支持更高级的功能,包括Jmx 、Loglevel 等。
能够监控的信息和功能如下:
- 显示健康状况。
- 显示详细信息,例如:JVM和内存指标、micrometer.io指标、数据源指标。
- 缓存指标。
- 显示构建信息编号。
- 关注并下载日志文件。
- 查看jvm系统和环境属性。
- 查看Spring Boot配置属性。
- 支持Spring Cloud的postable/env-和/refresh-endpoint。
- 轻松的日志级管理。
- 与JMX-beans交互。
- 查看线程转储。
- 查看Http跟踪。
- 查看auditevents。
- 查看http-endpoints。
- 查看计划任务。
- 查看和删除活动会话(使用spring-session)。
- 查看Flyway / Liquibase数据库迁移。
- 下载heapdump。
- 状态变更通知(通过电子邮件、Slack、Hipchat等方式)。
- 状态更改的事件日志(非持久性)。
本章主要从以下3个方面讲解Spring Boot Admin监控组件:
- 使用Spring Boot Admin监控Spring Boot应用程序。
- 使用Spring Boot Admin监控Spring Cloud微服务系统。
- Spring Boot Admin集成Security安全验证和报警邮件的功能。
15 Spring Boot Security详解
15.1 Spring Security简介
15.1.1 什么是Spring Security
Spring Security是Spring Resource 社区的一个安全组件,Spring Security为JavaEE企业级开发提供了全面的安全防护。安全防护是一个不断变化的目标,Spring Security通过版本不断迭代来实现这一目标。Spring Security采用“安全层”的概念,使每一层都尽可能安全,连续的安全层可以达到全面的防护。Spring Security可以在Controller层、Service层、DAO层等以加注解的方式来保护应用程序的安全。Spring Security提供了细粒度的权限控制,可以精细到每一个API接口、每一个业务的方法,或者每一个操作数据库的DAO层的方法。Spring Security提供的是应用程序层的安全解决方案,一个系统的安全还需要考虑传输层和系统层的安全,例如采用Https协议、服务器部署防火墙、服务器集群隔离部署等。15.1.2 为什么选择Spring Security
使用Spring Security有很多原因,其中一个重要原因是它对环境的无依赖性、低代码耦合性。将工程重现部署到一个新的服务器上,不需要为Spring Security做什么工作。SpringSecurity提供了数十个安全模块,模块与模块间的耦合性低,模块之间可以自由组合来实现特定需求的安全功能,具有较高的可定制性。总而言之,Spring Security具有很好的可复用性和可定制性。
在安全方面,有两个主要的领域,一是“认证”,即你是谁;二是“授权”,即你拥有什么权限,Spring Security的主要目标就是在这两个领域。“认证”是认证主体的过程,通常是指可以在应用程序中执行操作的用户、设备或其他系统。“授权”是指决定是否允许已认证的主体执行某一项操作。
安全框架多种多样,那为什么选择Spring Security作为微服务开发的安全框架呢?JavaEE有另一个优秀的安全框架Apache Shiro,Apache Shiro在企业级的项目开发中十分受欢迎,一般使用在单体服务中。Spring Security来自Spring Resource社区,采用了注解的方式控制权限,熟悉Spring的开发者很容易上手Spring Security。还有一个原因就是Spring Security易于应用于Spring Boot工程,也易于集成到采用Spring Cloud构建的微服务系统中。15.1.3 Spring Security提供的安全模块
在安全验证方面,Spring Security 提供了很多的安全验证模块。大部分的验证模块来自第三方的权威机构或者一些相关的标准制定组织,Spring Security自身也提供了一些验证模型。Spring Security目前支持对以下技术的整合。(注:这部分内容来自Spring Security官方文档。)
HTTP BASIC头认证(一个基于IETF RFC的标准)。
HTTP Digest头认证(一个基于IETF RFC的标准)。
HTTP X.509客户端证书交换认证(一个基于IETF RFC的标准)。
LDAP(一种通用的跨平台身份验证,特别是在大型软件架构中)。
基于表单的验证。
OpenID验证。
基于预先建立的请求头的验证。
Jasig Central Authentication Service,也被称作CAS,是一个流行的开源单点登录系统。
远程方法调用(RMI)和HttpInvoker(Spring远程协议)的认证。
自动“记住我”的身份验证。
匿名验证(允许每一次未经身份验证的调用)。
Run-as身份验证(每一次调用都需要提供身份标识)。
Java认证和授权服务。
Java EE 容器认证。
Kerberos。
Java开源的单点登录。
OpenNMS网络管理平台。
AppFuse 。
AndroMDA 。
Mule ESB 。
Direct Web Request (DWR)。
Grails 。
Tapestry 。
JTrac 。
Jasypt 。
Roller 。
Elastic Path 。
Atlassian Crowd。
自己创建的认证系统。
以上都是Spring Security支持的安全验证模块,其中带的是来自第三方的安全验证模块,Spring Security对这些模块做了整合和封装。15.2 Spring Boot Security与Spring Security的关系
在Spring Security框架中,主要包含了两个依赖Jar,分别是spring-security-web依赖和spring-security-config依赖,代码如下:
Spring Boot对Spring Security框架做了封装,仅仅是封装,并没有改动Spring Security这两个包的内容,并加上了Spring Boot的起步依赖的特性。spring-boot-starter-security依赖如下:<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
进入spring-boot-starter-security的pom文件,可以发现pom文件包含了Spring Security的两个Jar包,并移除了这两个Jar包的apo功能,引入了apo的依赖,另外包含了spring-boot-starter的依赖。由此可见,spring-boot-starter-security是对Spring Security的一个封装。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
16 使用Spring Cloud OAuth2保护微服务系统
在Spring Cloud构建的微服务系统中使用Spring Cloud OAuth2来保护微服务系统。16.1 什么是OAuth2
OAuth2是一个标准的授权协议。OAuth2取代了在2006年创建的OAuth1的工作,OAuth2对OAuth1没有做兼容,即完全废弃了OAuth1。OAuth2允许不同的客户端通过认证和授权的形式来访问被其保护起来的资源。在认证和授权的过程中,主要包含以下3种角色。
服务提供方Authorization Server。
资源持有者Resource Server。
客户端Client。
OAuth2的认证流程如图16-1所示,具体如下。
用户(资源持有者)打开客户端,客户端询问用户授权。
用户同意授权。
客户端向授权服务器申请授权。
授权服务器对客户端进行认证,也包括用户信息的认证,认证成功后授权给予令牌。
客户端获取令牌后,携带令牌向资源服务器请求资源。
资源服务器确认令牌正确无误,向客户端释放资源。16.2 如何使用Spring OAuth2
OAuth2协议在Spring Resource中的实现为Spring OAuth2。Spring OAuth2分为两部分,分别是OAuth2 Provider和OAuth2 Client。17 使用Spring Security OAuth2和JWT保护微服务系统
上一章讲述了如何通过Spring Security OAuth2来保护Spring Cloud架构的微服务系统。上一章的系统有一个缺陷,即每次请求都需要经过Uaa服务去验证当前Token的合法性,并且需要查询该Token对应的用户的权限。在高并发场景下,会存在性能瓶颈,改善的方法是将Uaa服务集群部署并加上缓存。本章针对上一章的系统的缺陷,采用Spring Security OAuth2和JWT的方式,避免每次请求都需要远程调度Uaa服务。采用Spring Security OAuth2和JWT的方式,Uaa服务只验证一次,返回JWT。返回的JWT包含了用户的所有信息,包括权限信息。18 使用Spring Cloud构建微服务综合案例