第1章 基础知识

什么是微服务架构

1. 简单的说,**微服务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful API进行通信。被拆分成的每一个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写**。

与单体系统的区别

1. 在以往传统的企业系统架构中,我们针对一个复杂的业务需求通常使用对象或业务类型来构建一个单体项目。在项目中我们通常将需求分为三个主要部分:数据库、服务端处理、前端展现。在业务发展初期,由于所有的业务逻辑在一个应用中,开发、测试、部署都还比较容易且方便。但是,随着企业的发展,系统为了应对不同的业务需求会不断为该单体项目增加不同的业务模块;同时随着移动端设备的进步,前端展现模块已经不仅仅局限于Web的形式,这对于系统后端向前端的支持需要更多的接口模块。单体应用由于面对的业务需求更为宽泛,不断扩大的需求会使得单体应用变得越来越臃肿。单体应用的问题就逐渐凸显出来,***由于单体系统部署在一个进程内,往往我们修改了一个很小的功能,为了部署上线会影响其它功能的运行。而且,单体应用中的这些功能模块的使用场景、并发量、消耗的资源类型都各有不同,对于资源的利用又互相影响,这样使得我们对各个业务模块的系统容量很难给出较为准确的评估。***所以,单体,引用在初期虽然可以非常方便的进行开发和使用,但是随着系统的发展,维护成本会变得越来越大,且难以控制。
2. 为了解决单体系统变得庞大臃肿之后产生的难以维护的问题,微服务框架诞生了并被大家所关注。我们将系统的不同功能模块拆分成多个不同的服务,这些服务都能够独立部署和扩展。由于每个服务都运行在自己的进程内,在部署上有稳固的边界,这样每个服务的更新都不会影响其他服务的运行。同时,由于是独立部署的,我们可以更准确地为每一个服务评估性能容量,通过配合服务间的协作流程也可以更容易的发现系统的瓶颈位置,以及给出较为准确的系统级容量评估。

如何实施微服务

1. 在实施微服务之前,我们必须知道,微服务虽然有非常多吸引人的优点,但是也因为服务的拆分引发了诸多原本在单体应用中没有的问题。
	1. **运维的新挑战**:在微服务架构中,运维人员需要维护的进程数量会大大增加。有条不紊的将这些进程编排和组织起来不是一件容易的事,传统的运维人员往往会很难适应这样的改变。我们需要云纹人员有更多的技能来应对这样的挑战,运维过程需要更多的自动化,这就要求运维人员具备一定的开发能力来编排运维过程并让它们能自定运行起来。
	2. ***接口的一致性***:虽然我们拆分了服务,但是业务逻辑上的依赖并不会消除,只是从单体应用中的代码依赖变成了服务间的通信依赖。而当我们对原有接口进行了一些修改,那么交互方也需要协调这样的改变来进行发布,以保证接口的正确调用。我们需要更完善的接口和版本管理,或是严格的遵循开闭原则。
	3. ***分布式的复杂性***:由于拆分后的各个微服务都是独立部署并运行在各自的进程内,他们只能通过通信来进行协作,所以分布式环境的问题都将是微服务架构系统设计时需要考虑的重要因素,比如网络延迟、分布式事务、异步消息等。

服务组件化

1. ***组件***,是一个可以独立更换和升级的单元。
2. 在微服务架构中,需要我们对服务进行组件化分解。服务,是一种进程外的组件(这句话相当不理解),它通过HTTP等通信协议进行协作,而不是像传统组件那样以嵌入的方式协同工作。每一个服务都独立开发、部署,可以有效避免一个服务的修改引起整个系统的重新部署。

智能端点与哑管道

1. 在单体应用中,组件间直接通过函数调用的方式进行交互协作。而在微服务架构中,由于服务不在一个进程中,组件间的通信模式发生了变化,若仅仅将原本在进程内的方法调用改成RPC方式的调用,会导致微服务之间产生繁琐的通信,使得系统表现更为糟糕,所有,我们需要更粗粒度的通信协议。
2. 在微服务架构中,通常会使用一下两种服务调用方式:
	1. 第一种,使用HTTP的RESTful API或者轻量级的消息发送协议,实现信息传递与服务调用的触发。
	2. 第二种,通过在轻量级消息总线上传递消息,类似RabbitMQ等一些提供可靠异步交换的中间件。

