三、开始使用Spring Cloud
开始之前
还是选择最新的Spring Boot 2.3.4.RELEASE 版本,采用mysql作为数据库而不是h2。 也用到了一些常用的maven插件
1.从例子入手
一个电影售票系统,用户向电影微服务发起购票请求,电影微服务要调用用户微服务接口,查询用户余额,场景示例如下
先编写一个用户微服务,再编写一个电影微服务
2.编写服务提供者
需求:通过主键查询用户信息。便于测试,使用Spring Data JPA作为持久层框架,使用H2作为数据库
编写项目
1.起一个项目microservice-simple-provider-user
,使用IDEA创建Spring Boot项目即可(创建过程不再赘述),具体的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lzy.cloud</groupId>
<artifactId>microservice-simple-provider-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>microservice-simple-provider-user</name>
<description>a user information provider</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.建表语句内容如下:
drop table user if exists;
create table user (
id bigint generated by default as identity,
username varchar (40),
name varchar (40),
age int(3),
balance decimal(10,2),
primary key (id)
);
3.插入初始数据:
insert into user (id,username,name,age,balance) values
(1,'account1','张三',20,100.00);
insert into user (id,username,name,age,balance) values
(2,'account2','李四',28,180.00);
insert into user (id,username,name,age,balance) values
(3,'account3','王五',32,280.00);
4.创建实体类
package com.lzy.cloud.microservicesimpleprovideruser.po;
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String username;
@Column
private String name;
@Column
private Integer age;
@Column
private BigDecimal balance;
}
5.创建DAO
package com.lzy.cloud.microservicesimpleprovideruser.dao;
import com.lzy.cloud.microservicesimpleprovideruser.po.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserDao extends JpaRepository<User, Long>{
}
6.创建Controller
package com.lzy.cloud.microservicesimpleprovideruser.controller;
import com.lzy.cloud.microservicesimpleprovideruser.dao.UserDao;
import com.lzy.cloud.microservicesimpleprovideruser.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
@RestController
public class UserController {
@Autowired
private UserDao userDao;
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
Optional<User> findOne = userDao.findById(id);
User result = findOne.get();
return result;
}
}
7.启动类代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MicroserviceSimpleProviderUserApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceSimpleProviderUserApplication.class, args);
}
}
8.编写配置文件
server:
port: 8080
spring:
datasource:
#通用数据源配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?charset=utf8mb4&useSSL=false
username: root
password: 123456
# Hikari 数据源专用配置 Hikari 是Springboot用的连接池
hikari:
maximum-pool-size: 20
minimum-idle: 5
#初始化数据
# JPA 相关配置
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
hibernate:
# 慎用!
# ddl-auto: create
logging:
level:
root: INFO
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.Extractor: TRACE
# 配置在日志中打印出执行的 SQL 语句信息。
#spring.jpa.show-sql=true
# 配置指明在程序启动的时候要删除并且创建实体类对应的表。这个参数很危险,因为他会把对应的表删除掉然后重建。
# 所以千万不要在生成环境中使用。只有在测试环境中,一开始初始化数据库结构的时候才能使用一次。
#spring.jpa.hibernate.ddl-auto=create
# 在 SrpingBoot 2.0 版本中,Hibernate 创建数据表的时候,默认的数据库存储引擎选择的是 MyISAM。
# 这个参数是在建表的时候,将默认的存储引擎切换为 InnoDB 用的。
#spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
启动项目,进行测试,访问网址http://localhost:8080/1
得到json格式返回数据
{
"id": 1,
"username": "account1",
"name": "张三",
"age": 20,
"balance": 100
}
3.编写服务消费者
前面写了一个服务提供者(用户微服务),下面写一个服务消费者(电影微服务)。
该服务十分简单,它使用RestTemplate调用用户微服务API,查询指定id的用户信息
1.创建Maven项目,也可以直接创建Spring Boot项目,ArtifactId 是 microservice-simple-consumer-movie
2.和用户微服务相同,引入同样的依赖,可以使用和上面一样的pom.xml文件,但要把没有用到的组件依赖删除掉
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lzy.cloud</groupId>
<artifactId>microservice-simple-consumer-movie</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>microservice-simple-consumer-movie</name>
<description>a consumer for movie</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.创建用户实体类,该类是一个POJO
package com.lzy.cloud.microservicesimpleconsumermovie.pojo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class User {
private Long id;
private String username;
private String name;
private Integer age;
private BigDecimal balance;
}
4.创建启动类,代码如下:
package com.lzy.cloud.microservicesimpleconsumermovie;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class MicroserviceSimpleConsumerMovieApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MicroserviceSimpleConsumerMovieApplication.class, args);
}
}
@Bean是一个方法注解,可以实例化一个Bean并使用该方法的名称命名,添加了@Bean注解的restTemplate()方法,等价于RestTemplate restTemplate = new RestTemplate();
5.创建Controller,在其中使用RestTemplate请求用户微服务的API
package com.lzy.cloud.microservicesimpleconsumermovie.controller;
import com.lzy.cloud.microservicesimpleconsumermovie.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject("http://localhost:8080/" +id, User.class);
}
}
6.编写配置文件application.yaml
server:
port: 8010
测试访问http://localhost:8010/user/1,结果如下
{
"id": 1,
"username": "account1",
"name": "张三",
"age": 20,
"balance": 100
}
4.整合Spring Boot Actuator
Spring Boot Actuator提供了很多监控端点,能使用 http://{ip}:{port}/{endpoint}形式来访问,了解应用程序运行状况
为上面两个项目添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Springboot 2.x 之后,多数端点被禁,日志打印信息有变,变为如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvtZMKLn-1600789728197)(/Users/lzy/资料/学习/Typora图片/image-20200915131401169.png)]
所有的监管点加上了 /actuator
前缀,如果想暴露所有监管点,需要在配置文件中引入
配置application.yml文件暴露所有监管点:
management:
endpoints:
web:
base-path: /actuator #如果修改,则监管点访问路径前缀也会改变
exposure:
include: "*" #星号代表暴露全部,可以选择暴露部分,填入想暴露的即可
更多参考:http://www.ityouknow.com/springboot/2018/02/06/spring-boot-actuator.html
原生端点
- 应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与SpringBoot密切相关的配置类信息
- 度量指标类:获取应用程序运行过程中用于监控的度量指标,如内存、线程池、http请求信息等
- 操作控制类:提供了对应用关闭等操作类功能
下面我们看一下可用的端点,他们大部分在1.x中已经存在。尽管如此,有些端点新增,有些被删除,有些被重构。
- /auditevents:同Actuator 1.x,还可以通过关键字进行过滤
- /beans:同Actuator 1.x,不可以过滤
- /conditions:返回服务中的自动配置项
- /configprops:允许我们获取
@ConfigurationProperties
的bean对象 - /env:返回当前的环境变量,我们也可以检索某个值
- /flyway:提供Flyway数据库迁移的详细情况
- /health:同Actuator 1.x
- /heapdump:返回应用服务使用地jvm堆dump信息
- /info:同Actuator 1.x
- /liquibase:类似于 /flyway,但是组件工具为Liquibase
- /logfile:返回应用的普通日志文件
- /loggers:允许我们查询和修改应用的日志等级
- /metrics:同Actuator 1.x
- /prometheus:返回与/metrics类似,与Prometheus server一起使用
- /scheduledtasks:返回应用的周期性任务
- /sessions:同Actuator 1.x
- /shutdown:同Actuator 1.x
- /threaddump:dump所依赖的jvm线程信息
例如metrics端点,进入后变成如下结果:
{
"names": [
"http.server.requests",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.gc.pause",
...
],
}
如果想要看某项监控指标,需要路径中继续添加,如
http://localhost:8082/actuator/metrics/jvm.buffer.memory.used
结果如下,比1.X 更加详细
{
"name": "jvm.buffer.memory.used",
"description": "An estimate of the memory that the Java virtual machine is using for this buffer pool",
"baseUnit": "bytes",
"measurements": [
{
"statistic": "VALUE",
"value": 69632
}
],
"availableTags": [
{
"tag": "id",
"values": [
"direct",
"mapped"
]
}
]
}
需要注意 2.x 之后很多路径都改变了
访问 localhost:8080/info返回了空的json结构,可以用info.*属性来自定info端点公开的数据,在yaml配置文件中添加:
info:
app:
name:microservice-simple-provider-user
再访问localhost:8080/info,得到如下数据
{
"app": "name:microservice-simple-provider-user"
}
5.硬编码的问题
在电影微服务的Controller类中:
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject("http://localhost:8080/" +id, User.class);
}
将服务提供者的网络地址和端口硬编码在了代码中,也可以提取到配置文件中:
user:
userServiceUrl:http://localhost:8080/
代码改为
@Value("user.userServiceUrl")
private String userServiceUrl;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject(this.userServiceUrl +id, User.class);
}
传统应用程序中经常这样做,但依然存在很多问题
- 适用场景有限:服务提供者网络地址发生改变,就会影响服务消费者
- 无法动态伸缩:生产环境中每个微服务要部署多个实例,实现容灾和负载均衡,微服务架构系统中,还要系统具有自动伸缩能力,例如动态增删节点等。硬编码无法适应这种需求。
下一节将关注微服务的注册与发现,解决这些问题