Spring Cloud 學習-中
1.Spring Cloud 學習
- Feign 宣告式服務呼叫
- Hystrix 熔斷器
- Gateway 閘道器
2.Feign
2.1-Feign-概述
• Feign 是一個宣告式的 REST 客戶端,它用了基於介面的註解方式,很方便實現客戶端配置。
• Feign 最初由 Netflix 公司提供,但不支援SpringMVC註解,後由 SpringCloud 對其封裝,支援了SpringMVC注
解,讓使用者更易於接受
2.2-Feign-快速入門
- 在消費端引入 open-feign 依賴
<!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 編寫Feign呼叫介面
package com.itheima.consumer.feign; import com.itheima.consumer.config.FeignLogConfig; import com.itheima.consumer.domain.Goods; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * * feign宣告式介面。發起遠端呼叫的。 * String url = "http://FEIGN-PROVIDER/goods/findOne/"+id; Goods goods = restTemplate.getForObject(url, Goods.class); * * 1. 定義介面 * 2. 介面上添加註解 @FeignClient,設定value屬性為 服務提供者的 應用名稱 * 3. 編寫呼叫介面,介面的宣告規則 和 提供方介面保持一致。 * 4. 注入該介面物件,呼叫介面方法完成遠端呼叫 */ @FeignClient(value = "FEIGN-PROVIDER") public interface GoodsFeignClient { @GetMapping("/goods/findOne/{id}") public Goods findGoodsById(@PathVariable("id") int id); }
OrderController
package com.itheima.consumer.controller; import com.itheima.consumer.domain.Goods; import com.itheima.consumer.feign.GoodsFeignClient; 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; @RestController @RequestMapping("/order") public class OrderController { @Autowired private RestTemplate restTemplate; @Autowired private GoodsFeignClient goodsFeignClient; @GetMapping("/goods/{id}") public Goods findGoodsById(@PathVariable("id") int id){ /* String url = "http://FEIGN-PROVIDER/goods/findOne/"+id; // 3. 呼叫方法 Goods goods = restTemplate.getForObject(url, Goods.class); return goods;*/ Goods goods = goodsFeignClient.findGoodsById(id); return goods; } }
goodsFeignClient報紅,不影響使用
- 在啟動類 新增 @EnableFeignClients 註解,開啟Feign功能
package com.itheima.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient // 啟用DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients //開啟Feign的功能
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
- 測試呼叫
2.3-Feign超時配置
• Feign 底層依賴於 Ribbon 實現負載均衡和遠端呼叫。
• Ribbon預設1秒超時。
• 超時配置:
feign-consumer application.yml
# 設定Ribbon的超時時間
ribbon:
ConnectTimeout: 1000 # 連線超時時間 預設1s 預設單位毫秒
ReadTimeout: 3000 # 邏輯處理的超時時間 預設1s 預設單位毫秒
2.4-Feign-日誌記錄
• Feign 只能記錄 debug 級別的日誌資訊。
feign-consumer application.yml
# 設定當前的日誌級別 debug,feign只支援記錄debug級別的日誌
logging:
level:
com.itheima: debug
• 定義Feign日誌級別Bean
FeignLogConfig
package com.itheima.consumer.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignLogConfig {
/*
NONE,不記錄
BASIC,記錄基本的請求行,響應狀態碼資料
HEADERS,記錄基本的請求行,響應狀態碼資料,記錄響應頭資訊
FULL;記錄完成的請求 響應資料
*/
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
• 啟用該Bean:
package com.itheima.consumer.feign;
import com.itheima.consumer.config.FeignLogConfig;
import com.itheima.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
*
* feign宣告式介面。發起遠端呼叫的。
*
String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
*
* 1. 定義介面
* 2. 介面上添加註解 @FeignClient,設定value屬性為 服務提供者的 應用名稱
* 3. 編寫呼叫介面,介面的宣告規則 和 提供方介面保持一致。
* 4. 注入該介面物件,呼叫介面方法完成遠端呼叫
*/
@FeignClient(value = "FEIGN-PROVIDER",configuration = FeignLogConfig.class)
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
3.Hystrix
3.1-Hystrix-概述
• Hystix 是 Netflix 開源的一個延遲和容錯庫,用於隔離訪問遠端服務、第三方庫,防止出現級聯失敗(雪崩)。
• 雪崩:一個服務失敗,導致整條鏈路的服務都失敗的情形
Hystix 主要功能
• 隔離
執行緒池隔離
訊號量隔離
• 降級:異常,超時
• 熔斷
• 限流
3.2-Hystrix-降級
3.2.1-提供方降級
Hystix 降級:當服務發生異常或呼叫超時,返回預設資料
服務提供方降級
-
在服務提供方,引入 hystrix 依賴
<!-- hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
定義降級方法
/**
* 定義降級方法:
* 1. 方法的返回值需要和原方法一樣
* 2. 方法的引數需要和原方法一樣
*/
public Goods findOne_fallback(int id){
Goods goods = new Goods();
goods.setTitle("降級了~~~");
return goods;
}
- 使用 @HystrixCommand 註解配置降級方法
/**
* 降級:
* 1. 出現異常
* 2. 服務呼叫超時
* * 預設1s超時
*
* @HystrixCommand(fallbackMethod = "findOne_fallback")
* fallbackMethod:指定降級後呼叫的方法名稱
*/
@GetMapping("/findOne/{id}")
@HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
//設定Hystrix的超時時間,預設1s
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public Goods findOne(@PathVariable("id") int id){
//1.造個異常
int i = 3/0;
try {
//2. 休眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Goods goods = goodsService.findOne(id);
goods.setTitle(goods.getTitle() + ":" + port);//將埠號,設定到了 商品標題上
return goods;
}
- 在啟動類上開啟Hystrix功能:@EnableCircuitBreaker
package com.itheima.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 啟動類
*/
@EnableEurekaClient //該註解 在新版本中可以省略
@SpringBootApplication
@EnableCircuitBreaker // 開啟Hystrix功能
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class,args);
}
}
3.2.2-消費方降級
-
feign 元件已經集成了 hystrix 元件。
-
定義feign 呼叫介面實現類,複寫方法,即 降級方法
GoodsFeignClientFallback
package com.itheima.consumer.feign;
import com.itheima.consumer.domain.Goods;
import org.springframework.stereotype.Component;
/**
* Feign 客戶端的降級處理類
* 1. 定義類 實現 Feign 客戶端介面
* 2. 使用@Component註解將該類的Bean加入SpringIOC容器
*/
@Component
public class GoodsFeignClientFallback implements GoodsFeignClient {
@Override
public Goods findGoodsById(int id) {
Goods goods = new Goods();
goods.setTitle("又被降級了~~~");
return goods;
}
}
-
在 @FeignClient 註解中使用 fallback 屬性設定降級處理類。
GoodsFeignClient
package com.itheima.consumer.feign;
import com.itheima.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "HYSTRIX-PROVIDER",fallback = GoodsFeignClientFallback.class)
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
-
配置開啟 feign.hystrix.enabled = true
application.yml
# 開啟feign對hystrix的支援
feign:
hystrix:
enabled: true
3.3-Hystrix-熔斷
3.3.1-熔斷-概念
• Hystrix 熔斷機制,用於監控微服務呼叫情況,當失敗的情況達到預定的閾值(5秒失敗20次),會開啟
斷路器,拒絕所有請求,直到服務恢復正常為止。
斷路器三種狀態:開啟、半開、關閉、
3.3.2-熔斷-程式碼演示
修改服務提供方的方法,演示熔斷機制
熔斷配置
• circuitBreaker.sleepWindowInMilliseconds:監控時間
• circuitBreaker.requestVolumeThreshold:失敗次數
• circuitBreaker.errorThresholdPercentage:失敗率
GoodsController
package com.itheima.provider.controller;
import com.itheima.provider.domain.Goods;
import com.itheima.provider.service.GoodsService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.Date;
/**
* Goods Controller 服務提供方
*/
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@Value("${server.port}")
private int port;
/**
* 降級:
* 1. 出現異常
* 2. 服務呼叫超時
* * 預設1s超時
*
* @HystrixCommand(fallbackMethod = "findOne_fallback")
* fallbackMethod:指定降級後呼叫的方法名稱
*/
@GetMapping("/findOne/{id}")
@HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
//設定Hystrix的超時時間,預設1s
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
//監控時間 預設5000 毫秒
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"),
//失敗次數。預設20次
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "20"),
//失敗率 預設50%
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50") })
public Goods findOne(@PathVariable("id") int id){
//如果id == 1 ,則出現異常,id != 1 則正常訪問
if(id == 1){
//1.造個異常
int i = 3/0;
}
/*try {
//2. 休眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
Goods goods = goodsService.findOne(id);
goods.setTitle(goods.getTitle() + ":" + port);//將埠號,設定到了 商品標題上
return goods;
}
/**
* 定義降級方法:
* 1. 方法的返回值需要和原方法一樣
* 2. 方法的引數需要和原方法一樣
*/
public Goods findOne_fallback(int id){
Goods goods = new Goods();
goods.setTitle("降級了~~~");
return goods;
}
}
3.3.3-熔斷監控
• Hystrix 提供了 Hystrix-dashboard 功能,用於實時監控微服務執行狀態。
• 但是Hystrix-dashboard只能監控一個微服務。
• Netflix 還提供了 Turbine ,進行聚合監控。
熔斷器監控安裝 請檢視Turbine搭建步驟.md
4.Gateway
4.1-Gateway-概述
-
閘道器旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。
-
在微服務架構中,不同的微服務可以有不同的網路地址,各個微服務之間通過互相呼叫完成使用者請求,客戶端可能通過呼叫N個微服務的介面完成一個使用者請求。
-
存在的問題:
1.客戶端多次請求不同的微服務,增加客戶端的複雜性
2.認證複雜,每個服務都要進行認證
3.http請求不同服務次數增加,效能不高
-
閘道器就是系統的入口,封裝了應用程式的內部結構,為客戶端提供統一服務,一些與業務本身功能無關的公共邏輯可以在這裡實現,諸如認證、鑑權、監控、快取、負載均衡、流量管控、路由轉發等
-
在目前的閘道器解決方案裡,有Nginx+ Lua、Netflix Zuul 、Spring Cloud Gateway等等
4.2-Gateway-快速入門
-
搭建閘道器模組
建立api-gateway-server模組
-
引入依賴:starter-gateway
<dependencies> <!--引入gateway 閘道器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- eureka-client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
-
編寫啟動類
package com.itheima.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class ApiGatewayApp { public static void main(String[] args) { SpringApplication.run(ApiGatewayApp.class,args); } }
-
編寫配置檔案
application.yml
server: port: 80 spring: application: name: api-gateway-server cloud: # 閘道器配置 gateway: # 路由配置:轉發規則 routes: #集合。 # id: 唯一標識。預設是一個UUID # uri: 轉發路徑 # predicates: 條件,用於請求閘道器路徑的匹配規則 - id: gateway-provider uri: http://localhost:8001/ predicates: - Path=/goods/**
-
啟動測試
4.3-Gateway-靜態路由
application.yml 中的uri是寫死的,就是靜態路由
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 閘道器配置
gateway:
# 路由配置:轉發規則
routes: #集合。
# id: 唯一標識。預設是一個UUID
# uri: 轉發路徑
# predicates: 條件,用於請求閘道器路徑的匹配規則
# filters:配置區域性過濾器的
- id: gateway-provider
# 靜態路由
uri: http://localhost:8001/
predicates:
- Path=/goods/**
4.4-Gateway-動態路由
啟動類新增@EnableEurekaClient(新版本不加也可以)
package com.itheima.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class,args);
}
}
引入eureka-client配置
application.yml 中修改uri屬性:uri: lb://服務名稱
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 閘道器配置
gateway:
# 路由配置:轉發規則
routes: #集合。
# id: 唯一標識。預設是一個UUID
# uri: 轉發路徑
# predicates: 條件,用於請求閘道器路徑的匹配規則
# filters:配置區域性過濾器的
- id: gateway-provider
# 靜態路由
# uri: http://localhost:8001/
# 動態路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
4.5-Gateway-微服務名稱配置
application.yml中配置微服務名稱配置
# 微服務名稱配置
discovery:
locator:
enabled: true # 設定為true 請求路徑前可以新增微服務名稱
lower-case-service-id: true # 允許為小寫
4.6-Gateway-過濾器
4.6.1-過濾器-概述
-
Gateway 支援過濾器功能,對請求或響應進行攔截,完成一些通用操作。
-
Gateway 提供兩種過濾器方式:“pre”和“post”
pre 過濾器,在轉發之前執行,可以做引數校驗、許可權校驗、流量監控、日誌輸出、協議轉換等。
post 過濾器,在響應之前執行,可以做響應內容、響應頭的修改,日誌的輸出,流量監控等。 -
Gateway 還提供了兩種型別過濾器
GatewayFilter:區域性過濾器,針對單個路由
GlobalFilter :全域性過濾器,針對所有路由
4.6.2-區域性過濾器
- GatewayFilter 區域性過濾器,是針對單個路由的過濾器。
- 在Spring Cloud Gateway 元件中提供了大量內建的區域性過濾器,對請求和響應做過濾操作。
- 遵循約定大於配置的思想,只需要在配置檔案配置區域性過濾器名稱,併為其指定對應的值,就可以讓其生效。
具體配置參見gateway內建過濾器工廠.md
測試配置
api-gateway-server application.yml
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 閘道器配置
gateway:
# 路由配置:轉發規則
routes: #集合。
# id: 唯一標識。預設是一個UUID
# uri: 轉發路徑
# predicates: 條件,用於請求閘道器路徑的匹配規則
# filters:配置區域性過濾器的
- id: gateway-provider
# 靜態路由
# uri: http://localhost:8001/
# 動態路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
filters:
- AddRequestParameter=username,zhangsan
gateway-provider模組中GoodsController中的findOne新增username引數
public Goods findOne(@PathVariable("id") int id,String username){
System.out.println(username);
//如果id == 1 ,則出現異常,id != 1 則正常訪問
if(id == 1){
//1.造個異常
int i = 3/0;
}
/*try {
//2. 休眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
Goods goods = goodsService.findOne(id);
goods.setTitle(goods.getTitle() + ":" + port);//將埠號,設定到了 商品標題上
return goods;
}
4.6.3-全域性過濾器
-
GlobalFilter 全域性過濾器,不需要在配置檔案中配置,系統初始化時載入,並作用在每個路由上。
-
Spring Cloud Gateway 核心的功能也是通過內建的全域性過濾器來完成。
-
自定義全域性過濾器步驟:
- 定義類實現 GlobalFilter 和 Ordered介面
- 複寫方法
- 完成邏輯處理
MyFilter
package com.itheima.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義全域性過濾器執行了~~~");
return chain.filter(exchange);//放行
}
/**
* 過濾器排序
* @return 數值越小 越先執行
*/
@Override
public int getOrder() {
return 0;
}
}