微服务学习之路
微服务学习之路,本系列文章是博主学习微服务的路程,特意分享自己的学习笔记,也为了以后能回溯查看。
核心微服务开发模式
前言
本章记录核心微服务开发模式的重点内容。
一、为什么使用微服务
在传统的大型开发项目中,大多数都是使用瀑布开发模式,坚持在项目开始时界定应用所有的需求和设计。这种方式很少能满足新的业务需求,很少能够重构。
瀑布开发模式粒度的特点:
特点 | 描述 |
---|---|
紧耦合 | 业务逻辑的调用发生在编程语言层面,而不是通过中立的协议(如SOAP、REST)。这增加了即使对应用程序做很小的改动也可能导致应用程序出现其他漏洞的机会 |
有漏洞 | 由于共用一个数据存储结构,不同维度的业务之间的数据可以互相访问,比如一个CRM应用程序中,可能管理客户信息、产品信息、销售信息,销售信息的业务可以使用客户信息、产品信息数据。这种对数据轻松访问引入了隐藏的依赖关系,有可能导致对某张表做很小的改动,也需要对整个应用程序进行大量的代码更改和回归测试。 |
单体 | 由于传统程序的大多数组件都存放在共享的单个代码库中,任何时候更改,都需要对整个应用程序进行编译、部署。这使得即使对应用程序代码做很小的改动也需要耗费大量的时间。 |
微服务架构的特点:
特点 | 描述 |
---|---|
有约束 | 微服务具有范围有限的单一职责集。微服务遵循UNIX的理念,即应用程序是服务的集合,每个服务管理一件事,做好一件事。 |
松耦合 | 基于微服务的应用程序是小型服务的集合,服务之间使用非专属调用协议通过非特定实现的接口彼此交互。 |
抽象的 | 微服务完全拥有自己的数据结构和数据源。 |
独立的 | 微服务应用程序中的每个服务可以独立于应用程序中使用的其他服务进行编译和部署。 |
因此,通过比较以上两个表格,微服务的优势就凸显出来了。
二、如何设计微服务架构
该问题是架构师的主要工作,主要关注以下3个关键点:
- 分解业务问题
- 建立服务粒度
- 定义服务接口
2.1. 分解业务问题
将业务问题分解成代表离散活动领域的块。这些块封装了与业务域特定部分相关联的业务规则和数据逻辑。通过查看数据域中那些不适合放到一起的地方来划分组微服务的服务边界。业务事务的两个不同部分的交互通常成为微服务的服务接口。
可分离业务问题的点:
关注点 | 描述 |
---|---|
描述业务问题,并聆听用来描述问题的名词 | 在描述问题时,反复使用的同一名词通常意味着它们是核心业务领域并且适合创建微服务。 |
注意动词 | 如果发现自己说出了“事务X需要从事物A和事物B获取数据”这种话,通常表明多个服务正在起作用。 |
寻找数据内聚 | 将业务问题分解成离散的部分时,要寻找彼此高度相关的数据。如果在会话过程中,突然读取或更新与迄今为止所讨论的内容完全不同的数据时,那么就可能还存在其他候选服务。微服务应该拥有自己的数据。 |
2.2. 建立服务粒度
在构建微服务架构时,粒度的问题很重要,可以采用以下思想来确定正确的解决方案。
思想 | 描述 |
---|---|
开始的时候可以让微服务涉及的范围更广泛一些,然后将其重构到更小的服务。 | 将问题域分解为小型的服务通常会导致过早的复杂性,因为微服务变成了细粒度的数据服务。 |
重点关注服务如何交互 | 这有助于建立问题域的粗粒度接口。从粗粒度重构到细粒度是比较容易的。 |
随着对问题域的理解不断增长,服务的职责将随着时间的推移而改变 | 通常来说,需要新的应用功能时,微服务就会承担起职责。最初的微服务可能会发展为多个服务,原始的微服务则充当这些新服务的编排层,负责将应用的其他部分的功能封装起来。 |
糟糕的粗粒度设计体现:
问题 | 描述 |
---|---|
服务承担过多的职责 | 服务中的业务逻辑的一般流程很复杂,并且视乎正在执行一组过于多样化的业务规则。 |
该服务正在跨大量表来管理数据 | 如果发现持久化数据接触到多个表或当前数据库以外的表,那么这就是一条服务过与粗粒度的线索。微服务通常在3~5个表,再多一点就可能承担过多职责。 |
测试用例太多 | 如果一开始只有少量测试用例的服务,到了最后该服务需要数百个单元测试,那么久可能需要重构。 |
糟糕的细粒度设计体现:
问题 | 描述 |
---|---|
问题域的一部分微服务像兔子一样繁殖 | 如果一切都成为微服务,将服务的业务逻辑组合起来会变得复杂和困难,因为完成一项工作所需要的服务数量会快速增长。 |
微服务彼此之间严重相互依赖 | 在问题域的某一个部分中,微服务相互来回调用以完成单个用户请求。 |
微服务成功简单的CRUD | 微服务是业务逻辑的表达,而不是数据源的抽象层。如果微服务除了CRUD相关逻辑之外什么都不做,那么它们可能被划分得太细粒度了。 |
应该通过演化思维的过程来开发一个微服务架构,在这个过程中,你知道不会第一次就要得到正确的设计。这就是最好从一组粗粒度的服务而不是一组细粒度的服务开始的原因。同样重要的是,不要对设计带有教条主义。我们可能会面临两个单独的服务之间交互过于频繁,或者服务域之间不存在明确的边界这样的物理约束,当面临这样的约束时,需要创建一个聚合服务来将数据连接在一起。
2.3. 定义服务接口
架构师需要关心的最后一部分,是应用程序中微服务该如何彼此交流。一般来说,可以使用以下指导方针思考服务接口设计。
方针 | 描述 |
---|---|
拥抱REST的理念 | REST对服务的处理方式是将HTTP作为服务的调用协议并使用标准HTTP动词(GET\PUT\POST\DELETE)。围绕这些HTTP动词进行建模 |
使用URI来传达意图 | 用作服务端点的URI应描述问题域中的不同资源,并为问题域内的资源的关系提供一种基本机制。 |
请求和响应使用JSON | JavaScript对象表示法(JavaScript Object Notation, JSON) |
使用HTTP状态码来传达结果 | HTTP协议具有丰富的标准响应代码,以指示服务的成果或失败。学习这些状态码,并且最重要的是在所有服务中始终如一地使用它们。 |
三、何时不使用微服务
前面说了使用微服务的好处,那么何时使用微服务何时不需要使用微服务,其考虑因素如下:
因素 | 描述 |
---|---|
构建分布式系统的复杂性 | 微服务架构需要高度运维成熟度。除非愿意投入自动化和运维工作,否则不要考虑使用微服务。 |
虚拟服务器/容器散乱 | 微服务最常用的部署方式是一台服务器部署一个微服务实例。这导致最终可能需要50~100个服务器或容器,成本高,管理和监控的操作复杂性也是巨大的。 |
应用程序的类型 | 微服务面向可复用性,并且对构建需要高度弹性和可伸缩性的大型应用程序非常有用。如果只具有较小用户群的应用程序,那么搭建一个分布式模型的复杂性可能太昂贵。 |
数据事务和一致性 | 如果应用程序需要跨多个数据源进行复杂的数据聚合或转换,那么微服务的分布式性质会让这项工作变得困难。这样的微服务总是承担过多职责,也可能变得容易受到性能影响。 |
四、用Spring Boot和Java构建微服务
开发人员需要建立一个实现应用程序中每个微服务的基本模式。虽然每项服务都是独一无二的,但我们希望确保使用的是一个移除样板代码的框架,并且微服务的每个部分都采用相同的布局。
构建样板框架的几个步骤如下:
- 构建微服务的基本框架并构建应用程序的Maven脚本。
- 实现一个Spring引导类,它将启动用于微服务的Spring容器,并启动类的所有初始化工作。
- 实现映射端点的Spring Boot控制器类,以公开服务的端点。
maven脚本pom示例代码:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.throughtmechanix</groupId>
<artifactId>licensing-com.service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>confsvr</module>
</modules>
<packaging>pom</packaging>
<name>EagleEye Licensing Service</name>
<description>Licensing Service</description>
<parent>
<artifactId>spring-boot-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
引导类示例代码:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
控制器类示例代码:
@RestController
@RequestMapping(value = "/v1/organizations/{orgnizationId}/licenses")
public class LicenseServiceController {
@GetMapping(value = "/{licenseId}")
public License getLicenses(@PathVariable("orgnizationId") String orgnizationId,
@PathVariable("licenseId") String licenseId){
return new License()
.withId(licenseId)
.withProductName("Jrol")
.withLicenseType("Seat")
.withOrganizationId(orgnizationId);
}
}
最好微服务都遵循REST方法来构建,使其具有如下特点:
- 使用HTTP作为服务的调用协议
- 将服务的行为映射到标准HTTP动词
- 使用JSON作为进出服务的所有数据的序列化格式
- 使用HTTP状态码传达服务调用的状态
端点的命名有以下指导方针:
- 使用明确的URL名称来确立服务所代表的资源
- 使用URL来确立资源之间的关系
- 尽早建立URL的版本控制方案
五、构建与部署
基于4条原则开始微服务的开发和构建:
- 微服务应该是独立的和可独立部署的。多个服务实例可以使用单个软件制品进行启动和拆卸。
- 微服务应该是可配置的。当服务实例启动时,它应该从中央位置读取需要配置其自身的数据,或者让它的配置信息作为环境变量传递。配置服务无需认为干预。
- 微服务实例需要对客户端是透明的。客户端不应该知道服务的确切位置。相反,微服务客户端应该与服务发现代理通信,该代理将允许应用程序定位微服务的实例,而不必知道微服务的物理位置。
- 微服务应该传达它的健康信息,这是云架构的关键部分。一旦微服务实例无法正常运行,客户端需要绕过不良的服务实例。
以上4条原则在转换到运维生命周期步骤如下:
- 服务装配。如何打包和部署服务以保证可重复性和一致性,以便相同的服务代码和运行时被完全相同的部署?
- 服务引导。如何将应用程序和环境特定的配置代码与运行时代码分开,以便可以在任何环境中快速启动和部署微服务实例,而无需对配置微服务进行人为干预?
- 服务注册/发现。部署一个新的微服务实例时,如何让新的服务实例可以被其他应用程序客户端发现。
- 服务监控。在微服务环境中,由于高可用性需求,同一服务运行多个实例非常常见。从DevOps的角度看,需要监控微服务实例,并确保绕过微服务中的任何故障,而且状况不佳的服务实例会被拆卸。
5.1. 服务装配
从DevOps的角度来看,微服务架构背后的一个关键概念是可以快速部署微服务的多个实例,以应对变化的应用程序环境。
为了实现这一点,微服务需要作为带有所有依赖项的单个制品进行打包和安装,然后可以将这个制品部署安装到Java JDK的任何服务器上。这些依赖项还包括承载微服务的运行时引擎。
这种持续构建、打包和部署的过程就是服务装配。
几乎所有的Java微服务框架都包含可以使用代码进行打包和部署的运行时引擎。在实例代码中,可以使用Maven和Spring Boot构建一个可执行的JAR包,该文件具有嵌入式的Tomcat引擎内置于其中。以下命令是构建可执行JAR包,然后运行JAR文件。
mvn clean package && java -jar licensing.jar
5.2. 服务引导
服务引导发生在微服务首次启动并需要加载其应用程序配置信息的时候。
有时需要使应用程序的运行时行为可配置。通常从应用程序部署的属性文件读取应用程序的配置数据,或从数据存储区读取数据。
微服务通常会遇到相同类型的配置需求。不同之处在于,在云上运行的微服务应用程序中,可能会运行数百个微服务实例,更可能分散在全球各地,导致重新部署服务以获取新的配置数据变得难以实施。
将数据存储在服务器外部的数据存储中解决该问题,但云上的微服务提出一些挑战:
- 配置数据的结构往往很简单,通常读取频繁但不经常写入。这种情况下用关系型数据库就相当于大材小用。
- 因为数据是定期访问的,但是很少更改,所以数据必须具有低延迟的可读性。
- 数据存储必须具有高可用性,并且靠近读取数据的服务。配置数据存储不能完全关闭,否则它将成为应用程序的单点故障。
5.3. 服务注册/发现
基于云的环境中,服务器是短暂的。短暂意味着承载服务的服务器通常比在企业数据中心运行的服务的寿命更短。可以通过分配给运行服务的服务器的全新IP地址来快速启动和拆除基于云的服务。
通过坚持将服务视为短暂的可自由处理的对象,微服务架构可以通过运行多个服务实例来实现高度的可伸缩性和可用性。短暂服务的缺点是,随着服务的不断出现和消亡,手动或手工管理大量的短暂服务容易造成运行中断。
微服务实例需要向第三方代理注册。此注册过程称为服务发现。当微服务实例使用服务发现代理进行注册时,微服务实例将告诉服务发现代理两件事,服务实例的IP地址或域名地址,以及应用程序可以用来查找服务的逻辑名称。某种服务发现代理还要求能访问到注册服务的URI,服务发现代理可以使用此URL来执行健康检查。
5.4. 服务监控
在基于云的微服务应用程序中,某些服务实例迟早会出现一些问题。服务发现代理监控其注册的每个服务实例的健康状况,并从其路由表中移除有问题的服务实例,以确保客户端不会访问已经发生故障的服务实例。
在发现微服务后,服务发现代理将继续监视和ping健康检查接口,以确保该服务可用。通过构建一致的健康检查接口,我们可以使用基于云的监控工具来检测问题并对其进行适当的响应。如果服务发现代理发现服务实例存在问题,则可以采取纠正措施,如关闭出现故障的实例或启动另外的服务实例。
在Spring Boot中,可以通过引入Spring Actuator模块。它是一个开箱即用的运维端点,帮助用户了解和管理服务的健康状况。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
六、综合视角
云中微服务想要成功,需要有一个综合视角。从不同的角度来看,有不同的分工,大致有以下3种视角:
视角 | 关键结论 |
---|---|
架构师 | 专注于业务问题的自然轮廓。描述业务问题域,筛选目标备选微服务。最好从粗粒度的微服务开始,并重构到较小的服务。 |
软件工程师 | 专注于构建分层服务。服务中的每一层都有离散的职责,避免在代码中构建框架的诱惑,并尝试使每个微服务完全独立。 |
DevOps工程师 | 关注如何自动化的构建和部署,以及健康服务的健康状态。实施服务通常需要比编写业务逻辑更多的工作,也更需要深谋远虑。 |
总结
- 微服务是一种强大的架构范型,它有优点和缺点。并非所有应用程序都应该是微服务应用程序。
- 从架构师角度,微服务是小型的、独立的和分布式的。微服务应具有狭窄的边界,并管理一小组数据。
- 从开发人员角度,微服务通常使用REST风格的设计构建,JSON作为服务发送和接收数据的净荷。
- 从DevOps的角度来看,微服务如何打包、部署和监控至关重要。