第二章 使用SpringBoot构建微服务
一个完整的交付涉及到多个角色,成功的微服务开发的基础将从以下三个角色的视角开始。
架构师—分解业务、建立服务粒度、定义服务接口
软件开发人员—开发代码,必要的进行单元测试
运维人员—提供服务部署和服务管理
下面将以上述三个角色为出发点,讲述不同的故事。
架构师
架构师首先要做的就是划分服务的粒度,微服务划分过于粗粒度将会出现一下现象:
- 服务承担了过多的责任,也就是违反了单一职责,不可能要求转账的服务去处理用户登录相关的
- 跨大量表来管理数据,作者给出的参考是微服务应该不超过3~5张表
- 测试用例太多
那么微服务如果划分的过于细粒度,将会出现以下现象:
- 微服务过多,甚至于一个微服务只与一张表进行交互,这将造成不必要的服务治理
- 微服务之间严重依赖,一个请求可能会调用5个以上的微服务才能完成,这将造成服务之间调用耗时的积累
- 微服务不再是服务,而是成为了简单的CRUD的集合
一个经验的做法是:从粗粒度的服务划分逐渐推演到细粒度的服务划分。
架构师需要关心的另一点是接口的设计,可能在其他公司,例如我所在的公司,接口设计,也有开发人员参与,接口设计的要求是易于理解和使用。具体有以下几个要求:
- REST风格—将HTTP作为服务的调用协议,并使用标准的动词(GET、POST、PUT)
- 使用URI来传递意图—用作服务端点的URI应该能描述一个资源的定位
- 请求响应使用JSON—这已经成为默认的理念,当然在一些银行业、保险业中,还在大量使用XML。
- 使用HTTP状态码来传达结果
在讲述开发人员关于微服务的故事前,先了解下何时不适合使用微服务
- 微服务需要高度的运维复杂度和成熟度,要考虑领导愿不愿意投入时间和金钱来搞这一套啦
- 小应用,小用户群不建议使用,杀鸡焉用宰牛刀
- 分布式事务问题,一个请求可能会跨多个数据源,怎样保证这些数据源数据的一致性,也成为了微服务的副作用,如果没有成熟的处理方案,建议慎用。
开发人员
本节使用的代码还是作者GitHub上的代码,主要实现以下工作:
- 在基本框架之上构建应用程序的Maven脚本
- 实现一个引导类
- 实现一个控制器类,公开一个端点供客户端调用
先介绍下整个项目的包结构
下面看下Maven脚本,重要的做了下注释:
<?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.thoughtmechanix</groupId>
<artifactId>licensing-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Eagle Eye Licensing Service</name>
<description>Licensing Service</description>
<parent>
<groupId>org.springframework.boot</groupId>
<!--告诉Maven包含Springboot起步工具包依赖项,SpringBoot将相关的各种依赖都封装到了起步工具包中-->
<artifactId>spring-boot-starter-parent</artifactId>
<!--版本号-->
<version>1.4.4.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<!--以下的Web起步工具包和Actuator工具包是服务 核心-->
<groupId>org.springframework.boot</groupId>
<!--告诉框架你将要构建一个WEB项目-->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--查看项目中自己制定的bean有没有被注册上,环境属性,程序健康指标等等,-->
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
关于引导类,这里在第一章已经描述过,就不再多说,直接进入控制器的说明。
通过一段代码进行描述:
//这个注解告诉SpringBoot它将自动序列化反序列化请求/响应到JSON
@RestController
//类级注解,在这个类中使用v1/organizations/{organizationId}/licenses的前缀,公开所有HTTP端点
//{organizationId} 可以参数化organizationId,拼装特定的organizationId
@RequestMapping(value="v1/organizations/{organizationId}/licenses")
public class LicenseServiceController {
@Autowired
private LicenseService licenseService;
//GET 方式,最后拼装的URL将是v1/organizations/{organizationId}/licenses/{licenseId}
@RequestMapping(value="/{licenseId}",method = RequestMethod.GET)
public License getLicenses( @PathVariable("organizationId") String organizationId,
@PathVariable("licenseId") String licenseId) {
//return licenseService.getLicense(licenseId);
return new License()
.withId(licenseId)
.withOrganizationId(organizationId)
.withProductName("Teleco")
.withLicenseType("Seat");
}
//PUT 方式
@RequestMapping(value="{licenseId}",method = RequestMethod.PUT)
public String updateLicenses( @PathVariable("licenseId") String licenseId) {
return String.format("This is the put");
}
//POST方式
@RequestMapping(value="{licenseId}",method = RequestMethod.POST)
public String saveLicenses( @PathVariable("licenseId") String licenseId) {
return String.format("This is the post");
}
//DELETE 方式
@RequestMapping(value="{licenseId}",method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public String deleteLicenses( @PathVariable("licenseId") String licenseId) {
return String.format("This is the Delete");
}
}
我们启动引导类,并用PostMan进行模拟调用,结果如下
运维人员
微服务确实是一个好的架构模式,使服务更加小而专一,但使用微服务也会引入更多的其他部件或者说一些中间件,来实现服务协调,也会带来运维上的不便,因为他们可能更加故障频发,更加要求运维的成熟度和技术。
对于运维人员来说,管理微服务需要考虑以下四点:
- 如何打包和部署服务以保证可重复并且一致,即不同环境需要部署相同的包和运行相同的代码
- 将代码与配置进行分离,要求不同环境可以快速部署启动,不会受到配置的影响
- 服务的注册发现
- 故障处理(***)
服务的打包部署
一般会要求打包出的是一个可执行jar,可以将其部署到安装了JDK的任何服务器上,它应该包含嵌入在其中的应用程序和运行的容器,简单示意下从命令行打包jar文件。
在C:\soft\idea\program\carnellj\chapter2\spmia-chapter2\licensing-service目录下启动cmd 命令 ,输入mvn package 即可。
打出来的包如下:
运行的话,直接 在命令行输入 java -jar xxxx.jar 即可
配置管理
对于配置管理的要求应该是: - 当服务启动时,配置信息应该作为环境变量传入服务或者从集中式配置管理中心进行拉取
- 如果服务的配置发生变化,应该通知服务重新拉取(推给服务)或者运行旧的配置的实例应该被删除
服务注册发现 - 服务启动时,将服务实例注册到注册中心
- 客户端永远都不会知道服务的IP,它应该只知道它要通信的对象应该是代理服务器
故障处理
重要的一点是,代理服务器不仅仅是一个引路人,引导客户端的请求到真实的服务实例,它还负责服务的健康检查,因为在微服务中,一个服务可能会运行多个实例,某些实例可能会出现故障,代理服务器还负责每个注册在其上服务实例的健康检查,并将有问题的从其路由表中移除。
还记得之前的Spring Actuator吗,其提供了开箱即用的运维端点,可帮助用户了解和管理服务的运行状况。示意如下: