初识 Spring Cloud

微服务

  开始使用 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
12
springcloud-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
<?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>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-servicepom.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
10
server:
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
34
SET 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
@SpringBootApplication
@MapperScan("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
@Data
@Table(name = "user")
public class User {
@Id
@KeySql(useGeneratedKeys = true)
private Integer id;
private String username;
private String password;
private String name;
private Integer telephone;
}

  通用mapper

1
2
public interface UserMapper extends Mapper<User> {
}

  Service接口及实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface UserService {
User queryById(Integer id);
}

@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;

@Override
public User queryById(Integer id) {
return userMapper.selectByPrimaryKey(id);
}
}

  对外查询的controller接口:

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;

@GetMapping("/{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

① 添加依赖

  consumerpom.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
@SpringBootApplication
public class ConsumerApplication {

@Bean
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
@Data
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
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;

@GetMapping("{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 中得到答案。

0%