Mybatis-終極版-01
http://localhost:8001/customer/30[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zpn6SWDb-1607759535247)(Pictures/1587880250279.png)]
Author:Eric
Version:9.0.0
文章目錄
一、SpringCloud介紹
1.1 微服務架構
微服務架構的提出者:馬丁福勒
https://martinfowler.com/articles/microservices.html
簡而言之,微服務架構樣式[1]是一種將單個應用程式開發為一組小服務的方法,每個小服務都在自己的程序中執行並與輕量級機制(通常是HTTP資源API)進行通訊。這些服務圍繞業務功能構建,並且可以由全自動部署機制獨立部署。這些服務的集中管理幾乎沒有,它可以用不同的程式語言編寫並使用不同的資料儲存技術。
1、 微服務架構只是一個樣式,一個風格。
2、 將一個完成的專案,拆分成多個模組去分別開發。
3、 每一個模組都是單獨的執行在自己的容器中。
4、 每一個模組都是需要相互通訊的。 Http,RPC,MQ。
5、 每一個模組之間是沒有依賴關係的,單獨的部署。
6、 可以使用多種語言去開發不同的模組。
7、 使用MySQL資料庫,Redis,ES去儲存資料,也可以使用多個MySQL資料庫。
總結:將複雜臃腫的單體應用進行細粒度的劃分,每個拆分出來的服務各自打包部署。
1.2 SpringCloud介紹
SpringCloud是微服務架構落地的一套技術棧。
SpringCloud中的大多數技術都是基於Netflix公司的技術進行二次研發。
SpringCloud的中文社群網站:http://springcloud.cn/
SpringCloud的中文網:http://springcloud.cc/
八個技術點:
- Eureka - 服務的註冊與發現
- Robbin - 服務之間的負載均衡
- Feign - 服務之間的通訊
- Hystrix - 服務的執行緒隔離以及斷路器
- Zuul - 服務閘道器
- Stream - 實現MQ的使用
- Config - 動態配置
- Sleuth - 服務追蹤
二、服務的註冊與發現-Eureka【重點
】
2.1 引言
Eureka就是幫助我們維護所有服務的資訊,以便服務之間的相互呼叫
Eureka |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-T4B8Cb5e-1607759535255)(Pictures/1587887319057.png)] |
SpringBoot 2.2.9
SpringCloud Hoxton.SR4
2.2 Eureka的快速入門
2.2.1 建立一個父工程
建立一個父工程【springcloud-ghy】,並且在父工程中指定SpringCloud的版本,並且將packing修改為pom
除了pom.xml【idea配置,.gitignore】,其他都可以刪掉
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.2.2 建立EurekaServer【01-eureka-server】
建立eureka的server【01-eureka-server】
建立Maven工程,改成SpringBoot工程
- 匯入依賴
<!-- 1. 匯入eureka的服務 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2. 使用JBL SpringBootAppGen外掛生成引導類和配置檔案
啟動引導類添加註解 @EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer //代表當前應用是一個Eureak服務
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
編寫yml配置檔案
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
#當前服務要不要註冊到eureka
registerWithEureka: false
#拉取資訊
fetchRegistry: false
serviceUrl:
#eureka的服務地址
# http://localhost:10086/eureka/
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
測試
- 啟動引導類
- 瀏覽器訪問 http://localhost:10086,能夠看到一個美麗的介面【Eureka】就成功了
2.2.3 建立EurekaClient【02-search-service】
建立Maven工程【02-search-service】,修改為SpringBoot
匯入依賴
<!-- 1. 匯入eureka的客戶端起步依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在啟動引導類上添加註解 @EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient //標記當前服務是Eureka的客戶端
public class SearchApplication {
public static void main(String[] args) {
SpringApplication.run(SearchApplication.class, args);
}
}
編寫配置檔案
server:
port: 9001
spring:
application:
name: search-service
# 指定eurkea服務地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10086/eureka/
編寫SearchController
package com.qf.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author ghy
* @version 1.0
* @date 2020-09-09
**/
@RestController
@RequestMapping("/search")
public class SearchController {
@Value("${server.port}")
String port;
@RequestMapping("/test")
public String test() {
return "Hello Test " + port;
}
}
測試
- 先啟動 00-eureka-server
- 啟動引導類
- 瀏覽器重新整理 http://localhost:10086,能夠看到 search-service
- 瀏覽器訪問 http://localhost:9001/search/test,能夠看到效果,就代表成功
2.2.4 建立EurekaClient【03-customer-service】
匯入依賴
<!-- 1. 匯入eureka的客戶端起步依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在啟動引導類上添加註解 @EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class CustomerApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
編寫配置檔案
server:
port: 8001
spring:
application:
name: customer-service
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10086/eureka/
編寫SearchController
package com.ghy.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author ghy
* @version 1.0
* @date 2020-09-09
**/
@RestController
@RequestMapping("/customer")
public class CustomerController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/test")
public String test(){
String result = restTemplate.getForObject("http://localhost:9001/search/test", String.class);
return result;
}
}
測試
- 先啟動 01-eureka-server
- 啟動引導類
- 瀏覽器重新整理 http://localhost:10086,能夠看到 customer-service
- 瀏覽器訪問 http://localhost:9001/customer/test,能夠看到效果,就代表成功
2.2.5 使用到EurekaClient的物件去獲取服務資訊
使用到EurekaClient的物件去獲取服務資訊
在 03-customer-service 工程的 CustomerController中注入
@Autowired
private EurekaClient eurekaClient;
正常RestTemplate呼叫即可
@GetMapping("/test")
public String test(){
InstanceInfo instanceInfo = eurekaClient.getNextServerFromEureka("SEARCH-SERVICE", false);
String uri = instanceInfo.getHomePageUrl();
//http://localhost:9001/
System.out.println("uri-> " + uri);
String result = restTemplate.getForObject(uri + "/search/test", String.class);
return result;
}
2.3 Eureka的安全性
01-eureka-server
實現Eureka認證
匯入依賴
<!-- eureka安全 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
編寫配置類
package com.qf.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author ghy
* @version 1.0
* @date 2020-09-10
**/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//忽略 /eureka/** 路徑
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
編寫配置檔案
# 指定使用者名稱和密碼
spring:
application:
name: eureka-server
security:
user:
name: root
password: root
問題:
當啟動 Eureka的客戶端時,出錯了
原因是因為當前Eureka已經開啟安全了,那麼註冊到Eureka的服務也是需要提供認證的【使用者名稱跟密碼】
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
解決方案:
其他服務想註冊到Eureka上需要新增使用者名稱和密碼。【只要是註冊到Eureka中的服務都需要配置認證資訊】
# 指定eurkea服務地址
eureka:
client:
serviceUrl:
#defaultZone: http://localhost:10086/eureka/
defaultZone: http://root:[email protected]:10086/eureka/
在我們開發過程中,是很少啟動認證的,因為每次重啟都需要登入,太麻煩。
為了方便寫程式碼,我們去掉認證
01-eureka-server
- 註釋安全依賴
- 把類刪掉
- 把配置檔案中使用者名稱密碼相關資訊註釋掉
02-search-service 跟 03-customer-service
把 defaultZone 改成原來的【不需要使用者名稱跟密碼】
當前springcloud應用過多,不方便檢視
開啟 dashboard
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-uSxa2m5l-1607759535258)(Pictures\image-20200910114738335.png)]
2.4 Eureka的高可用
如果程式正在執行,突然Eureka宕機了。
如果呼叫方訪問過一次被呼叫方了,因為本地快取了被呼叫方的資訊,Eureka的宕機不會影響到功能。
如果呼叫方沒有訪問過被呼叫方,Eureka的宕機就會造成當前功能不可用。
2.4.1 搭建兩個Eureka
修改01-eureka-server的application.yml
server:
port: ${port:10086}
spring:
application:
name: eureka-server
#叢集
eureka:
instance:
hostname: localhost
client:
#如果是叢集,則需要相互註冊
rgisterWithEureka: true
#拉取資訊
fetchRegistry: true
serviceUrl:
#需要把自己註冊到對方的地址
defaultZone: ${defaultZone:http://localhost:10087/eureka/}
在 idea 的spring-boot應用配置介面上,複製一個 EureakApplication 啟動項,改名為 EureakApplication 10087;並且在面板上啟動引數
參考下圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9kz6WaKC-1607759535262)(C:\Users\ghy\AppData\Roaming\Typora\typora-user-images\image-20200910141107891.png)]
2.4.2 修改EurekaClient的application.yml檔案,讓其註冊到兩個Eureka中。
customer-service
search-service
# 指定eurkea服務地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10086/eureka/,http://localhost:10087/eureka/
2.4.3 測試
啟動 02-search-service,03-customer-service 服務
瀏覽器上重新整理eureka,就能夠看到兩個Eureka上都有了02-search-service,03-customer-service這兩個服務。
如果沒有同步,則可以選擇重新啟兩個Eureka,或者等會,Eureka節點需要時間同步資料。
2.5 Eureka的細節
EurekaClient啟動是,將自己的資訊註冊到EurekaServer上,EurekaSever就會儲存上EurekaClient的註冊資訊。
當EurekaClient呼叫服務時,本地沒有註冊資訊的快取時,去EurekaServer中去獲取註冊資訊。
EurekaClient會通過心跳的方式去和EurekaServer進行連線。(預設30s EurekaClient會發送一次心跳請求,如果超過了90s還沒有傳送心跳資訊的話,EurekaServer就認為你宕機了,將當前EurekaClient從登錄檔中移除)
eureka:
instance:
lease-renewal-interval-in-seconds: 30 #心跳的間隔
lease-expiration-duration-in-seconds: 90 # 多久沒傳送,就認為你宕機了
EurekaClient會每隔30s去EurekaServer中去更新本地的登錄檔
eureka:
client:
registry-fetch-interval-seconds: 30 # 每隔多久去更新一下本地的登錄檔快取資訊
Eureka的自我保護機制,統計15分鐘內,如果一個服務的心跳傳送比例低於85%,EurekaServer就會開啟自我保護機制
- 不會從EurekaServer中去移除長時間沒有收到心跳的服務。
- EurekaServer還是可以正常提供服務的。
- 網路比較穩定時,EurekaServer才會開始將自己的資訊被其他節點同步過去
這是觸發了Eureka的自我保護機制。當服務未按時進行心跳續約時,Eureka會統計服務例項最近15分鐘心跳續約的
比例是否低於了85%。在生產環境下,因為網路延遲等原因,心跳失敗例項的比例很有可能超標,但是此時就把服務
剔除列表並不妥當,因為服務可能沒有宕機。Eureka在這段時間內不會剔除任何服務例項,直到網路恢復正常。生
產環境下這很有效,保證了大多數服務依然可用,不過也有可能獲取到失敗的服務例項,因此服務呼叫者必須做好服
務的失敗容錯。
eureka:
server:
enable-self-preservation: true # 開啟自我保護機制
*** (面試可能有)CAP定理,C - 一致性,A-可用性,P-分割槽容錯性,這三個特性在分散式環境下,只能滿足2個,而且分割槽容錯性在分散式環境下,是必須要滿足的,只能在AC之間進行權衡。
如果選擇CP,保證了一致性,可能會造成你係統在一定時間內是不可用的,如果你同步資料的時間比較長,造成的損失大。
Eureka就是一個AP的效果,高可用的叢集,Eureka叢集是無中心,Eureka即便宕機幾個也不會影響系統的使用,不需要重新的去推舉一個master,也會導致一定時間內資料是不一致。
三、服務間的負載均衡-Robbin【重點
】
3.1 引言
Robbin是幫助我們實現服務和服務負載均衡,Robbin屬於客戶端負載均衡
客戶端負載均衡:customer客戶模組,將2個Search模組資訊全部拉取到本地的快取,在customer中自己做一個負載均衡的策略,選中某一個服務。
服務端負載均衡:在註冊中心中,直接根據你指定的負載均衡策略,幫你選中一個指定的服務資訊,並返回。
Robbin |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MfGWI0lD-1607759535265)(Pictures/1587977965492.png)] |
3.2 Robbin的快速入門
啟動兩個search模組
複製一個search模組的啟動項,啟動時加上埠號即可
在customer匯入robbin依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
配置整合RestTemplate和Robbin
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
在customer中去訪問search
@GetMapping("/test")
public String test(){
//使用ribbon來實現負載均衡時,需要通過服務名去訪問。
//預設的負載均衡策略為輪詢
String result = restTemplate.getForObject("http://SEARCH-SERVICE/search/test", String.class);
return result;
}
3.3 Robbin配置負載均衡策略
負載均衡策略
- RandomRule:隨機策略
- RoundRobbinRule:輪詢策略
- WeightedResponseTimeRule:預設會採用輪詢的策略,後續會根據服務的響應時間,自動給你分配權重
- BestAvailableRule:根據被呼叫方併發數最小的去分配
採用註解的形式【全域性】
@Bean
public IRule robbinRule(){
return new RandomRule();
}
配置檔案去指定負載均衡的策略(推薦)
# 指定具體服務的負載均衡策略
SEARCH-SERVICE: # 編寫服務名稱
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule # 具體負載均衡使用的類
註釋IRule對應的配置Bean
/*@Bean
public IRule robbinRule(){
return new RandomRule();
}*/
四、服務間的呼叫-Feign【重點
】
4.1 引言
Feign可以幫助我們實現面向介面程式設計,就直接呼叫其他的服務,簡化開發。
4.2 Feign的快速入門
匯入依賴
<!-- 匯入openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
引導類新增一個註解
@EnableFeignClients
建立一個介面,並且和search-service模組做對映
package com.ghy.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author ghy
* @version 1.0
* @date 2020-09-10
**/
@FeignClient("SEARCH-SERVICE")
public interface SearchFeign {
/**
* 將來,這個方法被呼叫時,會通過動態代理生成代理物件去呼叫真正目標物件
* 在對映時,需要把value屬性加上
* @return
*/
@RequestMapping(value = "/search/test")
String test();
}
CustomerController
@Autowired
SearchFeign searchFeign;
@GetMapping("/test")
public String test(){
System.out.println("CustomerController test 。。。。");
String result = searchFeign.test();
return result;
}
測試
瀏覽器訪問 http://localhost:8001/customer/test ,能夠正確呼叫到 search-service 模組的Controller介面的具體方法
4.3 Feign的傳遞引數方式
注意事項
- 如果你傳遞的引數,比較複雜時,預設會採用POST的請求方式。
- 傳遞單個引數時,推薦使用@PathVariable,如果傳遞的單個引數比較多,這裡也可以採用@RequestParam,不要省略value屬性
- 傳遞物件資訊時,統一採用json的方式,新增@RequestBody
- Client介面必須採用@RequestMapping
4.3.1 search-service 模組和customer-service模組
引入 lombok依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
search-service 模組和customer-service模組準備Customer實體類
package com.ghy.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author ghy
* @version 1.0
* @date 2020-09-10
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
private Integer id;
private String name;
private Integer age;
}
在Search模組下準備三個介面
package com.qf.controller;
import com.qf.entity.Customer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
/**
* @author ghy
* @version 1.0
* @date 2020-09-09
**/
@RestController
@RequestMapping("/search")
public class SearchController {
//========================================
//引數的傳遞
@GetMapping("/{id}")
public Customer findById(@PathVariable Integer id) {
return new Customer(id, "@PathVariable", 1);
}
@GetMapping("/get")
public Customer get(@RequestParam("id") Integer id,
@RequestParam("name") String name,
@RequestParam("age") Integer age) {
return new Customer(id, name, 2);
}
@PostMapping("/save")
public Customer save(@RequestBody Customer customer) {
return customer;
}
//========================================
}
在customer-service 模組的feign介面中增加如下方法
package com.ghy.feign;
import com.ghy.entity.Customer;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* @author ghy
* @version 1.0
* @date 2020-09-10
**/
@FeignClient("SEARCH-SERVICE")
public interface SearchFeign {
@GetMapping("/search/{id}")
Customer findById(@PathVariable Integer id);
@GetMapping("/search/get")
Customer get(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name") String name,
@RequestParam(value = "age") Integer age);
@PostMapping("/search/save")
Customer save(@RequestBody Customer customer);
}
在customer-service 模組的Controller中增加呼叫feign介面中的方法
package com.ghy.controller;
import com.ghy.entity.Customer;
import com.ghy.feign.SearchFeign;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.converters.Auto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.nio.file.FileAlreadyExistsException;
/**
* @author ghy
* @version 1.0
* @date 2020-09-09
**/
@RestController
@RequestMapping("/customer")
public class CustomerController {
@Autowired
SearchFeign searchFeign;
//========================================
//引數的傳遞
@GetMapping("/{id}")
public Customer findById(@PathVariable Integer id) {
System.out.println("CustomerController findById ....");
return searchFeign.findById(id);
}
@GetMapping("/get")
public Customer get(@RequestParam("id") Integer id,
@RequestParam("name") String name,
@RequestParam("age") Integer age) {
System.out.println("CustomerController get ....");
return searchFeign.get(id,name,age);
}
@PostMapping("/save")
public Customer save(@RequestBody Customer customer) {
System.out.println("CustomerController save ....");
return searchFeign.save(customer);
}
//========================================
}
測試
4.4 Feign的Fallback
4.4.0 引言
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wHSaRfkR-1607759535267)(Pictures\image-20200911111247662.png)]
Fallback可以幫助我們在使用Feign去呼叫另外一個服務時,如果出現了問題,走服務降級,返回一個錯誤資料,避免功能因為一個服務出現問題,全部失效。
4.4.1 FallBack方式
建立一個POJO類,實現SearchFeign介面,重寫其抽象方法,並注入到Spring容器中
package com.ghy.feign.fallback;
import com.ghy.entity.Customer;
import com.ghy.feign.SearchFeign;
import org.springframework.stereotype.Component;
/**
* @author ghy
* @version 1.0
* @date 2020-09-11
**/
@Component
public class SearchFeignFallback implements SearchFeign {
@Override
public String test() {
return "feign的服務降級成功了";
}
@Override
public Customer findById(Integer id) {
return null;
}
@Override
public Customer get(Integer id, String name, Integer age) {
return null;
}
@Override
public Customer save(Customer customer) {
return null;
}
}
修改Feign介面中的註解,新增一個屬性。
@FeignClient(value = "SEARCH-SERVICE", fallback = SearchFeignFallback.class)
public interface SearchFeign {}
新增一個配置檔案。【開啟feign跟hystrix的整合服務降級】
# feign和hystrix元件整合
feign:
hystrix:
enabled: true
讓搜尋模組【search-service】的test方法出現異常【模擬服務出問題了】
@RequestMapping("/test")
public String test() {
int i=1/0;
return "Hello Test " + port;
}
測試
訪問客戶模組的test介面 http://localhost:8001/customer/test
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-HggJmAz0-1607759535269)(Pictures\image-20200911105853736.png)]
4.4.2 FallBackFactory方式
呼叫方無法知道具體的錯誤資訊是什麼,通過FallBackFactory的方式去實現這個功能
FallBackFactory基於Fallback
建立一個POJO類,實現FallBackFactory<Feign介面實現類>,注入到Spring容器中,抽象方法中需要把Feign介面服務降級類物件返回
package com.ghy.feign.fallback.factory;
import com.ghy.feign.fallback.SearchFeignFallback;
import com.netflix.discovery.converters.Auto;
import feign.hystrix.FallbackFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author ghy
* @version 1.0
* @date 2020-09-11
**/
@Component
public class SearchFeignFallbackFactory implements FallbackFactory<SearchFeignFallback> {
@Autowired
SearchFeignFallback searchFeignFallback;
/**
* 獲取目標介面的問題資訊
* @param throwable
* @return
*/
@Override
public SearchFeignFallback create(Throwable throwable) {
//輸出異常堆疊資訊
throwable.printStackTrace();
return searchFeignFallback;
}
}
修改SearchFeign介面中的屬性
@FeignClient(value = "SEARCH-SERVICE", fallbackFactory= SearchFeignFallbackFactory.class)
測試
訪問客戶模組的test介面 http://localhost:8001/customer/test
頁面依舊是降級後結果,customer-service應用的控制檯輸出了異常資訊
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JdJ5MziK-1607759535271)(Pictures\image-20200911110943297.png)]
五、服務的隔離及斷路器-Hystrix【重點
】
5.1 引言
Hystrix |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-KbFAGLZL-1607759535274)(Pictures/1588044427589.png)] |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JwWWSQA0-1607759535275)(Pictures\image-20200911112725280.png)]
5.2 降級機制實現
匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
引導類新增hystrix的一個註解
@EnableCircuitBreaker
讓findById接口出問題
編寫他的降級方法【除方法名之外,其他都跟目標方法一致】
在目標方法中加上註解,實現服務降級【@HystrixCommand(fallbackMethod = “findByIdFallback”)】
@GetMapping("/{id}")
//當前降級服務的方法必須跟目標方法保持一致,除方法名之外
@HystrixCommand(fallbackMethod = "findByIdFallback")
public Customer findById(@PathVariable Integer id) {
System.out.println("CustomerController findById ....");
int i=1/0; //出問題了,希望走降級邏輯
return searchFeign.findById(id);
}
//服務降級的方法
public Customer findByIdFallback(Integer id) {
System.out.println("findById 出錯了,進入了降級邏輯....");
return new Customer(id, "hystrix服務降級成功",666);
}
5、 測試一下
測試編寫了降級邏輯的介面【findById】
http://localhost:8001/customer/30
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-tFQZNiEr-1607759535276)(Pictures\image-20200911113704966.png)] |
5.3 執行緒隔離
如果使用Tomcat的執行緒池去接收使用者的請求,使用當前執行緒去執行其他服務的功能,如果某一個服務出現了故障,導致tomcat的執行緒大量的堆積,導致Tomcat無法處理其他業務功能。
- Hystrix的執行緒池(預設),接收使用者請求採用tomcat的執行緒池,執行業務程式碼,呼叫其他服務時,採用Hystrix的執行緒池。
- 訊號量,使用的還是Tomcat的執行緒池,幫助我們去管理Tomcat的執行緒池。
Hystrix的執行緒池的配置
配置資訊 | name | value |
---|---|---|
執行緒隔離策略 | execution.isolation.strategy | THREAD |
指定超時時間 | execution.isolation.thread.timeoutInMilliseconds | 1000 |
是否開啟超時時間配置 | execution.timeout.enabled | true |
超時之後是否中斷執行緒 | execution.isolation.thread.interruptOnTimeout | true |
取消任務後是否中斷執行緒 | execution.isolation.thread.interruptOnCancel | false |
程式碼實現
@HystrixProperty 具體的屬性參考 HystrixCommandProperties類中的屬性
//---------------------------
@GetMapping("/get")
@HystrixCommand(fallbackMethod = "getFallback",commandProperties = {
//name : Hystrix執行緒池配置的屬性名
//value : 屬性值
@HystrixProperty(name="execution.isolation.strategy", value = "THREAD"),
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
@HystrixProperty(name="execution.timeout.enabled", value = "true"),
})
public Customer get(@RequestParam("id") Integer id,
@RequestParam("name") String name,
@RequestParam("age") Integer age) {
try {
System.out.println("CustomerController get ....");
System.out.println("執行緒: " + Thread.currentThread().getName());
Thread.sleep(2500);//目的是為了讓當前方法超時,進入降級邏輯
} catch (InterruptedException e) {
e.printStackTrace();
}
return searchFeign.get(id,name,age);
}
public Customer getFallback(Integer id,String name,Integer age) {
System.out.println("getFallback被降級了。是執行緒隔離Hystrix執行緒池的方式進行服務降級....");
return new Customer(id, "Hystrix執行緒池服務降級成功",666);
}
//---------------------------
測試 http://localhost:8001/customer/get?id=1&name=111&age=11111
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ONfwXBpJ-1607759535278)(Pictures\image-20200911120251608.png)]
訊號量的配置資訊
配置資訊 | name | value |
---|---|---|
執行緒隔離策略 | execution.isolation.strategy | SEMAPHORE |
指定訊號量的最大併發請求數 | execution.isolation.semaphore.maxConcurrentRequests | 10 |
程式碼實現
//--------------- 訊號量來管理tomcat執行緒 ----------------
@PostMapping("/save")
@HystrixCommand(fallbackMethod = "saveFallback",commandProperties = {
//name : Hystrix執行緒池配置的屬性名
//value : 屬性值
@HystrixProperty(name="execution.isolation.strategy", value = "SEMAPHORE"),
@HystrixProperty(name="execution.isolation.semaphore.maxConcurrentRequests", value = "10")
})
public Customer save(@RequestBody Customer customer) {
System.out.println("CustomerController save ....");
System.out.println("執行緒 ---> " + Thread.currentThread().getName());
int i=1/0;
return searchFeign.save(customer);
}
//========================================
public Customer saveFallback(Customer customer){
System.out.println("這是訊號量配置,出現異常,進入降級邏輯...............");
return new Customer(customer.getId(),"訊號量配置,出現異常,進入降級邏輯", 5555);
}
測試
因為是post請求,所以使用 postman測試
重啟 customer-service
測試介面: http://localhost:8001/customer/save
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xbgABPqY-1607759535279)(Pictures\image-20200911162158974.png)]
控制檯能夠看到的確使用的是tomcat的執行緒
5.4 斷路器
也叫熔斷器
5.4.1 斷路器介紹
馬丁福勒斷路器論文:https://martinfowler.com/bliki/CircuitBreaker.html
在呼叫指定服務時,如果說這個服務的失敗率達到你輸入的一個閾值,將斷路器從closed狀態,轉變為open狀態,指定服務時無法被訪問的,如果你訪問就直接走fallback方法,在一定的時間內,open狀態會再次轉變為half open狀態,允許一個請求傳送到我的指定服務,如果成功,轉變為closed,如果失敗,服務再次轉變為open狀態,會再次迴圈到half open,直到斷路器回到一個closed狀態。
斷路器 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-GdgP93wo-1607759535280)(Pictures\image-20200911163313991.png)] |
5.4.2 配置斷路器的監控介面
匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
在啟動引導類中添加註解
@EnableHystrixDashboard
配置一個Servlet路徑,指定上Hystrix的Servlet
@WebServlet("/hystrix.stream")
public class HystrixServlet extends HystrixMetricsStreamServlet {
}
//------------------------------------------------------------
// 在啟動類上,新增掃描Servlet的註解
@ServletComponentScan("com.ghy.servlet")
測試直接訪問 http://host:port/hystrix
http://localhost:8001/hystrix
進入之後,輸入被監控的地址 http://localhost:8001/hystrix.stream
監控介面 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-NWGuvLh5-1607759535281)(Pictures\image-20200911172103719.png)] |
在當前位置輸入對映好的servlet路徑
檢測效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-y5PwBpq5-1607759535282)(Pictures\image-20200911172012892.png)] |
5.4.3 配置斷路器的屬性
斷路器的屬性(預設10s秒中之內請求數)
配置資訊 | name | value |
---|---|---|
斷路器的開關 | circuitBreaker.enabled | true |
失敗閾值的總請求數 | circuitBreaker.requestVolumeThreshold | 20 |
請求總數失敗率達到%多少時 | circuitBreaker.errorThresholdPercentage | 50 |
斷路器open狀態後,多少秒是拒絕請求的 | circuitBreaker.sleepWindowInMilliseconds | 5000 |
強制讓服務拒絕請求 [A] | circuitBreaker.forceOpen | false |
強制讓服務接收請求 [B] | circuitBreaker.forceClosed | false |
[A] 跟 [B] 只能選一個
具體配置方式,引數參考 HystrixCommandProperties類進行配置
@GetMapping("/hello/{id}")
@HystrixCommand(fallbackMethod = "helloFallBack",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否開啟斷路器,預設為true:開啟
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //總共在10秒訪問多少次,預設為20次
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"), //失敗的比例達到多少【百分比】開啟斷路器
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000") //過了多少時間後,斷路器變成半開狀態
})
public String hello(@PathVariable Integer id){
System.out.println("customer hello");
if(id == 1) {
int i=1/0; //訪問至少20次,有一半以上是失敗的,則開啟斷路器
}
return "hello,正常了 " + id;
}
public String helloFallBack(Integer id){
System.out.println("hello方法出異常了,進入降級邏輯");
return id.toString() + "-> id=1,出異常,其他正常。。。。。";
}@GetMapping("/customer/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "70"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000")
})
在目標方法中,通過id的值去判斷當前請求是否成功
當id==1時,請求失敗,當失敗比例達到閾值時,開啟斷路器,這個時候,就算訪問成功的請求,也會失敗。等到開啟斷路器的時間到了這後,會允許一個請求訪問,如果成功,則關閉斷路器,如果失敗,依舊開啟斷路器。之後迴圈以上操作
if(id == 1) {
int i=1/0;
}
測試
- 先訪問一個正常的介面 http://localhost:8001/customer/hello/2, 返回正常
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-dMQpZp7u-1607759535284)(Pictures\image-20200911172440795.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZBhcxTa0-1607759535285)(Pictures\image-20200911172622552.png)]
- 再在10秒鐘之內,訪問失敗的介面10次【http://localhost:8001/customer/hello/1】,看到監控介面中,斷路器開關被打開了。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-369jI13Q-1607759535287)(Pictures\image-20200911172749166.png)]
- 這個時候,即便的訪問的是正常的介面,也會失敗。即 http://localhost:8001/customer/hello/2,也出現了服務降級
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-M4ephX4s-1607759535288)(Pictures\image-20200911172833743.png)]
5.5 請求快取
5.5.1 請求快取介紹
請求快取的宣告週期是一次請求
請求快取是快取當前執行緒中的一個方法,將方法引數作為key,方法的返回結果作為value
在一次請求中,目標方法被呼叫過一次,以後就都會被快取。
請求快取 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-U06lwvqi-1607759535291)(Pictures/1588127678866.png)] |
5.5.2 請求快取的實現
建立一個Service,在Service中呼叫Search服務。
@Service
public class CustomerService {
@Autowired
private SearchClient searchClient;
@CacheResult
@HystrixCommand(commandKey = "findById")
public Customer findById(@CacheKey Integer id) throws InterruptedException {
return searchClient.findById(id);
}
@CacheRemove(commandKey = "findById")
@HystrixCommand
public void clearFindById(@CacheKey Integer id){
System.out.println("findById被清空");
}
}
使用請求快取的註解
@CacheResult:幫助我們快取當前方法的返回結果(必須@HystrixCommand配合使用)
@CacheRemove:幫助我們清除某一個快取資訊(基於commandKey)
@CacheKey:指定哪個方法引數作為快取的標識
修改Search模組的返回結果
#使用隨機值來證明會有快取
return new Customer(1,"張三",(int)(Math.random() * 100000));
編寫Filter,去構建HystrixRequestContext
@WebFilter("/*")
public class HystrixRequestContextInitFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HystrixRequestContext.initializeContext();
filterChain.doFilter(servletRequest,servletResponse);
}
}
修改Controller
public Customer findById(@PathVariable Integer id) throws InterruptedException {
System.out.println(customerService.findById(id));
System.out.println(customerService.findById(id));
customerService.clearFindById(id);
System.out.println(customerService.findById(id));
System.out.println(customerService.findById(id));
return searchClient.findById(id);
}
測試結果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fd85DdUY-1607759535292)(Pictures/1588133189886.png)]
六、服務的閘道器-Zuul【重點
】
6.1 引言
客戶端維護大量的ip和port資訊,直接訪問指定服務
認證和授權操作,需要在每一個模組中都新增認證和授權的操作
專案的迭代,服務要拆分,服務要合併,需要客戶端進行大量的變化
統一的把安全性校驗都放在Zuul中
zuul |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-qUEnaHEN-1607759535293)(Pictures/1588145514669.png)] |
6.2 Zuul的快速入門
建立Maven專案,修改為SpringBoot
匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
新增一個註解
@EnableEurekaClient
@EnableZuulProxy
編寫配置檔案
# 指定Eureka服務地址
eureka:
client:
service-url:
defaultZone: http://root:[email protected]:8761/eureka,http://root:[email protected]:8762/eureka
#指定服務的名稱
spring:
application:
name: ZUUL
server:
port: 80
直接測試
測試效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-HTzbYo2U-1607759535295)(Pictures/1588148029538.png)] |
6.3 Zuul常用配置資訊
6.3.1 Zuul的監控介面
匯入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
編寫配置檔案
# 檢視zuul的監控介面(開發時,配置為*,上線,不要配置)
management:
endpoints:
web:
exposure:
include: "*"
直接訪問
/actuator/routes
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fYNGOTlB-1607759535297)(Pictures/1588157803975.png)]
6.3.2 忽略服務配置
# zuul的配置
zuul:
# 基於服務名忽略服務,無法檢視 ,如果要忽略全部的服務 "*",預設配置的全部路徑都會被忽略掉(自定義服務的配置,無法忽略的)
ignored-services: eureka
# 監控介面依然可以檢視,在訪問的時候,404
ignored-patterns: /**/search/**
6.3.3 自定義服務配置
# zuul的配置
zuul:
# 指定自定義服務(方式一 , key(服務名):value(路徑))
# routes:
# search: /ss/**
# customer: /cc/**
# 指定自定義服務(方式二)
routes:
kehu: # 自定義名稱
path: /ccc/** # 對映的路徑
serviceId: customer # 服務名稱
6.3.4 灰度釋出
新增一個配置類
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
準備一個服務,提供2個版本
version: v1
#指定服務的名稱
spring:
application:
name: CUSTOMER-${version}
修改Zuul的配置
# zuul的配置
zuul:
# 基於服務名忽略服務,無法檢視 , 如果需要用到-v的方式,一定要忽略掉
# ignored-services: "*"
測試
測試效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-G0vspNUN-1607759535298)(Pictures/1588165064217.png)] |
6.4 Zuul的過濾器執行流程
客戶端請求傳送到Zuul服務上,首先通過PreFilter鏈,如果正常放行,會吧請求再次轉發給RoutingFilter,請求轉發到一個指定的服務,在指定的服務響應一個結果之後,再次走一個PostFilter的過濾器鏈,最終再將響應資訊交給客戶端。
過濾器 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-CkC33rjN-1607759535300)(Pictures/1588172828199.png)] |
6.5 Zuul過濾器入門
建立POJO類,繼承ZuulFilter抽象類
@Component
public class TestZuulFilter extends ZuulFilter {}
指定當前過濾器的型別
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
指定過濾器的執行順序
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
配置是否啟用
@Override
public boolean shouldFilter() {
// 開啟當前過濾器
return true;
}
指定過濾器中的具體業務程式碼
@Override
public Object run() throws ZuulException {
System.out.println("prefix過濾器執行~~~");
return null;
}
測試
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ScMK8UuG-1607759535301)(Pictures/1588175034889.png)] |
6.6 PreFilter實現token校驗
準備訪問路徑,請求引數傳遞token
http://localhost/v2/customer/version?token=123
建立AuthenticationFilter
@Component
public class AuthenticationFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//..
}
}
在run方法中編寫具體的業務邏輯程式碼
@Override
public Object run() throws ZuulException {
//1. 獲取Request物件
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//2. 獲取token引數
String token = request.getParameter("token");
//3. 對比token
if(token == null || !"123".equalsIgnoreCase(token)) {
//4. token校驗失敗,直接響應資料
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
測試
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9DsUZV63-1607759535303)(Pictures/1588177367016.png)] |
6.7 Zuul的降級
建立POJO類,實現介面FallbackProvider
@Component
public class ZuulFallBack implements FallbackProvider {}
重寫兩個方法
@Override
public String getRoute() {
return "*"; // 代表指定全部出現問題的服務,都走這個降級方法
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("降級的服務:" + route);
cause.printStackTrace();
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
// 指定具體的HttpStatus
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
// 返回的狀態碼
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
// 指定錯誤資訊
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
// 給使用者響應的資訊
String msg = "當前服務:" + route + "出現問題!!!";
return new ByteArrayInputStream(msg.getBytes());
}
@Override
public HttpHeaders getHeaders() {
// 指定響應頭資訊
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
測試
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-sYJvYFT5-1607759535305)(Pictures/1588180538336.png)] |
6.8 Zuul動態路由
建立一個過濾器
// 執行順序最好放在Pre過濾器的最後面
在run方法中編寫業務邏輯
@Override
public Object run() throws ZuulException {
//1. 獲取Request物件
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
//2. 獲取引數,redisKey
String redisKey = request.getParameter("redisKey");
//3. 直接判斷
if(redisKey != null && redisKey.equalsIgnoreCase("customer")){
// http://localhost:8080/customer
context.put(FilterConstants.SERVICE_ID_KEY,"customer-v1");
context.put(FilterConstants.REQUEST_URI_KEY,"/customer");
}else if(redisKey != null && redisKey.equalsIgnoreCase("search")){
// http://localhost:8081/search/1
context.put(FilterConstants.SERVICE_ID_KEY,"search");
context.put(FilterConstants.REQUEST_URI_KEY,"/search/1");
}
return null;
}
測試
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZQpoY7I5-1607759535307)(Pictures/1588185307172.png)] |
七、多語言支援-Sidecar
7.1 引言
在SpringCloud的專案中,需要接入一些非Java的程式,第三方介面,無法接入eureka,hystrix,feign等等元件。啟動一個代理的微服務,代理微服務去和非Java的程式或第三方介面交流,通過代理的微服務去計入SpringCloud的相關元件。
sidecar |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-D0ySa1pK-1607759535308)(Pictures/1588234895867.png)] |
7.2 Sidecar實現
建立一個第三方的服務
建立一個SpringBoot工程,並且新增一個Controller
建立maven工程【sidecar】,修改為SpringBoot
匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-sidecar</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
增加引導類跟配置檔案
引導類上添加註解
@EnableSidecar
編寫配置檔案
server:
port: 81
# 指定Eureka服務地址
eureka:
client:
service-url:
defaultZone: http://root:[email protected]:8761/eureka,http://root:[email protected]:8762/eureka
# 指定服務名稱
spring:
application:
name: other-service
# 指定代理的第三方服務
sidecar:
port: 7001
6、 通過customer通過feign呼叫第三方服務
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MLiQOLhC-1607759535322)(Pictures/1588237130846.png)] |
八、服務間訊息傳遞-Stream
8.1 引言
Stream就是在訊息佇列的基礎上,對其進行封裝,讓咱們更方便的去操作MQ訊息佇列。
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-oeJBHLoo-1607759535324)(Pictures/1588245420362.png)] |
8.2 Stream快速入門
啟動RabbitMQ
消費者【search】-匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
消費者-配置檔案
spring:
# 連線RabbitMQ
rabbitmq:
host: 192.168.199.109
port: 5672
username: test
password: test
virtual-host: /test
消費者-監聽的佇列
public interface StreamClient {
@Input("myMessage")
SubscribableChannel input();
}
//-------------------------------------------------
@Component
@EnableBinding(StreamClient.class)
public class StreamReceiver {
@StreamListener("myMessage")
public void msg(Object msg){
System.out.println("接收到訊息: " + msg);
}
}
生產者-匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
生產者-配置檔案
spring:
# 連線RabbitMQ
rabbitmq:
host: 192.168.199.109
port: 5672
username: test
password: test
virtual-host: /test
生產者-釋出訊息
public interface StreamClient {
@Output("myMessage")
MessageChannel output();
}
//---------------------------------------------- 在啟動類中添加註解 @EnableBinding(StreamClient.class)
@Autowired
private StreamClient streamClient;
@GetMapping("/send")
public String send(){
streamClient.output().send(MessageBuilder.withPayload("Hello Stream!!").build());
return "訊息傳送成功!!";
}
8.3 Stream重複消費問題
消費者工程中需要新增一個配置,指定消費者組
spring:
cloud:
stream:
bindings:
myMessage: # 佇列名稱
group: customer # 消費者組
8.4 Stream的消費者手動ack
編寫配置
spring:
cloud:
stream:
# 實現手動ACK
rabbit:
bindings:
myMessage:
consumer:
acknowledgeMode: MANUAL
修改消費端方法
@StreamListener("myMessage")
public void msg(Object msg,
@Header(name = AmqpHeaders.CHANNEL) Channel channel,
@Header(name = AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
System.out.println("接收到訊息: " + msg);
channel.basicAck(deliveryTag,false);
}
九、服務的動態配置-Config【重點
】
9.1 引言
配置檔案分散在不同的專案中,不方便維護。
配置檔案的安全問題。
修改完配置檔案,無法立即生效。
config |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-dkZfOAfL-1607759535326)(Pictures/1588257620824.png)] |
準備工作
1、gitee 建立一個空倉庫【名稱:cloud-config】
2、克隆倉庫到本地
3、複製 customer-demo工程中的 application.yml 配置檔案到倉庫中,改名為 customer-dev.yml
4、提交到本地,推送到遠端
9.2 搭建Config-Server
建立Maven工程,修改為SpringBoot
匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
引導類上添加註解
@EnableConfigServer
編寫配置檔案(Git的操作)
server:
port: 10000
#config服務也需要註冊到eureka中
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: config-demo #服務名
cloud:
config:
server:
git:
basedir: D:\basedir #本地倉庫路徑
uri: https://gitee.com/cqwiu/cloud-config.git #遠端倉庫url
username: 你的使用者名稱 #gitee使用者名稱
password: 你的密碼 #gitee密碼
測試(http://localhost:port/{label}/{application}-{profile}.yml)
http://localhost:10000/master/customer-dev.yml
效果 |
---|
9.3 搭建Config-Client
就是你想要把配置交給gitee管理的具體服務,在這裡,我們以customer-demo為例
匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
編寫配置檔案
# 指定Eureka服務地址
eureka:
client:
service-url:
defaultZone: http://root:[email protected]:8761/eureka,http://root:[email protected]:8762/eureka
#指定服務的名稱
spring:
application:
name: CUSTOMER-${version}
cloud:
config:
discovery:
enabled: true
service-id: CONFIG
profile: dev
version: v1
# CONFIG -> CUSTOMER-v1-dev.yml
修改配置名稱
修改為bootstrap.yml
編輯遠端倉庫的配置檔案 customer-dev.yml
內容如下:【其實就是把工程中存在的配置從gitee上的配置檔案中去掉】
eureka:
instance:
lease-renewal-interval-in-seconds: 30 #心跳的間隔
lease-expiration-duration-in-seconds: 90 # 多久沒傳送,就認為你宕機了
server:
port: 8001
spring:
# 連線RabbitMQ
rabbitmq:
host: 192.168.20.128
port: 5672
username: demo
password: demo
virtual-host: /demo
SEARCH-DEMO:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
feign:
hystrix:
enabled: true
測試釋出訊息到RabbitMQ
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-olStmM87-1607759535327)(Pictures/1588263245773.png)] |
9.4 實現動態配置
9.4.1 實現原理
實現原理 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MZtl1D2q-1607759535329)(Pictures/1588268131944.png)] |
9.4.2 config-demo服務連線RabbitMQ
config-demo跟customer都匯入依賴
<!-- 手動重新整理向 MQ傳送訊息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 訊息匯流排依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
編寫配置檔案連線RabbitMQ資訊
spring:
rabbitmq:
host: 192.168.20.128
port: 5672
username: demo
password: demo
virtual-host: /demo
9.4.3 實現手動重新整理
編寫配置檔案
management:
endpoints:
web:
exposure:
include: "*" #開啟bus功能
為customer新增一個controller
@RestController
@RefreshScope //允許重新整理
public class CustomerController {
@Value("${env}")
private String env;
@GetMapping("/env")
public String env(){
return env;
}
}
測試
1. CONFIG在Gitee修改之後,自動拉取最新的配置資訊。
2. 其他模組需要更新的話,手動傳送一個post請求 http://ip:port/actuator/bus-refresh,不重啟專案,即可獲取最新的配置資訊
http://localhost:10000/actuator/bus-refresh
9.5.4 內網穿透
內網穿透的官網https://natapp.cn/
註冊登入
實名認證
購買一個免費的隧道。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-i2QvDvse-1607759535330)(Pictures\image-20200903221649035.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ttJwPcnR-1607759535332)(Pictures\image-20200903221747079.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-rzXrMZ1O-1607759535334)(Pictures\image-20200903221940195.png)]
配置隧道 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-hSH2kswU-1607759535335)(Pictures/1588317871876.png)] |
下載客戶端,並複製config.ini檔案,在檔案中指定authtoken
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-BjCzgOyJ-1607759535336)(Pictures\image-20200903222132643.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-CBWZFeAT-1607759535338)(Pictures\image-20200903222317525.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wihiktHB-1607759535339)(Pictures\image-20200903222413334.png)]
netapp軟體 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-OORgI9yL-1607759535341)(Pictures/1588317944258.png)] |
啟動exe檔案,並測試使用域名訪問config介面
效果 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JRer0Fpr-1607759535342)(Pictures/1588317929937.png)] |
9.5.5 實現自動重新整理
配置Gitee中的WebHooks
配置Gitee中的WebHooks |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Hdmj5maz-1607759535343)(Pictures\image-20200903222939794.png)] |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-DfZqMzOj-1607759535344)(Pictures\image-20200903223203214.png)] |
給Config新增一個過濾器
直接去程式碼中找到Filter
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* create by ghy
*/
@WebFilter("/*")
public class UrlFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
String url = new String(httpServletRequest.getRequestURI());
//只過濾/actuator/bus-refresh請求
if (!url.endsWith("/actuator/bus-refresh")) {
chain.doFilter(request, response);
return;
}
//獲取原始的body
String body = readAsChars(httpServletRequest);
System.out.println("original body: "+ body);
//使用HttpServletRequest包裝原始請求達到修改post請求中body內容的目的
CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
private class CustometRequestWrapper extends HttpServletRequestWrapper {
public CustometRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true:false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
public static String readAsChars(HttpServletRequest request)
{
BufferedReader br = null;
StringBuilder sb = new StringBuilder("");
try
{
br = request.getReader();
String str;
while ((str = br.readLine()) != null)
{
sb.append(str);
}
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (null != br)
{
try
{
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
return sb.toString();
}
}
在引導類上標記 @ServletComponentScan(basePackages = “filter所在包名”)
測試
重啟config和customer工程
修改倉庫中配置檔案的內容
測試 |
---|
十、服務的追蹤-Sleuth【重點
】
10.1 引言
在整個微服務架構中,微服務很多,一個請求可能需要呼叫很多很多的服務,最終才能完成一個功能,如果說,整個功能出現了問題,在這麼多的服務中,如何去定位到問題的所在點,出現問題的原因是什麼。
Sleuth可以獲得到整個服務鏈路的資訊。
Zipkin通過圖形化介面去看到資訊。
Sleuth將日誌資訊儲存到資料庫中。
Sleuth |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-NTBbLvIO-1607759535346)(Pictures/1588325099243.png)] |
10.2 Sleuth的使用
匯入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
編寫配置檔案
logging:
level:
org.springframework.web.servlet.DispatcherServlet: DEBUG
測試
日誌資訊 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-OiAFMqQy-1607759535347)(Pictures/1588336398785.png)] |
SEARCH:服務名稱
e9c:總鏈路id
f07:當前服務的鏈路id
false:不會將當前的日誌資訊,輸出其他系統中
10.3 Zipkin的使用
搭建Zipkin的web工程 https://zipkin.io/
version: "3.1"
services:
zipkin:
image: daocloud.io/daocloud/zipkin:latest
restart: always
container_name: zipkin
ports:
- 9411:9411
匯入依賴:把 sleuth 起步依賴換成 zipkin起步依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
編寫配置檔案
#指定服務的名稱
spring:
sleuth:
sampler:
probability: 1 # 百分之多少的sleuth資訊需要輸出到zipkin中,1代表百分百
zipkin:
base-url: http://192.168.20.128:9411/ # 指定zipkin的地址
測試
測試 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-f8auGw37-1607759535348)(Pictures/1588341969002.png)] |
10.4 整合RabbitMQ
匯入RabbitMQ依賴
customer修改配置檔案
spring:
zipkin:
sender:
type: rabbit
修改Zipkin的資訊
version: "3.1"
services:
zipkin:
image: daocloud.io/daocloud/zipkin:latest
restart: always
container_name: zipkin
ports:
- 9411:9411
environment:
- RABBIT_ADDRESSES=192.168.20.128:5672
- RABBIT_USER=test
- RABBIT_PASSWORD=test
- RABBIT_VIRTUAL_HOST=/test
測試
重啟customer
測試 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xtwUkGZ1-1607759535349)(Pictures/1588348586217.png)] |
10.5 Zipkin儲存資料到ES
重新修改zipkin的yml檔案
version: "3.1"
services:
zipkin:
image: daocloud.io/daocloud/zipkin:latest
restart: always
container_name: zipkin
ports:
- 9411:9411
environment:
- RABBIT_ADDRESSES=192.168.199.109:5672
- RABBIT_USER=test
- RABBIT_PASSWORD=test
- RABBIT_VIRTUAL_HOST=/test
- STORAGE_TYPE=elasticsearch
- ES_HOSTS=http://192.168.199.109:9200
十一、完整SpringCloud架構圖【重點
】
完整架構圖 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZQ0nH7t4-1607759535351)(Pictures/1588351313922.png)] |