去中心化治理

...后面的内容暂时无法理解,望日后再看这本书能有所收获。

第2章 微服务构建: Spring Boot

框架简介

1. Spring Boot的宗旨并非要重写Spring或是替代Spring,而是希望通过设计大量的自动化配置等方式来简化Spring原有样板化的配置,使得开发者可以快速构建应用。
2. 除了解决配置问题之外,Spring Boot还通过一系列Starter POMs的定义,让我们整合各项功能的时候,不需要在Maven的pom.xml中维护那些错综复杂的依赖关系,而是通过类似模块化的Starter模块定义来引用,使得依赖管理工作变得更为简单。

快速入门

配置详解

配置文件

1. Spring Boot的默认配置文件位置为src/main/resources/application.properties。
2. ***注意:***YAML目前还有一些不足,他无法通过@PropertySource注解来加载配置。但是,YAML将属性加载到内存中保存的时候是有序的,所以当配置文件中的信息需要具备顺序含义时,YAML的配置方式比起properties配置文件更有优势。

自定义参数

1. @Value注解加载属性值的时候可以支持两种表达式来进行配置
	1. 一种是PlaceHolder方式,格式为${...},大括号内为PlaceHolder。
	2. 另一种是使用SpEL表达式(Spring Expression Language),格式为#{...},大括号内为SpEL表达式。

参数引用

1. 在application.properties中的各个参数之间可以直接通过使用PlaceHolder的方式来进行引用。

使用随机数

1. 在一些特殊情况下,我们希望有些参数每次被加载的时候不是一个固定的值,比如密钥、服务端口等。在Spring Boot的属性配置文件中,可以通过使用${random}配置来产生随机的int值、long值或者string字符串,这样我们就可以容易地通过配置随机生成属性,而不是在程序中通过编码来实现这些逻辑。
	${random}的配置方式主要有以下几种
	# 随机字符串
	com.didispace.blog.value=${random.value}
	# 随机int
	com.didispace.blog.number=${random.int}
	# 随机long
	com.didispace.blog.bignumber=${random.long}
	# 10以内的随机数
	com.didispace.blog.test1=${random.int(10)}
	# 10~20的随机数
	com.didispace.blog.test2=${radom.int(10,20)}
	该配置方式可以设置应用端口等场景,以避免在本地调试时出现端口冲突的麻烦

命令行参数

1. 再用命令行方式启动Spring Boot应用时,连续的两个减号--就是对application.properties中的属性值进行赋值的标识。所以,java -jar XXX.jar--server.port=8888命令,等价于application.properties中添加属性server.port=8888。
2. 通过命令行来修改属性值是Spring Boot非常重要的一个特性。通过此特性,理论上已经使得应用的属性在启动前是可变的,所以其中的端口号也好、数据库连接也好,都是可以在应用启动时发生改变的,而不同于以往的Spring应用通过Maven的Profile在编辑器中进行不同环境的构建。

多环境配置

1. 对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后在进行区分打包,***Spring Boot也不例外,或者说实现起来更加简单。***
2. 在Spring Boot中,***多环境配置的文件名需要满足application-profile.properties的格式,其中{profile}对应你的环境标识。***
	1. application-dev.properties:开发环境。
	2. application-test.properties:测试环境。
	3. application-prod.properties:生产环境。
3. 至于具体那个配置文件会被加载,需要在application.properties文件中通过***spring.profiles.active***属性来设置,其值对应配置文件中的{profile}值。如果spring.profiles.active=test就会加载application-test.properties配置文件内容。
4. ***多环境配置思路***
	1. 在application.properties中配置***通用内容***,并设置spring.profiles.active=dev,以开发环境为默认配置。
	2. 在application-{profile}.properties中配置各个环境不同的内容。
	3. 通过命令方式去激活不同环境的配置。

加载顺序

