微服务
开始使用 Spring Cloud 之前,我们先来了解下什么是微服务。
什么是微服务?
简单来讲微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个服务代表着一个小的业务组件。
网上解释微服务的文章很多,具体可以自己去查询。
通常而言,一个微服务框架的应用程序有下列特性:
- 每个服务都容易被取代
- 服务是以功能来组织的,例如用户界面、前端、推荐系统、账单或是物流等
- 由于功能被拆成多个服务,因此可以由不同的编程语言、数据库实现
- 架构是对称而非分层(即生产者与消费者的关系)
微服务是一种架构方式,最终肯定需要技术架构去实现。
微服务的实现方式很多,但是最火的莫过于 Spring Cloud 了。
那么什么是 Spring Cloud 呢?
Spring Cloud
Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,熔断器,智能路由,微代理,控制总线,一次性令牌,全局锁定,领导选举,分布式会话,集群)。分布式系统的协调导致锅炉板模式,使用 Spring Cloud 开发人员可以快速实现这些模式的服务和应用程序。它们适用于任何分布式环境,包括开发人员自己的笔记本电脑,裸机数据中心和 Cloud Foundry 等托管平台。
其主要涉及的组件(部分)如下:
Eureka
:注册中心Ribbon
:负载均衡Hystix
:熔断器Feign
:服务调用Zuul
:服务网关
其架构图如下:
一个简单的微服务 Demo
现在我们模拟一个服务调用的场景,方便后面学习微服务架构。
项目结构
该 Demo 的项目结构如下:1
2
3
4
5
6
7
8
9
10
11
12springcloud-demo
├── pom.xml
├── consumer
│ ├── pom.xml
│ └── src
│ ├── main
│ └── test
└── user-service
├── pom.xml
└── src
├── main
└── test
创建父工程
微服务中需要创建多个项目,所以我们一般都会创建多模块项目。
在实际开发中,应该是每个微服务独立一个工程。
首先我们创建一个父工程springcloud-demo
并在pom.xml
文件进行如下配置: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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<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>cn.wk.springcloud.demo</groupId>
<artifactId>springcloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
<mapper.start.version>2.1.5</mapper.start.version>
<mysql.version>8.0.13</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<!--springcloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--通用mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.start.version}</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建服务提供者
创建一个子模块user-service
,继承自父模块springcloud-demo
,其对外提供查询用户的服务。
① 添加依赖
user-service
的pom.xml
文件配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<parent>
<artifactId>springcloud-demo</artifactId>
<groupId>cn.wk.springcloud.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
</dependencies>
② 编写代码
application.yml
属性文件配置:1
2
3
4
5
6
7
8
9
10server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&characterSetResults=utf8&serverTimezone=GMT
username: root
password: 123
mybatis:
type-aliases-package: cn.wk.pojo
创建一个数据库springcloud
,表的 SQL 语句如下: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
34SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`name` varchar(50) NOT NULL,
`telephone` int(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'lucy', '123', '章总', 0);
INSERT INTO `user` VALUES (2, 'tom', '666666', '阿斯顿', 0);
INSERT INTO `user` VALUES (3, 'wangwu', '123', '王五', 110);
INSERT INTO `user` VALUES (5, 'skying', '123', '张三', 120);
INSERT INTO `user` VALUES (6, 'zhangsan', '123', '李四', 120);
INSERT INTO `user` VALUES (7, 'lisi', '123', '刘九', 120);
INSERT INTO `user` VALUES (8, 'lucy2', '123', '钱七', 110);
INSERT INTO `user` VALUES (9, 'Jack', '1231313', '王总', 110);
INSERT INTO `user` VALUES (10, 'Jim', '123', '赵六', 222);
INSERT INTO `user` VALUES (11, 'william', '123456789', 'tim', 1501234567);
INSERT INTO `user` VALUES (12, 'william', '123456789', '刘总', 1371234567);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
启动类:1
2
3
4
5
6
7
"cn.wk.mapper") (
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
实体类:1
2
3
4
5
6
7
8
9
10
11
"user") (name =
public class User {
true) (useGeneratedKeys =
private Integer id;
private String username;
private String password;
private String name;
private Integer telephone;
}
通用mapper
:1
2public interface UserMapper extends Mapper<User> {
}
Service
接口及实现类:1
2
3
4
5
6
7
8
9
10
11
12
13
14public interface UserService {
User queryById(Integer id);
}
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
public User queryById(Integer id) {
return userMapper.selectByPrimaryKey(id);
}
}
对外查询的controller
接口:1
2
3
4
5
6
7
8
9
10
11
"/user") (
public class UserController {
private UserService userService;
"/{id}") (
public User queryById(@PathVariable("id") Integer id) {
return userService.queryById(id);
}
}
③ 启动并测试
当我们在浏览器访问http://localhost:8081/user/1
,则会得到如下 JSON 数据:1
{"id":1,"username":"lucy","password":"123","name":"章总","telephone":0}
创建服务调用者
现在我们创建另一个子模块consumer
,也继承自父模块springcloud-demo
,该模块用来调用子模块user-service
。
① 添加依赖
consumer
的pom.xml
文件配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<parent>
<artifactId>springcloud-demo</artifactId>
<groupId>cn.wk.springcloud.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.yml
属性文件默认即可。
② 编写代码
首先在启动类注册RestTemplate
:1
2
3
4
5
6
7
8
9
10
11
12
public class ConsumerApplication {
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
User
实体类:1
2
3
4
5
6
7
8
public class User {
private Integer id;
private String username;
private String password;
private String name;
private Integer telephone;
}
填写Controller
,在其中直接调用RestTemplate
,远程访问user-service
的服务接口:1
2
3
4
5
6
7
8
9
10
11
"consumer") (
public class ConsumerController {
private RestTemplate restTemplate;
"{id}") (
public User queryById(@PathVariable("id") Integer id) {
return restTemplate.getForObject("http://localhost:8081/user/" + id, User.class);
}
}
③ 启动并测试
我们启动两个模块,当在浏览器输入http://localhost:8080/consumer/1
,会通过consumer
模块调用user-service
模块,所以浏览器也会得到如下结果:1
{"id":1,"username":"lucy","password":"123","name":"章总","telephone":0}
虽然我们没有配置consumer
的 Tomcat 端口,但其默认端口为 8080 ,通过该模块调用了 8081 端口的模块,达到了服务分割的效果。
项目问题与解决方案
刚才我们写了什么?
user-service
模块:对外提供了查询用户的接口consumer
模块:通过RestTemplate
访问http://localhost:8080/consumer/{id}
接口,能调用另一个模块去查询用户数据
但上面的 demo 存在如下问题:
consumer
中把url
地址硬编码到了代码中,不方便后期维护consumer
需要记忆user-service
的地址,后期若出现变更,需要及时更新- 服务模块较少易管理,但在如今日益复杂的互联网中,一个项目肯定会拆分出十几甚至几十个微服务,人为管理地址会非常麻烦
consumer
不清楚user-service
的状态,其服务宕机也不知道user-service
只有 1 台 Tomcat 服务器,不具备高可用性- 即使
user-service
形成集群,consumer
还需自己实现负载均衡
上面说明的问题,概括一下就是分布式服务必然要面临的问题:
- 服务如何自动注册和发现
- 服务如何实现状态监管
- 服务如何实现负载均衡
- 服务如何实现动态路由
- 服务如何解决容灾问题
- 服务如何实现统一配置
这些问题,都将在 Spring Cloud 中得到答案。