一、最簡單的分佈式架構,服務消費者與服務提供者架構
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/