1. 为了能够更合理地重写各属性的值,Spring Boot使用了下面这种较为特别的属性加载顺序:
	1. 在命令行中传入参数
	2. SPRING_APPLICATION_JSON中的属性值。SPRING_APPLICATION_JSON是以JSON格式配置在系统环境变量中的内容。
	3. java:comp/env中的JNDI属性。
	4. java的系统属性,可以通过System.getProperties()获得内容。
	5. 操作系统的环境变量
	6. 通过random.*配置的随机属性
	7. 位于当前应用jar包之外,针对不同(profile)环境的配置文件内容,例如application-{profile}.properties或是YAML定义的配置文件。
	8. 位于当前应用jar包之内,针对不同{profile}环境的配置文件内容,例如application-{profile}.properties或是YAML定义的配置文件。
	9. 位于当前应用jar包之外的application.properties和YAML配置内容。
	10. 位于当前Jar包之内的application.properties和YAML配置内容。
	11. 在@Configuration注释修改的类中,通过@PropertySource注释定义的属性。
	12. 应用默认属性,使用SpringApplication.setDefaultProperties定义的内容
	13. 优先级按上面的顺序由高到低,数字越小优先级越高。

监控与管理

1. 当我们决定用Spring Boot来作为微服务框架时,除了它强大的快速开发功能之外,还因为它在Starter POMs中提供了一个特殊依赖模块***spring-boot-starter-actuator***。引入该模块能够自动为Spring Boot构建的应用***提供一系列用于监控的端点***。同时,Spring Cloud在实现各个微服务组件的时候,进一步为该模块做了不少扩展,比如,为原生端点增加了更多的指标和度量信息(比如在整合Eureka的时候会为/health端点增加相关的信息),并且根据不同的组件还提供了更多有空的端点(比如,为API网关组件Zuul提供了/routes端点来返回路由信息).

初识actuator

1. 在现有的Spring Boot应用中引入该模块非常简单,只需要在pom.xml的dependency节点中,新增spring-boot-starter-actuator的依赖即可。

原生端点

1. 根据端点的作用,可以将原生端点分为以下三个大类
	1. **应用配置类**:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与Spring Boot应用密切相关的配置类信息。
	2. **度量指标类**:获取应用程序运行过程中用于监控的度量指标,比如内存信息、线程池信息、HTTP请求统计等。
	3. **操作控制类**:提供了对应用的关闭等操作类功能。
2. 下面介绍这三类端点都分别可以为我们提供怎样的有用信息和强大功能,以及我们如何去扩展和配置它们。
应用配置类
1. 由于Spring Boot为了改善传统Spring应用繁杂的配置内容,采用了包扫描和自动化配置的机制来加载原本集中于XML文件中的各项内容。虽然这样的作法让我们的代码变得非常简洁,但是整个应用的实例创建和依赖关系等信息都被离散到各个配置类的注解上,我是我们分析整个应用中资源和实例的各种关系变得非常困难。**而这类端点可以帮助我们轻松获取一系列关于Spring应用配置内容的详细报告,比如自动化配置的报告、Bean创建的报告、环境属性的报告等**。
	1. /autoconfig:该端点用来获取应用的自动化配置报告,其中包括所有自动化配置的候选项。同时还列出了每个候选项是否满足自动化配置的各个先决条件。所有该端点可以帮助我们方便得找到一些自动化配置为什么没有生效的具体原因。**该报告内容将自动化配置内容分为以下两个部分**。
		1. positiveMatches中返回的是条件匹配成功的自动化配置。
		2. negativeMatches中返回的是条件匹配不成功的自动化配置。
	2. /beans:该端点用来获取应用上下文中创建的所有Bean。
		1. 在每个Bean中都包含了下面这些信息。
			1. bean:Bean的名称。
			2. scope:Bean的作用域。
			3. type:Bean的java类型
			4. resource:class文件的具体路径
			5. dependencies:依赖的Bean名称
	3. /configprops:该端点用来获取应用中配置的属性信息报告。该短信的配置信息,prefix属性代表了属性的配置前缀,properties代表了各个属性的名称和值。所有我们可以通过该报告来看到各个属性的配置路径,比如我们要关闭该端点,就可以通过使用endpoints.configprops.enabled=false来完成配置。
	4. /env:该端点与/configprops不同,它用来获取应用所有可用的环境属性报告。包括环境变量、JVM属性、应用的配置属性、命令行中的参数。
	5. /mappings:该端点用来返回所有Spring MVC的控制器映射关系报告。bean属性标识了该映射关系的请求处理器,method属性标识了该映射关系的具体处理类和处理函数。
	6. /info:该端点用来返回一些应用自定义的信息。默认情况下,该端点只会返回一个空的JSON内容。我们可以在application.properties配置文件中通过info前缀来设置一些属性。
