目錄
⼀、單體應⽤存在的問題
1.1 商城項⽬架構
1.2 單體項⽬存在的問題
⼆、微服務架構
2.1 微服務架構概念
2.2 微服務架構優點
2.3 微服務架構缺點
三、微服務架構開發需要解決的問題
3.1 服務之間如何互相發現?
3.2 服務之間如何進⾏通信?
3.2.1 同步調⽤
3.2.2 異步調⽤
3.3 服務挂了該如何解決?
3.3.1 服務故障雪崩
3.3.2 如何解決服務故障雪崩
3.4 用戶端如何統⼀通路多個接⼝服務?
四、微服務架構架構
4.1 主流的微服務架構架構
4.2 SpringCloud簡介
4.3 Spring Cloud核⼼元件
4.4 SpringCloud版本介紹
五、搭建服務注冊與發現中⼼
5.1 建立SpringBoot應⽤,添加依賴
5.2 配置服務注冊與發現中⼼
5.3 在啟動類添加@EnableEurekaServer注解
5.4 運⾏及通路
六、服務注冊
6.1 建立SpringBoot應⽤
6.2 注冊服務
6.2.1 添加依賴
6.2.2 配置application.yml
6.2.3 在目前服務應⽤的啟動類添加 @EnableEurekaClient 注解
七、服務發現-Ribbon
7.1 基礎配置
7.1.1 建立SpringBoot應⽤,添加依賴
7.1.2 配置application.yml
7.1.3 在啟動類添加 @EnableDiscoveryClient 注解
7.2 服務調⽤
7.2.1 配置RestTemplate
7.2.2 在Service中注⼊RestTemplate對象調⽤服務
7.3 案例流程圖
7.4 Ribbon服務調⽤說明
⼋、基于Ribbon進⾏服務調⽤的參數傳遞
8.1 RestTemplate發送調⽤請求的⽅法
8.2 put/post請求傳參
8.3 get請求傳參
九、服務發現-Feign
9.1 基礎配置
9.1.1 建立SpringBoot應⽤,添加依賴
9.1.2 配置application.yml
9.1.3 在啟動類添加注解
9.2 服務調⽤
9.2.1 建立Feign用戶端
9.2.2 使⽤Feign用戶端調⽤服務
9.3 Feign傳參
9.3.1 POST請求
9.3.2 Get請求
⼗、服務注冊與發現中⼼的可靠性和安全性
10.1 可靠性
10.2 安全性
10.2.1 添加SpringSecurity的依賴
10.2.3 配置Spring Security
10.2.4 服務提供者和服務消費者連接配接到注冊中⼼都要帳号和密碼
⼗⼀、熔斷器-Hystrix
11.1 熔斷器介紹
11.2 熔斷器的原理
11.3 基于Ribbon服務調⽤的熔斷器使⽤
11.3.1 服務消費者的 服務降級
11.3.2 服務提供者的 服務降級
11.3.3 服務熔斷配置
11.4 基于Feign服務調⽤的熔斷器使⽤
11.4.1 Feign中的熔斷器使⽤
11.4.2 Ribbon 參數配置
11.5 熔斷器儀表盤監控
11.5.1 搭建熔斷器儀表盤
11.5.2 配置使⽤了熔斷器的服務可被監控
⼗⼆、微服務拆分
⼗三、服務鍊路追蹤
13.1 服務追蹤說明
13.2 Zipkin
13.3 搭建zipkin伺服器
13.4 服務中Sleuth配置
13.5 zipkin服務資料存儲
⼀、單體應⽤存在的問題
1.1 商城項⽬架構
為了解決⾼并發問題,我們的 Tomcat 采⽤了叢集部署,但是每個 Tomcat 節點上不是的 依然是單體項⽬(雖然是前後端分離,但是後端采⽤的是單體開發 —— 所有的接⼝都在 同⼀個項⽬中)
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL4kjNiNWZ0AjZyUDO1QDN4czN4QjZ3UTM1AjY4EWYmJ2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
1.2 單體項⽬存在的問題
⼀個成功的應⽤必然有⼀個趨勢:⽤戶量會不斷增加、項⽬的業務也會不斷的擴充 ⽤戶量的增加會帶來⾼并發的問題,⾼并發問題解決⽅案:
- 應⽤伺服器 --> 單體優化 --> 叢集(負載均衡、分布式并發)
- 資料庫伺服器 --> 資料庫優化 --> 緩存redis --> 分布式資料庫
項⽬業務的擴充,也會帶來⼀些問題:
- 項⽬結構越來越臃腫(項⽬結構和代碼複雜、項⽬體積逐漸變得龐⼤)
- 項⽬結構和代碼複雜導緻項⽬不易維護和⼆次開發、擴充和更新就會變得困難
- 項⽬體積逐漸變得龐⼤導緻啟動時間越來越⻓、⽣産⼒⼤受限制
- 單體應⽤中任何⼀個子產品的任何⼀個bug都會導緻整個系統不可⽤(單點故障)
- 複雜的單體項⽬也會帶來持續部署的障礙
- 單體項⽬使得采⽤新的技術和架構會變得困難
⼆、微服務架構
2.1 微服務架構概念
微服務架構是⼀種架構概念,就是将⼀個單體應⽤中的每個功能分解到各個離散的服務 中以實作對單體應⽤的解耦,并提供更加靈活的服務⽀持 從單體到微服務
2.2 微服務架構優點
- 解決了單體項⽬的複雜性問題
- 每個服務都可以由單獨的團隊進⾏開發
- 每個服務都可以使⽤單獨的技術棧進⾏開發
- 每個服務都是獨⽴的進⾏部署和維護
- 每個服務都可以獨⽴進⾏擴充
2.3 微服務架構缺點
- 微服務架構本身就是⼀個缺點:如何把握“微”的粒度;
- 微服務架構是⼀個分布式系統,雖然單個服務變得簡單了,但是服務之間存在互相的調 ⽤,整個服務架構的系統變得複雜了;
- 微服務架構需要依賴分布式資料庫架構;
- 微服務的單元測試及調⽤變得⽐單體更為複雜;
- 部署基于微服務架構的應⽤程式變得⾮常複雜;
- 進⾏微服務架構的應⽤程式開發的技術成本變得更⾼。
三、微服務架構開發需要解決的問題
在微服務架構開發的系統中必然存在很多個服務,服務之間需要互相感覺對⽅的存在, 需要進⾏服務間的調⽤,該如何實作呢? —— 進⾏微服務架構開發需要解決的問題: 1. 如此多的服務,服務間如何互相發現? 2. 服務與服務之間該如何通信? 3. 如果某個服務挂了,該如何處理? 4. 前端通路多個不同的服務時該如何統⼀通路路徑呢?
3.1 服務之間如何互相發現?
微服務架構 —— 每個服務隻處理⼀件事情 / ⼀個步驟,在⼀個複雜的業務中必然會存在服 務間的互相調⽤,服務想要互相調⽤就需要先發現對⽅。 通過服務注冊與發現中⼼實作服務間的互相發現
服務注冊與發現中⼼也是⼀台獨⽴伺服器 1. 服務提供者在服務注冊與發現中⼼進⾏注冊 2. 服務注冊與發現中⼼進⾏服務記錄,并與服務提供者保持⼼跳 3. 服務消費者通過服務注冊與發現中⼼進⾏服務查詢(服務發現) 4. 服務注冊與發現中⼼傳回可⽤的服務的伺服器位址清單 5. 服務消費者通過負載均衡通路服務提供者
3.2 服務之間如何進⾏通信?
服務消費者在調⽤服務提供者時,⾸先需要通過 服務注冊與發現中⼼ 進⾏服務服務查詢, 傳回服務清單給服務消費者, 服務消費者 通過 LoadBalance 調⽤ 服務提供者 , 那麼他們之 間是如何通信呢? —— 資料傳輸規則 服務與服務間的通信⽅式有 2 種:同步調⽤ 和 異步調⽤
3.2.1 同步調⽤
- REST(SpringCloud Netflix,SpringCloud Alibaba)
- 基于HTTP協定的請求和響應
- 更容易實作、技術更靈活
- ⽀持多語⾔、同時可以實作跨用戶端
- 适⽤⾯很⼴
- RPC(Dubbo)
- 基于⽹絡層協定通信
- 傳輸效率⾼
- 安全性更⾼
- 如果有統⼀的開發規劃或者架構,開發效率是⽐較⾼的
3.2.2 異步調⽤
服務間的異步通信通常是通過消息隊列實作的
3.3 服務挂了該如何解決?
3.3.1 服務故障雪崩
3.3.2 如何解決服務故障雪崩
- 服務叢集——盡量保證每個服務可⽤
- 服務降級與熔斷——避免請求阻塞造成正常的服務出現故障
3.4 用戶端如何統⼀通路多個接⼝服務?
通過路由⽹關實作接⼝的統⼀通路
四、微服務架構架構
4.1 主流的微服務架構架構
- Dubbo(阿⾥、開源apache):2012年推出、2014年停更、2015年⼜繼續更新
- Dubbox(當當⽹基于Dubbo的更新)
- jd-hydra(京東基于Dubbo的更新)
- SpringCloud Netflix (2016年)/ SpringCloud Alibaba
- ServiceComb(CSE)華為 2017年
4.2 SpringCloud簡介
Spring Cloud 是⼀個基于 SpringBoot 實作的微服務架構應⽤開發架構,它為我們進⾏微 服務架構應⽤開發提供了服務注冊與發現、熔斷器、⽹關路由、配置管理、負載均衡、 消息總線、資料監控等⼀系列⼯具。 Spring Cloud ⽐較成熟的兩個體系:
- Spring Cloud Netflix
- Spring Cloud Alibaba
4.3 Spring Cloud核⼼元件
- Spring Cloud Netflix
- Eureka 服務注冊與發現中⼼,⽤于服務治理
- Ribbon 服務通路元件、進⾏服務調⽤,實作了負載均衡
- Hystrix 熔斷器,服務容錯管理
- Feign 服務通路元件(對Ribbon和HyStrix的封裝)
- zuul ⽹關元件
- Spring Cloud Config 配置管理的元件—分布式配置中⼼
- Spring Cloud Bus 消息總線
- Spring Cloud Consul 服務注冊與發現中⼼(功能類似eureka)
4.4 SpringCloud版本介紹
- SpringCloud版本 : A-H,2020.0.2
- SpringCloud的版本對SpringBoot版本時有依賴的
- A ---- 1.2
- B ---- 1.3
- C ---- 1.4
- D-E ---- 1.5
- F-G-H ---- 2.x
五、搭建服務注冊與發現中⼼
使⽤ Spring Cloud Netflix 中的 Eureka 搭建服務注冊與發現中⼼
5.1 建立SpringBoot應⽤,添加依賴
- spring web
- eureka server
5.2 配置服務注冊與發現中⼼
## 設定服務注冊與發現中⼼的端⼝ server : port : 8761 ## 在微服務架構中,服務注冊中⼼是通過服務應⽤的名稱來區分每個服務的 ## 我們在建立每個服務之後,指定目前服務的 應⽤名 / 項⽬名 spring : application : name : service-eureka eureka : client : ## ip 就是服務注冊中⼼伺服器的 ip ## port 就是服務注冊與發現中⼼設定的 port service-url : defaultZone : http : //192.168.54.59 : 8761/eureka ## 設定服務注冊與發現中⼼是否為為叢集搭建(如果為叢集模式,多個 eureka 節點之間 需要互相注冊) register-with-eureka : false ## 設定服務注冊與發現中是否作為服務進⾏注冊 fetch-registry : false
5.3 在啟動類添加@EnableEurekaServer注解
5.4 運⾏及通路
六、服務注冊
建立儲存訂單的服務( order-add )注冊到服務注冊與發現中⼼
6.1 建立SpringBoot應⽤
建立 spring boot 應⽤,完成功能開發
6.2 注冊服務
将能夠完成特定業務的 SpringBoot 應⽤作為服務提供者,注冊到服務注冊與發現中⼼
6.2.1 添加依賴
eureka-server [ 注意版本! ]
<dependency> <groupId> org.springframework.cloud </groupId> <artifactId> spring-cloud-starter-netflix-eureka-server </artifactId> </dependency>
6.2.2 配置application.yml
## 目前服務的 port server : port : 9001 ## 目前應⽤名會作為服務唯⼀辨別注冊到 eureka spring : application : name : order-add datasource : driver-class-name : com.mysql.jdbc.Driver url : jdbc : mysql : //localhost : 3306/db_2010_sc?characterEncoding=utf-8 username : root password : admin123 mybatis : mapper-locations : classpath : mappers/* type-aliases-package : com.qfedu.order.beans ## 配置 Eureka 服務注冊與發現中⼼的位址 eureka : client : service-url : defaultZone : http : //localhost : 8761/eureka
6.2.3 在目前服務應⽤的啟動類添加 @EnableEurekaClient 注解
@SpringBootApplication
@MapperScan("com.qfedu.order.dao")
@EnableEurekaClient
public class OrderAddApplication {
public static void main(String[] args) {
SpringApplication.run(OrderAddApplication.class, args);
}
}
七、服務發現-Ribbon
服務消費者( api-order-add )通過 eureka 查找服務提供者( order-add ) , 通過服務調⽤ 元件調⽤提供者
- eureka server
- ribbon
7.1 基礎配置
Ribbon 用戶端已經停更進維啦
7.1.1 建立SpringBoot應⽤,添加依賴
- eureka server
- ribbon
7.1.2 配置application.yml
server : port : 8001 spring : application : name : api-order-add eureka : client : service-url : defaultZone : http : //localhost : 8761/eureka
7.1.3 在啟動類添加 @EnableDiscoveryClient 注解
@SpringBootApplication
@EnableDiscoveryClient
public class ApiOrderAddApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddApplication.class, args);
}
}
7.2 服務調⽤
7.2.1 配置RestTemplate
@Configuration
public class AppConfig {
@LoadBalanced //啟⽤Ribbon(負載均衡)
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
7.2.2 在Service中注⼊RestTemplate對象調⽤服務
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private RestTemplate restTemplate;
@Override
public ResultVO saveOrder(Order order) {
//1. 調⽤ order-add服務進⾏儲存
ResultVO vo = restTemplate.postForObject("http://orderadd/order/add", order, ResultVO.class);
//2. 調⽤ orderitem-add 儲存訂單快照
//3. 調⽤ stock-update 修改商品庫存
//4. 調⽤ shopcart-del 删除購物⻋記錄
return null;
}
}
7.3 案例流程圖
服務注冊與發現-案例流程圖
7.4 Ribbon服務調⽤說明
@LoadBalanced 注解是 Ribbon 的⼊⼝,在 RestTemplate 對象上添加此注解之後,再使 ⽤ RestTemplate 發送 REST 請求的時候,就可以通過 Ribbon 根據服務名稱從 Eureka 中查 找服務對應的通路位址清單,再根據負載均衡政策(預設輪詢)選擇其中的⼀個,然後 完成服務的調⽤
- 擷取服務清單
- 根據負載均衡政策選擇服務
- 完成服務調⽤
⼋、基于Ribbon進⾏服務調⽤的參數傳遞
8.1 RestTemplate發送調⽤請求的⽅法
SpringCloud 的服務調⽤是基于 REST 的,是以當服務提供者規定了請求的⽅式,服務消 費者必須發送對應⽅式的請求才能完成服務的調⽤, RestTemplate 提供了多個⽅法⽤于 發送不同形式的請求
//post⽅式請求
restTemplate.postForObject();
//get⽅式請求
restTemplate.getForObject();
//delete⽅式請求
restTemplate.delete();
//put⽅式請求
restTemplate.put();
8.2 put/post請求傳參
- 服務消費者請求傳參
//參數1:通路服務的url
//參數2:傳遞的對象參數
//參數3:指定服務提供者傳回的資料類型
ResultVO vo = restTemplate.postForObject("http://order-add/order/add",
order, ResultVO.class);
- 服務提供者接收參數
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order){
return orderService.saveOrder(order);
}
8.3 get請求傳參
- 服務消費者請求傳參
String userId = order.getUserId();
ResultVO vo = restTemplate.getForObject("http://order-add/order/add?
userId="+userId, ResultVO.class);
- 服務提供者接收參數
@GetMapping("/add")
public ResultVO addOrder(Order order){
return orderService.saveOrder(order);
}
@GetMapping("/add")
public ResultVO addOrder(String userId){
//return orderService.saveOrder(order);
}
九、服務發現-Feign
9.1 基礎配置
9.1.1 建立SpringBoot應⽤,添加依賴
- spring web
- eureka server
- OpenFeign
9.1.2 配置application.yml
server : port : 8002 spring : application : name : api-order-add-feign eureka : client : service-url : defaultZone : http : //localhost : 8761/eureka
9.1.3 在啟動類添加注解
@SpringBootApplication
@EnableDiscoveryClient //聲明為服務消費者
@EnableFeignClients //聲明啟⽤feign用戶端
public class ApiOrderAddFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddFeignApplication.class, args);
}
}
9.2 服務調⽤
使⽤ Feign 進⾏服務調⽤的時候,需要⼿動建立⼀個服務通路用戶端(接⼝)
9.2.1 建立Feign用戶端
@FeignClient("order-add")
public interface OrderAddClient {
@PostMapping("order/add")
public ResultVO addOrder(Order order);
}
9.2.2 使⽤Feign用戶端調⽤服務
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private OrderAddClient orderAddClient;
@Override
public ResultVO saveOrder(Order order) {
//1. 調⽤ order-add服務進⾏儲存
ResultVO vo = orderAddClient.addOrder(order);
//2. 調⽤ orderitem-add 儲存訂單快照
//3. 調⽤ stock-update 修改商品庫存
//4. 調⽤ shopcart-del 删除購物⻋記錄
return vo;
}
}
9.3 Feign傳參
9.3.1 POST請求
- 通過請求體傳遞對象
- 服務提供者
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order){
System.out.println("-------------------order-add");
System.out.println(order);
return orderService.saveOrder(order);
}
-
- 服務消費者(Feign用戶端)
@FeignClient("order-add")
public interface OrderAddClient {
@PostMapping("order/add")
public ResultVO addOrder(Order order);
} 1234567
- 通過請求⾏傳參
- 服務提供者
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order,String str){
System.out.println("-------------------order-add");
System.out.println(order);
System.out.println(str);
return orderService.saveOrder(order);
}
-
- 服務消費者(Feign用戶端)
//1.對⽤POST請求調⽤服務,Feign用戶端的⽅法參數預設為body傳值(body隻能有⼀個
值)
//2.如果有多個參數,則需要通過@RequestParam聲明參數為請求⾏傳值
@PostMapping("order/add")
public ResultVO addOrder(Order order,@RequestParam("str") String
str);
9.3.2 Get請求
Get 請求調⽤服務,隻能通過 url 傳參 在 Feign 用戶端的⽅法中,如果不指定參數的傳值⽅式,則預設為 body 傳參, Get 請求也 不例外;是以對于 get 請求傳遞參數,必須通過 @RequestParam 注解聲明
- 服務提供者
@GetMapping("/get")
public Order addOrder(String orderId){
return new Order();
}
- 服務消費者(Feign用戶端)
@GetMapping("order/get")
public Order getOrder(@RequestParam("orderId") String orderId);
⼗、服務注冊與發現中⼼的可靠性和安全性
10.1 可靠性
在微服務架構系統中,服務消費者是通過服務注冊與發現中⼼發現服務、調⽤服務的, 服務注冊與發現中⼼伺服器⼀旦挂掉,将會導緻整個微服務架構系統的崩潰,如何保證 Eureka 的可靠性呢?
- 使⽤eureka叢集
Eureka 叢集搭建 互相注冊、互相發現
## 設定服務注冊與發現中⼼的端⼝ server : port : 8761 ## 在微服務架構中,服務注冊中⼼是通過服務應⽤的名稱來區分每個服務的 ## 我們在建立每個服務之後,指定目前服務的 應⽤名 / 項⽬名 spring : application : name : service-eureka eureka : client : ## 設定服務注冊與發現中⼼是否為叢集搭建 register-with-eureka : true ## 設定服務注冊與發現中是否作為服務進⾏注冊 fetch-registry : true ## ip 就是服務注冊中⼼伺服器的 ip ## port 就是服務注冊與發現中⼼設定的 port service-url : defaultZone : http : //192.168.54.10 : 8761/eureka
10.2 安全性
當完成 Eureka 的搭建之後,隻要知道 ip 和 port 就可以随意的注冊服務、調⽤服務,這是 不安全的,我們可以通過設定帳号和密碼來限制服務的注冊及發現。 在 eureka 中整合 Spring Security 安全架構實作帳号和密碼驗證
10.2.1 添加SpringSecurity的依賴
<dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-security </artifactId> </dependency>
10.2.2 設定通路 eureka 的帳号和密碼
spring : security : user : name : zhangsan password : 123456
10.2.3 配置Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
//設定目前伺服器的所有請求都要使⽤spring security的認證
http.authorizeRequests().anyRequest().authenticated().and().httpBasic(
);
}
}
10.2.4 服務提供者和服務消費者連接配接到注冊中⼼都要帳号和密碼
eureka : client : service-url : defaultZone : http : //zhangsan : [email protected] : 8761/eureka
⼗⼀、熔斷器-Hystrix
服務故障的雪崩效應:當 A 服務調⽤ B 服務時,由于 B 服務的故障導緻 A 服務處于阻塞狀 态,當量的請求可能會導緻 A 服務因資源耗盡⽽出現故障。 為了解決服務故障的雪崩效應,出現了熔斷器模型。
11.1 熔斷器介紹
熔斷器作⽤ :
- 服務降級 :⽤戶請求A服務,A服務調⽤B服務,當B服務出現故障或者在特定的時間段内 不能給A服務響應,為了避免A服務因等待B服務⽽産⽣阻塞,A服務就不等B服務的結果 了,直接給⽤戶⼀個降級響應
- 服務熔斷 :⽤戶請求A服務,A服務調⽤B服務,當B服務出現故障的頻率過⾼達到特定阈 值(5s 20次)時,當⽤戶再請求A服務時,A服務将不再調⽤B服務,直接給⽤戶⼀個降級響應
11.2 熔斷器的原理
11.3 基于Ribbon服務調⽤的熔斷器使⽤
11.3.1 服務消費者的 服務降級
- 添加熔斷器依賴 hystrix
<dependency> <groupId> org.springframework.cloud </groupId> <artifactId> spring-cloud-starter-netflix-hystrix </artifactId> </dependency>
- 在啟動類添加 @EnableHystrix 注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class ApiOrderAddApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddApplication.class, args);
}
}
- 在調⽤服務提供者的業務處理⽅法中,進⾏降級配置
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod =
"fallbackSaveOrder",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds
",value="3000")
})
public ResultVO saveOrder(Order order) {
//1. 調⽤ order-add服務進⾏儲存
//參數1:通路服務的url
//參數2:傳遞的對象參數
//參數3:指定服務提供者傳回的資料類型
ResultVO vo =
restTemplate.postForObject("http://order-add/order/add",
order, ResultVO.class);
System.out.println(vo);
//2. 調⽤ orderitem-add 儲存訂單快照
//3. 調⽤ stock-update 修改商品庫存
//4. 調⽤ shopcart-del 删除購物⻋記錄
return vo;
}
/**
* 降級⽅法:與業務⽅法擁有相同的參數和傳回值
* @return
*/
public ResultVO fallbackSaveOrder(Order order){
return ResultVO.fail("⽹絡異常,請重試!",null);
}
}
11.3.2 服務提供者的 服務降級
- 配置步驟⼀緻
- 服務提供者接⼝降級
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@HystrixCommand(fallbackMethod =
"fallbackAddOrder",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMillisecond
s",value="3000")
})
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order){
System.out.println("-------------------order-add");
System.out.println(order);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return orderService.saveOrder(order);
}
public ResultVO fallbackAddOrder(@RequestBody Order order){
System.out.println("-------------------order-add--fallback");
return ResultVO.fail("訂單儲存失敗!",null);
}
}
11.3.3 服務熔斷配置
- 熔斷器狀态:閉合、打開、半開
- 服務熔斷配置
@HystrixCommand(fallbackMethod =
"fallbackSaveOrder",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMillise
conds",value="3000"),
@HystrixProperty(name="circuitBreaker.enabled",value="true"),
//啟⽤服務熔斷
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",v
alue="10000"),//時間
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",valu
e="10"),//請求次數
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",va
lue="50"),//服務錯誤率
})
public ResultVO saveOrder(Order order) {
//1. 調⽤ order-add服務進⾏儲存
ResultVO vo = restTemplate.postForObject("http://orderadd/order/add", order, ResultVO.class);
System.out.println(vo);
//2. 調⽤ orderitem-add 儲存訂單快照
//3. 調⽤ stock-update 修改商品庫存
//4. 調⽤ shopcart-del 删除購物⻋記錄
return vo; }
/**
* 降級⽅法:與業務⽅法擁有相同的參數和傳回值
* @return
*/
public ResultVO fallbackSaveOrder(Order order){
return ResultVO.fail("⽹絡異常,請重試!",null);
}
服務熔斷:當⽤戶請求服務 A ,服務 A 調⽤服務 B 時,如果服務 B 的故障率達到特定的 門檻值時,熔斷器就會被打開⼀個時間周期(預設 5s ,可⾃定義),在這個時間周期 内如果⽤戶請求服務 A ,服務 A 将不再調⽤服務 B ,⽽是直接響應降級服務。
11.4 基于Feign服務調⽤的熔斷器使⽤
Feign 是基于 Ribbon 和 Hystrix 的封裝
11.4.1 Feign中的熔斷器使⽤
- 添加依賴
SpringBoot 2.3.11 Spring Cloud H
<parent> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-parent </artifactId> <version> 2.3.11.RELEASE </version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version> 1.8 </java.version> <spring-cloud.version> Hoxton.SR11 </spring-cloud.version> </properties>
<dependency> <groupId> org.springframework.cloud </groupId> <artifactId> spring-cloud-starter-netflix-hystrix </artifactId> </dependency>
- 在application.yml啟⽤熔斷器機制
feign : hystrix : enabled : true
- 在啟動類添加 @EnableHystrix
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class ApiOrderAddFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddFeignApplication.class,
args);
}
}
- 建立服務降級處理類
FeignClient的服務降級類: 1. 必須實作 Feign 用戶端接⼝, 2. 必須交給 Spring 容器管理
@Component
public class OrderAddClientFallback implements OrderAddClient {
public ResultVO addOrder(Order order, String str) {
System.out.println("-------addOrder的降級服務");
return ResultVO.fail("fail",null);
}
public Order getOrder(String orderId) {
System.out.println("-------getOrder的降級服務");
return new Order();
}
}
- 在Feign用戶端指定降級處理類
@FeignClient(value = "order-add", fallback =
OrderAddClientFallback.class)
public interface OrderAddClient {
//1.對⽤POST請求調⽤服務,Feign用戶端的⽅法參數預設為body傳值(body隻能
有⼀個值)
//2.如果有多個參數,則需要通過@RequestParam聲明參數為請求⾏傳值
@PostMapping("order/add")
public ResultVO addOrder(Order order,@RequestParam("str")
String str);
@GetMapping("order/get")
public Order getOrder(@RequestParam("orderId") String orderId);
}
- Service類通過Feign用戶端調⽤服務
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private OrderAddClient orderAddClient;
//當我們建立Feign用戶端的降級類并交給Spring管理後 在Spring容器中就會出現
兩個OrderAddClient對象
@Override
public ResultVO saveOrder(Order order) {
//1. 調⽤ order-add服務進⾏儲存
ResultVO vo = orderAddClient.addOrder(order,"測試字元串");
Order order1 = orderAddClient.getOrder("訂單編号");
System.out.println(order1);
//2. 調⽤ orderitem-add 儲存訂單快照
//3. 調⽤ stock-update 修改商品庫存
//4. 調⽤ shopcart-del 删除購物⻋記錄
return vo;
}
}
11.4.2 Ribbon 參數配置
ribbon : ## Ribbon 建⽴連接配接最⼤等待時間 ConnectTimeout : 1000 ## 在目前服務提供者嘗試連接配接次數 MaxAutoRetries : 2 ## 與服務提供者通信時間 ReadTimeout : 5000 ## 設定熔斷器服務降級時間 (預設 1000 ) hystrix : command : default : execution : isolation : thread : timeoutInMilliseconds : 8000
11.5 熔斷器儀表盤監控
如果伺服器的并發壓⼒過⼤、伺服器⽆法正常響應,則熔斷器狀态變為 open 屬于正常的 情況;但是如果⼀個熔斷器⼀直處于 open 狀态、或者說伺服器提供者沒有通路壓⼒的情 況下熔斷器依然為 open 狀态,說明熔斷器的狀态就不屬于正常情況了。如何了解熔斷器 的⼯作狀态呢 ?
- 熔斷器儀表盤
11.5.1 搭建熔斷器儀表盤
- 建立SpringBoot項⽬,添加依賴
<dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-actuator </artifactId> </dependency> <dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-web </artifactId> </dependency> <dependency> <groupId> org.springframework.cloud </groupId> <artifactId> spring-cloud-starter-netflix-hystrix dashboard </artifactId> </dependency>
- 配置儀表盤的port和appName
server : port : 9999 spring : application : name : hystrix-dashboard hystrix : dashboard : proxy-stream-allow-list : "localhost"
- 啟動類添加 @EanbleHystrixDashboard 注解
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class,
args);
}
}
- 通路 http://localhost:9999/hystrix
11.5.2 配置使⽤了熔斷器的服務可被監控
- 添加依賴
<dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-actuator </artifactId> </dependency>
- 配置
@Configuration
public class DashBoardConfig {
@Bean
public ServletRegistrationBean getServletRegistrationBean(){
HystrixMetricsStreamServlet streamServlet = new
HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean =
new ServletRegistrationBean(streamServlet);
registrationBean.setName("HystrixMetricsStreamServlet");
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
return registrationBean;
}
}
- 檢視指定服務的熔斷器⼯作參數
⼗⼆、微服務拆分
⼗三、服務鍊路追蹤
13.1 服務追蹤說明
- 微服務架構是通過業務來劃分服務的,使⽤REST調⽤。對外暴露的⼀個接⼝,可能需要 很多個服務協同才能完成這個接⼝功能,如果鍊路上任何⼀個服務出現問題或者⽹絡超 時,都會形成導緻接⼝調⽤失敗。
- 随着業務的不斷擴張,服務之間互相調⽤會越來越複雜,它們之間的調⽤關系也許如下:
- 随着服務的越來越多,對調⽤鍊的分析會越來越複雜。
13.2 Zipkin
- ZipKin是⼀個開放源代碼的分布式跟蹤系統,由Twitter公司開源,它緻⼒于收集服務的 定時資料,以解決微服務架構中的延遲問題,包括資料的收集、存儲、查找和展現。它 的理論模型來⾃于 Google Dapper 論⽂。
- 每個服務向 ZipKin 報告計時資料,ZipKin 會根據調⽤關系通過 ZipKin UI ⽣成依賴關系 圖,顯示了多少跟蹤請求通過每個服務,該系統讓開發者可通過⼀個 Web 前端輕松的收 集和分析資料,例如⽤戶每次請求服務的處理時間等,可⽅便的監測系統中存在的瓶 頸。
13.3 搭建zipkin伺服器
- 建立SpringBoot項⽬(版本2.1.x)
- 添加依賴
<dependency> <groupId> io.zipkin.java </groupId> <artifactId> zipkin-server </artifactId> <version> 2.11.10 </version> </dependency> <!--zipkin 界⾯ --> <dependency> <groupId> io.zipkin.java </groupId> <artifactId> zipkin-autoconfigure-ui </artifactId> <version> 2.11.10 </version> </dependency>
- 在啟動類添加 @EnableZipkinServer 注解
@SpringBootApplication
@EnableZipkinServer
public class ZipkinApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinApplication.class, args);
}
}
- 配置yml
spring : application : name : zipkin server : port : 9411 management : endpoints.web.exposure.include : '*' metrics.web.server.auto-time-requests : false
13.4 服務中Sleuth配置
- 在服務應⽤中添加Sleuth依賴
<!-- spring-cloud-sleuth-zipkin --> <dependency> <groupId> org.springframework.cloud </groupId> <artifactId> spring-cloud-sleuth-zipkin </artifactId> <version> 2.0.2.RELEASE </version> </dependency>
- 在服務應⽤中配置yml
spring : application : name : goods-provider zipkin : enabled : true base-url : http : //localhost : 9411 sleuth : sampler : probability : 0.1
13.5 zipkin服務資料存儲
- 建立資料庫資料表
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT "Span.timestamp(): epoch micros used
for endTs query and to implement TTL",
`duration` BIGINT COMMENT "Span.duration(): micros used for
minDuration and maxDuration query"
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id`, `id`) COMMENT
"ignore insert on duplicate";
ALTER TABLE zipkin_spans ADD INDEX(`trace_id`, `id`) COMMENT "for
joining with zipkin_annotations";
ALTER TABLE zipkin_spans ADD INDEX(`trace_id`) COMMENT "for
getTracesByIds";
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT "for getTraces
and getSpanNames";
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT "for
getTraces ordering and range";
CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id` BIGINT NOT NULL COMMENT "coincides with
zipkin_spans.trace_id",
`span_id` BIGINT NOT NULL COMMENT "coincides with
zipkin_spans.id",
`a_key` VARCHAR(255) NOT NULL COMMENT "BinaryAnnotation.key or
Annotation.value if type == -1",
`a_value` BLOB COMMENT "BinaryAnnotation.value(), which must be
smaller than 64KB",
`a_type` INT NOT NULL COMMENT "BinaryAnnotation.type() or -1 if
Annotation",
`a_timestamp` BIGINT COMMENT "Used to implement TTL;
Annotation.timestamp or zipkin_spans.timestamp",
`endpoint_ipv4` INT COMMENT "Null when
Binary/Annotation.endpoint is null",
`endpoint_ipv6` BINARY(16) COMMENT "Null when
Binary/Annotation.endpoint is null, or no IPv6 address",
`endpoint_port` SMALLINT COMMENT "Null when
Binary/Annotation.endpoint is null",
`endpoint_service_name` VARCHAR(255) COMMENT "Null when
Binary/Annotation.endpoint is null"
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id`,
`span_id`, `a_key`, `a_timestamp`) COMMENT "Ignore insert on
duplicate";
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`)
COMMENT "for joining with zipkin_spans";
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`) COMMENT "for
getTraces/ByIds";
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`)
COMMENT "for getTraces and getServiceNames";
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT "for
getTraces";
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT "for
getTraces";
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`,
`child`);
- pom依賴
<!-- zipkin-storage-mysql-v1 --> <dependency> <groupId> io.zipkin.zipkin2 </groupId> <artifactId> zipkin-storage-mysql-v1 </artifactId> <version> 2.11.12 </version> </dependency> <!--mysql 驅動 --> <dependency> <groupId> mysql </groupId> <artifactId> mysql-connector-java </artifactId> <version> 5.1.47 </version> </dependency>
- 配置yml
spring : application : name : zipkin datasource : username : root password : admin123 driver-class-name : com.mysql.jdbc.Driver url : jdbc : mysql : //localhost : 3306/zipkin zipkin : storage : type : mysql