SpringCloud 微服务(服务提供者和服务消费者)

本系列基于《SpringCloud 与 Docker 微服务架构实战》(作者:周立)一书

传统的项目都是 单体 结构的,简单的说就是一个 war 包,部署在 servlet 容器中。这是很多项目初期的架构选择,这种方案的优点是开发和运维都比较容易,简单的业务场景下维护的成本较低。随着业务的发展,这种架构暴露出严重的问题

  • 业务的发展带来代码量的增加,随之而来的是复杂性的急剧上升,这样维护的成本大大提高,添加一个功能或者修复一个 bug 都胆战心惊
  • 可靠性差:某个应用的 bug 有可能导致整个服务变得不可用
  • 阻碍技术发展:单体结构中一般采用统一的技术平台或者技术方案解决所有的问题,表现为相同的开发语言和框架,这时想引入新的技术框架变得异常困难

微服务 的出现就是来解决这些问题的

微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间采用轻量级通信机制(通常为 RESTful 形式的 API)。这些服务围绕业务能力构建并可以通过全自动部署机制进行部署,这些服务共用一个最小型的集中式管理,服务可用不同的语言开发,使用不同的数据存储技术。

相比单体架构,微服务有以下优势

  • 易于开发和维护:一个微服务只会关注某个特定的业务功能,结构清晰,代码量较少
  • 局部修改容易部署:单体应用中只要有修改就需要重新部署整个应用,而在微服务中只需要部署被修改的微服务即可
  • 技术栈不受限:每个微服务可以根据业务的需要有不同的技术选型,比如 Java、Node.js 等
  • 按需伸缩:根据每个微服务的业务特性实现细粒度的拓展,比如对于高 IO 的微服务可以使用高性能的磁盘比如 SSD,对于计算密集型的微服务可以增加 CPU 等提高计算能力

微服务看似很好的解决了这些问题,但实际上它也带来了一些技术挑战,比如运维要求高、接口成本的代价大以及重复劳动等问题,因为我们要正确的看待,根据自身业务的需要决定是否要使用它

框架和平台

相对于单体应用的交付,微服务的应用要复杂的多,这里技术选型方面主要从框架和平台考虑。Spring 技术栈较为全面,文档丰富、社区活跃,所以使用 SpringCloud 作为主要框架。微服务的运行并不绑定特定平台,最后将演示如何使用 docker 运行微服务

微服务开发框架 SpringCloud

什么是 SpringCloud ?

SpringCloud 是在 SpringBoot 基础上构建的,用于快速构建分布式系统的通用模式的工具集

SpringCloud 开箱即用,启动快速,组件丰富,功能齐全,并且选型丰富、中立,比如就服务发现这个功能,SpringCloud 并没有绑定技术栈,开发者可以自由选择使用 Eureka、Zookeeper 或者 Consul 来实现

下面就正式开始进入微服务实战,本节以电影系统为模型,搭建基础的电影模块和用户模块,其中电影模块是服务消费者,用户模块是服务提供者,电影模块需要从用户模块查询用户信息

服务消费者:服务的调用方(依赖其他服务的服务)

服务提供者:服务的被调用方(为其他服务提供服务的服务)

搭建

版本

  • SpringBoot 1.5.6.RELEASE
  • SpringCloud Dalston.SR2
  • JDK 1.8
  • Maven 3.5.0
父模块

首先新建一个以 pom 形式组织的父模块,导入依赖版本规定,pom文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<packaging>pom</packaging>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
用户模块

在此父工程下新建一个模块,artifactId 为 microservice-provider-user,它是一个 web 应用,提供了根据用户 id 查询用户信息的一个接口,pom 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<artifactId>microservice-provider-user</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>

处于简单的考虑,这里使用了 h2 内存数据库和 jpa 形式访问数据库,由于是一个 web 项目,因此我们添加了依赖:spring-boot-starter-web

在 user 模块中新建一个controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @author zlren
* @date 2017-11-07
*/
@RestController
@RequestMapping("user")
public class UserController {

@Autowired
private UserRepository userRepository;

/**
* 根据 userid 查询用户信息
*
* @param id
* @return user信息
*/
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
return this.userRepository.findOne(id);
}
}

其中 User 如下

1
2
3
4
5
6
7
8
9
10
11
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String username;
@Column
private Integer age;
}

UserRepository 如下

1
2
3
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

将 h2 数据库的初始化和数据 sql 文件置于 resources 目录下

schema.sql

1
2
drop table user if exists;
create table user (id bigint generated by default as identity, username varchar(255), age int, primary key (id));

data.sql

1
2
3
4
insert into user (id, username, age) values (1,'Tom',12);
insert into user (id, username, age) values (2,'Jerry', 23);
insert into user (id, username, age) values (3,'Reno', 44);
insert into user (id, username, age) values (4,'Josh', 55);

application.yml 配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 7900
spring:
application:
name: microservice-provider-user # 项目名称尽量用小写
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: none
datasource: # 指定数据源
platform: h2 # 指定数据源类型
schema: classpath:schema.sql # 指定h2数据库的建表脚本
data: classpath:data.sql # 指定h2数据库的初始数据
logging:
level:
root: INFO
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE

启动 user 模块,访问 localhost:7900/user/1,显示如下结果,表示从 h2 数据库中查到了对应的数据

电影模块

同理新建一个子模块为 microservice-consumer-movie,它同样是一个 web 应用,pom 如下(只用添加 web 的起始依赖即可)

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

在 movie 中新建一个 controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("movie")
@Slf4j
public class MovieController {

@Autowired
private RestTemplate restTemplate;

@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return this.restTemplate.getForObject("http://localhost:7900/user/" + id, User.class);
}
}

其中 RestTemplate 需要注入成 Spring 中的 Bean

1
2
3
4
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

application.yml

1
2
3
4
5
6
server:
port: 7901

spring:
application:
name: microservice-consumer-movie # 项目名称尽量用小写

启动 movie 模块,访问 localhost:7901/movie/user/1,得到如下结果,说明在 movie 中调用了 user 模块中提供的用户查询的接口,这便实现了微服务之间的调用

总结

我们实现了一个用户微服务和一个电影微服务,并在电影微服务中使用了 RestTemplate 调用用户微服务中的 RESTful 形式的 API,一切都很好。真的是这样吗?

目前存在的最大的问题是硬编码,来看一下 MovieController

1
return this.restTemplate.getForObject("http://localhost:7900/user/" + id, User.class);

在调用用户微服务的时候,将其 IP 地址和端口硬编码在代码中,这显示是不够灵活的。当然这时候我们会想到可以将这两个参数配在配置文件中,并使用 SpringBoot 中的 @Value 注解,但是这样依然是不够优雅

  • 如果服务提供者的网络信息发生了改变,我们依然需要手动更新这些信息
  • 生产环境中为了高可用,同一个微服务要部署很多个实例,这样的配置显然不能实现动态的伸缩和发现

下一节将介绍微服务框架中的 服务发现 组件,看看它的出现为我们带来了怎样的便利