度量指标类
1. 上面我们所介绍的应用配置类端点所提供的信息报告在应用启动的时候就已经基本确定了其返回内容,可以说是一个静态报告。而度量指标类端点提供的报告内容则是动态变化的,这些端点提供了应用程序在运行过程中的一些快照信息,比如内存使用情况,HTTP请求统计、外部资源指标等。下面列出这些强大的端点功能:
	1. /metrics:该端点用来返回当前应用的各类重要度量指标,比如内存信息、线程信息、垃圾回收信息等。有如下这些重要的度量值:
		1. 系统信息:包括处理器数量processors、运行时间uptime和instance.uptime、系统平均负载systemload.average。
		2. mem.*:内存概要信息,包括分配给应用的总内存数量以及当前空闲的内存数量。这些信息来自**java.lang.Runtime**
		3. heap.*:堆内存使用情况。这些信息来自java.lang.managment.MemoryMXBean接口中getHeapMemoryUsage方法获取的java.lang.management.MemoryUsage。
		4. nonheap.*:非堆内存使用情况。java.lang.management.MemoryMXBean接口中getNonHeapMemoryUsage方法获取的java.lang.management.MemoryUsage。
		5. thread.*:线程使用情况,包括线程数、守护线程数(daemon)、线程峰值(peak)等,这些数据均来自java.lang.management.ThreadMXBean。
		6. classes.*:应用加载和卸载的类统计。这些数据均来自java.lang.management.ClassLoadingMXBean。
		7. gc.*:垃圾收集器的详细信息,包括垃圾回收次数gc.ps_scavenge.count、垃圾回收消耗时间gc.ps_scavenge.time、标记-清除算法的次数gc.ps_marksweep.count、标记-清除算法的消耗时间gc.ps_marksweep.time。这些数据均来自java.lang.management.GarbageCollectMXBean。
		8. httpsessions.*:Tomcat容器的会话使用情况。包括最大会话数httpsessions.max和活跃会话数httpsessions.active。该度量指标信息仅在引入嵌入式Tomcat作为应用容器的时候才会提供。
		9. gauge.*:HTTP请求的性能指标之一,它主要用来反映一个绝对数值。比如gauge.response.hello:5,它表示上一次hello请求的延迟时间为5ms。
		10. counter.*:HTTP请求的性能指标之一,它主要作为计数器使用,记录了增加量和减少量。例如counter.status.200.hello:11,它代表了hello请求返回200状态的次数为11。
			1. 对于gauge.*和counter.*的统计,这里有一个特殊的内容请求star-star,它代表了对静态资源的访问。这两类度量指标非常有用,我们不仅可以使用它默认的统计指标,还可以在程序中轻松地增加自定义统计值。只需要通过注入org.springframework.boot.actuate.metrics.CounterService和org.springframework.boot.actuate.metrics.GaugeService来实现自定义的统计指标信息。
		11. **/metrics端点可以提供应用运行状态的完整度量指标报告,这项功能非常使用,但是对于监控系统中的各项监控功能,它们的监控内容、数据收集频率都有所不同,如果每次都通过全量获取报告的方式来收集,略显粗暴。所以,我们还可以通过/metrics/{name}接口来更细粒度的获取度量信息,比如可以通过访问/metrics/mem.free来获取当前空用内存数量。**
	2. /health:该端点用来获取应用的各类健康指标信息。这些检测器都通过HealthIndicator接口实现,并且会根据依赖关系的引入实现自动化装配,下面列出一些常用接口:
		1. DiskSpaceHealthIndicator:低磁盘空间检测
		2. DataSourceHealthIndicator:检测DataSource的连接是否可用
		3. MongoHealthIndicator:检测Mongo数据库是否可用
		4. RabbitHealthIndicator:检测Rabbit服务器是否可用。
		5. RedisHealthIndicator:检测Redis服务器是否可用
		6. SolrHealthIndicator:检测Solr服务器是否可用
		7. 有时候,我们可能还会用到一些Spring Boot的Starter POMs中还没有封装的产品来进行开发,由于没有自动化配置的检测器,所以需要自己来实现一个用来采集健康信息的检测器。我们可以在Spring Boot的应用中,为org.springframework.boot.actuate.health.HealthIndicator接口实现一个检测器类通过重写health()函数可实现健康检查,在返回的Heath对象中,共有两项内容,一个是状态信息,除了UP与DOWN之外,还有UNKNOWN和OUT_OF_SERVICE,可以根据需要来实现返回;还有一个详细信息,采用Map的方式存储,可通过withDetail函数注入,这里可以注入对象的IP地址、端口等。
	3. /dump:该端点用来暴露程序运行中的线程信息。它使用java.lang.management.ThreadMXBean的dumpAllThreads方法来返回所有含有同步信息的活动线程详情。
	4. /trace:该端点用来返回基本的HTTP跟踪信息。默认情况下,跟踪信息的存储采用org.springframework.boot.actuate.trace.InMemoryTraceRepository实现的内存方式,始终保留最近的100条请求记录。
