一、最简单的分布式架构,服务消费者与服务提供者架构
1.1 定义
名词 | 定义 |
服务提供者 | 服务的被调用方(即:为其他服务提供服务的服务) |
服务消费者 | 服务的调用方(即:依赖其他服务的服务) |
1.2 架构概述
以电影售票系统为例。如图,用户向电影微服务发起了一个购票的请求。在进行购票的业务操作前,电影微服务需要调用用户微服务的接口,查询当前用户的余额是多少、是不是符合购票标准等。在这种场景下,用户微服务就是一个服务提供者,电影微服务则是一个服务消费者。
围绕该场景,先来编写一个用户微服务,然后编写一个电影微服务。
注意:
服务消费者和服务提供者描述的只是微服务之间的调用关系,一般成对出现。例如本文,用户微服务是是电影微服务的服务提供者,电影微服务是用户微服务的服务消费者。很多初学者和笔者交流时,会描述提供者如何如何……仿佛消费者和提供者是微服务的固有属性,这是不对的——例如A调用B,B调用C,那么B相对A就是提供者,B相对C就消费者。
二、开发
创建新的项目,并在目录下新建子模块,分别是microservice-simple-provider-user(服务提供者)和microservice-simple-consumer-movie(服务消费者)
2.1 创建microservice-simple-provider-user(服务提供者)
2.1.1 在父目录pom.xml文件添加配置
<!-- 引入spring boot的依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
</parent>
2.1.2 在microservice-simple-provider-user(服务提供者)子模块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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-study</artifactId>
<groupId>com.qhr.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>microservice-simple-provider-user</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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-data-jpa</artifactId>
</dependency>
<!-- 引入H2数据库,一种内嵌的数据库,语法类似MySQL -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- 引入Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 引入spring cloud的依赖,不能少,主要用来管理Spring Cloud生态各组件的版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件,不能少,打jar包时得用 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.1.3 创建实体类User
package com.qhr.cloud.study.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.math.BigDecimal;
/**
* @Author : qhr
* @Description :
* @Date : Created in 16:44 2020/6/24
* @Modified By :
*/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
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;
}
2.1.4 创建持久层UserRepository文件
package com.qhr.cloud.study.repository;
import com.qhr.cloud.study.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @Author : qhr
* @Description :
* @Date : Created in 16:46 2020/6/24
* @Modified By :
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
2.1.5 创建UserController文件
package com.qhr.cloud.study.controller;
import com.qhr.cloud.study.entity.User;
import com.qhr.cloud.study.repository.UserRepository;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Optional;
/**
* @Author : qhr
* @Description :
* @Date : Created in 16:49 2020/6/24
* @Modified By :
*/
@RequestMapping("/users")
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/{id}")
public Optional<User> findById(@PathVariable Long id) {
return this.userRepository.findById(id);
}
@GetMapping("/list")
public List<User> getList() {
return this.userRepository.findAll();
}
}
2.1.6 创建启动类ProviderUserApplication
package com.qhr.cloud.study;
import com.qhr.cloud.study.entity.User;
import com.qhr.cloud.study.repository.UserRepository;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.math.BigDecimal;
import java.util.stream.Stream;
/**
* @Author : qhr
* @Description :
* @Date : Created in 16:56 2020/6/24
* @Modified By :
*/
@SpringBootApplication
public class ProviderUserApplication {
public static void main(String args[]) {
SpringApplication.run(ProviderUserApplication.class, args);
}
/**
* 初始化用户信息
* 注:Spring Boot2不能像1.x一样,用spring.datasource.schema/data指定初始化SQL脚本,否则与actuator不能共存
* 原因详见:
* https://github.com/spring-projects/spring-boot/issues/13042
* https://github.com/spring-projects/spring-boot/issues/13539
*
* @param repository repo
* @return runner
*/
@Bean
ApplicationRunner init(UserRepository repository) {
return args -> {
User user1 = new User(1L, "account1", "张三", 20, new BigDecimal(100.00));
User user2 = new User(2L, "account2", "李四", 28, new BigDecimal(180.00));
User user3 = new User(3L, "account3", "王五", 32, new BigDecimal(280.00));
Stream.of(user1, user2, user3)
.forEach(repository::save);
};
}
}
2.1.7 创建application.yml文件
server:
# 指定Tomcat端口
port: 8000
spring:
jpa:
# 让hibernate打印执行的SQL
show-sql: true
logging:
level:
root: INFO
# 配置日志级别,让hibernate打印出执行的SQL参数
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
3.1 创建 microservice-simple-consumer-movie(服务消费者)
3.1.2 在microservice-simple-consumer-movie(服务消费者)的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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-study</artifactId>
<groupId>com.qhr.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>microservice-simple-consumer-movie</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 引入spring cloud的依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.1.3 创建实体User
package com.qhr.cloud.study.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* @Author : qhr
* @Description :
* @Date : Created in 17:15 2020/6/24
* @Modified By :
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String name;
private Integer age;
private BigDecimal balance;
}
3.1.4 创建MovieController
package com.qhr.cloud.study.controller;
import com.qhr.cloud.study.entity.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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @Author : qhr
* @Description :
* @Date : Created in 17:16 2020/6/24
* @Modified By :
*/
@RequestMapping("/movies")
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/users/{id}")
public User findById(@PathVariable Long id) {
// 这里用到了RestTemplate的占位符能力
User user = this.restTemplate.getForObject("http://localhost:8000/users/{id}", User.class, id);
// ...电影微服务的业务...
return user;
}
@GetMapping("/users/list")
public List<User> getList() {
// 这里用到了RestTemplate的占位符能力
List list = this.restTemplate.getForObject("http://localhost:8000/users/list", List.class);
// ...电影微服务的业务...
return list;
}
}
3.1.5 创建启动类ConsumerMovieApplication
package com.qhr.cloud.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @Author : qhr
* @Description :
* @Date : Created in 17:17 2020/6/24
* @Modified By :
*/
@SpringBootApplication
public class ConsumerMovieApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
3.1.6 创建application.yml文件
server:
port: 8010
4 测试
分别启动microservice-simple-provider-user(服务提供者)和microservice-simple-consumer-movie(服务消费者),并通过http://localhost:8010/movies/users/list访问:
访问结果如下,证明环境搭建成功:
文章参考:http://www.itmuch.com/spring-cloud/finchley-2/
5 问题
至此,我们已经实现了这个最简单的分布式应用,应用之间通过HTTP通信。代码非常简单,但这些简单的代码里,存在着若干问题:
- 应用没有监控,没有画板,一切指标都没有。
- 地址硬编码问题。
- 负载均衡。
- 服务之间没有容错机制。
- 如果应用发生故障,你怎么迅速找到问题所在?
- 用户认证和授权。
- .......
6 监控
6.1 监控的实现
我们通过使用Spring Boot Actuator来实现监控的,Spring Boot Actuator是Spring Boot官方提供的监控组件。只需在需要用到的微服务的pom.xml文件添加以下配置,即可:
<!--Spring Boot Actuator监测组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
6.2 Spring Boot Actuator使用的监控端点
端点(Spring Boot 2.x) | 描述 | HTTP方法 | 是否敏感 | 端点(Spring Boot 1.x) |
---|---|---|---|---|
auditevents | 显示应用暴露的审计事件(比如认证进入、订单失败) | GET | 是 | |
beans | 显示应用程序上下文所有的Spring bean | GET | 是 | beans |
health |
显示应用程序的健康指标,值由HealthIndicator的实现类提供;结果有UP、 DOWN、OUT_OF_SERVICE、UNKNOWN;如需查看详情,需配置: management.endpoint.health.show-details |
GET | 否 | health |
conditions | 显示自动配置的信息 | GET | 是 | autoconfig |
configprops | 显示所有@ConfigurationProperties的配置属性列表 | GET | 是 | configprops |
env | 显示环境变量,包括系统环境变量以及应用环境变量 | GET | 是 | env |
info | 显示应用的信息,可使用info.* 属性自定义info端点公开的数据 |
GET | 否 | info |
loggers | 显示和修改配置的loggers | GET | 是 | |
heapdump | 返回一个GZip压缩的JVM堆dump | GET | 是 | dump |
threaddump | 执行一个线程dump | GET | 是 | dump |
metrics | 显示应用的度量标准信息 | GET | 是 | metrics |
scheduledtasks | 显示应用中的调度任务 | GET | 是 | |
httptrace | 显示HTTP足迹,最近100个HTTP request/reponse | GET | 是 | |
mappings | 显示所有的URL路径 | GET | 是 | mappings |
参考文章:https://www.jianshu.com/p/8bfac9289c7e
访问http://{ip}:{port}/actuator/{endpoint}
端点,即可监控应用的运行状况。
6.3 测试
6.3.1 /health端点
为前文编写的microservice-simple-provider-user
服务整合Actuator后,访问:http://localhost:8000/actuator/health
{"status":"UP"}
6.3.2 /health端点展示详情
在microservice-simple-provider-user
的application.yml,添加:
management:
endpoint:
health:
# 是否展示健康检查详情
show-details: always
访问测试:http://localhost:8000/actuator/health
{
"status": "UP",
"details": {
"db": {
"status": "UP",
"details": {
"database": "H2",
"hello": 1
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 691212906496,
"free": 397430468608,
"threshold": 10485760
}
}
}
}
6.3.3 暴露敏感路径
暴露指定敏感路径:
management:
endpoints:
web:
exposure:
# 暴露metrics端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*'
include: metrics
暴露所有敏感路径:
management:
endpoints:
web:
exposure:
# 开放所有监控端点
include: '*'
访问:http://localhost:8000/actuator/metrics
{
"names": [
"jvm.memory.max",
"jdbc.connections.active",
"jvm.gc.memory.promoted",
"tomcat.cache.hit",
"tomcat.cache.access",
"jvm.memory.used",
"jvm.gc.max.data.size",
"jdbc.connections.max",
"jdbc.connections.min",
"jvm.gc.pause",
"jvm.memory.committed",
"system.cpu.count",
"logback.events",
"tomcat.global.sent",
"jvm.buffer.memory.used",
"tomcat.sessions.created",
"jvm.threads.daemon",
"system.cpu.usage",
"jvm.gc.memory.allocated",
"tomcat.global.request.max",
"hikaricp.connections.idle",
"hikaricp.connections.pending",
"tomcat.global.request",
"tomcat.sessions.expired",
"hikaricp.connections",
"jvm.threads.live",
"jvm.threads.peak",
"tomcat.global.received",
"hikaricp.connections.active",
"hikaricp.connections.creation",
"process.uptime",
"http.server.requests",
"tomcat.sessions.rejected",
"process.cpu.usage",
"tomcat.threads.config.max",
"jvm.classes.loaded",
"hikaricp.connections.max",
"hikaricp.connections.min",
"jvm.classes.unloaded",
"tomcat.global.error",
"tomcat.sessions.active.current",
"tomcat.sessions.alive.max",
"jvm.gc.live.data.size",
"tomcat.servlet.request.max",
"hikaricp.connections.usage",
"tomcat.threads.current",
"tomcat.servlet.request",
"hikaricp.connections.timeout",
"jvm.buffer.count",
"jvm.buffer.total.capacity",
"tomcat.sessions.active.max",
"hikaricp.connections.acquire",
"tomcat.threads.busy",
"process.start.time",
"tomcat.servlet.error"
]
}
访问:http://localhost:8000/actuator/metrics/jvm.memory.max
{
"name": "jvm.memory.max",
"description": "The maximum amount of memory in bytes that can be used for memory management",
"baseUnit": "bytes",
"measurements": [
{
"statistic": "VALUE",
"value": 4.484759551E9
}
],
"availableTags": [
{
"tag": "area",
"values": [
"heap",
"nonheap"
]
},
{
"tag": "id",
"values": [
"Compressed Class Space",
"PS Survivor Space",
"PS Old Gen",
"Metaspace",
"PS Eden Space",
"Code Cache"
]
}
]
}
参考文章:http://www.itmuch.com/spring-cloud/finchley-3/