操作控制类
1. 由于之前介绍的所有端点都是用来反映应用自身的属性或是运行中的状态,相对于操作控制类端点没有那么敏感,所有它们都是默认启用的。**而操作控制类端点拥有更强大的控制力,如果要使用它们的话,需要通过属性来配置开启操作**。
2. 在原生端点中,只提供了用来关闭应用的端点:/shutdown(在后续我们引入例如EureKa之后,会引入更多的控制端点)。可以通过如下配置开启它:
	1. endpoints.shutdown.enabled=true
3. 在配置了上述属性之后,只需要访问该应用的/shutdown端点就能实现关闭该应用的远程操作。**由于开放关闭应用的操作本身是一件非常危险的事,所以真正在线上使用的时候,需要对其加入一定的保护机制,比如定制actuator的端点路径、整合Spring Security进行安全校验等。**

第3章 服务治理:Spring Cloud Eureka

1. Spring Cloud Eureka是Spring Cloud Netfix微服务套件中的一部分,它基于Netfix Eureka做了二次封装,**主要负责完成微服务架构中的服务治理功能。**Spring Cloud通过为Eureka增加了Spring Boot风格的自动化配置,我们只需要通过简单引入依赖和注解配置就能让Spring Boot构建的微服务应用轻松的与Eureka服务治理体系进行整合。

服务治理

1. 服务治理可以说是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册和发现。**为什么我们在微服务架构中那么需要服务治理模块呢?微服务系统没有它会有什么不好的地方吗?**
2. 在最初开始构建微服务系统的时候可能服务并不多,我们可以通过做一些静态配置来完成服务的调用。比如,有两个服务A和B,其中服务A需要调用服务B来完成一个业务操作时,为了实现服务B的高可用,不论采用服务端负载均衡还是客户端负载均衡,都需要手工维护服务B的具体实例清单。但是随着业务的发展,系统功能越来越复杂,相应的微服务应用也不断增加,我们的静态配置就会变得越来越难以维护。并且面对不断发展的业务,我们的集群规模、服务的位置、服务的命名等都有可能发生变化,如果还是通过手工维护的方式,那么极易发生错误或是命名冲突等问题。同时,对于这类静态内容的维护也必将消耗大量的人力。
3. 为了解决微服务架构中的服务实例维护问题,产生了大量的服务治理框架和产品。这些框架和产品的实现都围绕着服务注册和服务发现机制来完成对微服务应用实例的自动化管理。
	1. 服务注册:在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。另外,**服务注册中心还需要以心跳的方式去检测清单中的服务是否可用,若不可用需要从服务清单中剔除,达到排除故障服务的效果。**
	2. 服务发现:由于在服务治理框架下运作,服务间的调用不再通过指定具体的实例地址来实现,而是通过向服务名发起请求调用实现。

Netfix Eureka

1. Eureka服务端,我们也称为服务注册中心。他同其他服务注册中心一样,支持高可用配置。他依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。如果Eureka以集群模式部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其他分片会把它们的状态再次同步回来。以在AWS的实践为例,Netflix推荐每个可用的区域运行一个Eureka服务端,通过它来形成集群。不同可用区域的服务注册中心通过异步模式互相复制各自的状态,这意味着在任意给定的时间点每个实例关于所有服务的状态是有细微差别的。
2. Eureka客户端,主要处理服务的注册和发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务并周期性的发送心跳来更新它的服务租约。同时,它也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期性的刷新服务状态。

搭建服务注册中心

注册服务提供者

高可用注册中心

服务发现与消费

Eureka详解

基础架构

服务治理机制

源码分析

配置详解

服务注册类配置

服务实例类配置

跨平台支持

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