SpringCloud的入門(Hystix、Feign、Zuul)
Hystix
簡介
Hystix,即熔斷器。
首頁:https://github.com/Netflix/Hystrix/
Hystix是Netflix開源的一個延遲和容錯庫,用于隔離通路遠端服務、第三方庫,防止出現級聯失敗。
熔斷器 Hystrix是容錯管理工具,作用是通過隔離、控制服務進而對延遲和故障提供更強大的容錯能力,避免整個系統被拖垮。
複雜分布式架構通常都具有很多依賴,當一個應用高度耦合其他服務時非常危險且容易導緻失敗,這種失敗很容易傷害服務的調用者,最後導緻一個接一個的連續錯誤,應用本身就處在被拖垮的風險中,最後失去控制,就像在一個高流量的網站中,某個單一的後端一旦發生延遲,将會在數秒内導緻所有應用資源被耗盡。如何處理這些問題是有關系統性能和效率的關鍵性問題。
當在系統高峰時期,大量對微服務的調用可能會堵塞遠端伺服器的線程池,如果這個線程池沒有和主應用伺服器的線程池隔離,就可能導緻整個伺服器挂機。
Hystrix使用自己的線程池,這樣和主應用伺服器線程池隔離,如果調用花費很長時間,會停止調用,不同的指令或指令組能夠被配置使用它們各自的線程池,可以隔離不同的服務。
熔斷器的工作機制:
熔斷機制的原理很簡單,像家裡的電路熔斷器,如果電路發生短路能立刻熔斷電路,避免發生災難。在分布式系統中應用這一模式之後,服務調用方可以自己進行判斷某些服務反應慢或者存在大量逾時的情況時,能夠主動熔斷,防止整個系統被拖垮。
不同于電路熔斷隻能斷不能自動重連,Hystrix可以實作彈性容錯,當情況好轉之後,可以自動重連。這就好比魔術師把鴿子變沒了容易,但是真正考驗技術的是如何把消失的鴿子再變回來。
通過斷路的方式,可以将後續請求直接拒絕掉,一段時間之後允許部分請求通過,如果調用成功則回到電路閉合狀态,否則繼續斷開。
正常工作的情況下,用戶端請求可以正常調用服務API接口,當有服務出現異常時,直接進行失敗復原,服務降級處理。當服務繁忙時,如果服務出現異常,不是粗暴的直接報錯,而是傳回一個友好的提示,雖然拒絕了使用者的通路,但是會傳回一個結果。這就好比去買魚,平常超市買魚會額外贈送殺魚的服務。等到逢年過節,逾時繁忙時,可能就不提供殺魚服務了,這就是服務的降級。系統特别繁忙時,一些次要服務暫時中斷,優先保證主要服務的暢通,一切資源優先讓給主要服務來使用,在雙十一、618時,京東天貓都會采用這樣的政策。
動手實踐
引入依賴
首先在user-consumer中引入Hystix依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
開啟熔斷
feign:
hystrix:
enabled: true # 啟動hystrix熔斷機制
@SpringBootApplication
@EnableFeignClients // 啟動Feign
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
改造消費者
我們改造user-consumer,添加一個用來通路的user服務的DAO,并且聲明一個失敗時的復原處理函數:
@Component
public class UserDao {
@Autowired
private RestTemplate restTemplate;
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
@HystrixCommand(fallbackMethod = "queryUserByIdFallback")
public User queryUserById(Long id){
long begin = System.currentTimeMillis();
String url = "http://user-service/user/" + id;
User user = this.restTemplate.getForObject(url, User.class);
long end = System.currentTimeMillis();
// 記錄通路用時:
logger.info("通路用時:{}", end - begin);
return user;
}
public User queryUserByIdFallback(Long id){
User user = new User();
user.setId(id);
user.setName("使用者資訊查詢出現異常!");
return user;
}
}
-
:聲明一個失敗復原處理函數queryUserByIdFallback,當queryUserById執行逾時(預設是1000毫秒),就會執行fallback函數,傳回錯誤提示。@HystrixCommand(fallbackMethod="queryUserByIdFallback")
- 為了友善檢視熔斷的觸發時機,我們記錄請求通路時間。
在原來的業務邏輯中調用這個DAO:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
ids.forEach(id -> {
// 我們測試多次查詢,
users.add(this.userDao.queryUserById(id));
});
return users;
}
}
改造服務提供者
改造服務提供者,随機休眠一段時間,以觸發熔斷:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) throws InterruptedException {
// 為了示範逾時現象,我們在這裡然線程休眠,時間随機 0~2000毫秒
Thread.sleep(new Random().nextInt(2000));
return this.userMapper.selectByPrimaryKey(id);
}
}
啟動測試
優化
雖然熔斷實作了,但是我們的重試機制似乎沒有生效,是這樣嗎?
其實這裡是因為我們的Ribbon逾時時間設定的是1000ms:
而Hystix的逾時時間預設也是1000ms,是以重試機制沒有被觸發,而是先觸發了熔斷。
是以,Ribbon的逾時時間一定要小于Hystix的逾時時間。
我們可以通過
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
來設定Hystrix逾時時間。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 設定hystrix的逾時時間為6000ms
Feign
在前面的學習中,我們使用了Ribbon的負載均衡功能,大大簡化了遠端調用時的代碼:
String baseUrl = "http://user-service/user/";
User user = this.restTemplate.getForObject(baseUrl + id, User.class)
如果就學到這裡,你可能以後需要編寫類似的大量重複代碼,格式基本相同,無非參數不一樣。有沒有更優雅的方式,來對這些代碼再次優化呢?
這就是我們接下來要學的Feign的功能了。
簡介
feign(僞裝)
Feign可以把Rest的請求進行隐藏,僞裝成類似SpringMVC的Controller一樣。你不用再自己拼接url,拼接參數等等操作,一切都交給Feign去做。
項目首頁:https://github.com/OpenFeign/feign
Feign是Netflix開發的聲明式、模闆化的HTTP用戶端,其靈感來自Retrofit、JAXRS-2.0以及 WebSocket。Feign可幫助我們更加便捷、優雅地調用HTTP API。
在Spring Cloud中,使用Feign非常簡單——建立一個接口,并在接口上添加一些注解,代碼就完成了。Feign支援多種注解,例如 Feign自帶的注解或者JAX-RS注解等。
Spring Cloud對 Feign進行了增強,使Feign支援了Spring MVC注解,并整合了Ribbon和Eureka,進而讓 Feign的使用更加友善。
快速入門
導入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Feign的用戶端
@FeignClient("user-service")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
- 首先這是一個接口,Feign會通過動态代理,幫我們生成實作類。這點跟mybatis的mapper很像
-
,聲明這是一個Feign用戶端,類似@FeignClient
注解。同時通過@Mapper
屬性指定服務名稱value
- 接口中的定義方法,完全采用SpringMVC的注解,Feign會根據注解幫我們生成URL,并通路擷取結果
改造原來的調用邏輯,不再調用UserDao:
@Service
public class UserService {
@Autowired
private UserFeignClient userFeignClient;
public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
ids.forEach(id -> {
// 我們測試多次查詢,
users.add(this.userFeignClient.queryUserById(id));
});
return users;
}
}
你會發現RestTemplate的注冊被我删除了。Feign中已經自動內建了Ribbon負載均衡,是以我們不需要自己定義RestTemplate了
啟動測試:
通路接口,就可以得到結果
負載均衡
Feign中本身已經內建了Ribbon依賴和自動配置:
是以我們不需要額外引入依賴,也不需要再注冊
RestTemplate
對象。
另外,我們可以像上節課中講的那樣去配置Ribbon,可以通過
ribbon.xx
來進行全局配置。也可以通過
服務名.ribbon.xx
來對指定服務配置:
user-service:
ribbon:
ConnectTimeout: 250 # 連接配接逾時時間(ms)
ReadTimeout: 1000 # 通信逾時時間(ms)
OkToRetryOnAllOperations: true # 是否對所有操作重試
MaxAutoRetriesNextServer: 1 # 同一服務不同執行個體的重試次數
MaxAutoRetries: 1 # 同一執行個體的重試次數
Hystix支援
Feign預設也有對Hystix的內建
隻不過,預設情況下是關閉的。我們需要通過下面的參數來開啟:
feign:
hystrix:
enabled: true # 開啟Feign的熔斷功能
但是,Feign中的Fallback配置不像Ribbon中那樣簡單了。
1)首先,我們要定義一個類,實作剛才編寫的UserFeignClient,作為fallback的處理類
@Component
public class UserFeignClientFallback implements UserFeignClient {
@Override
public User queryUserById(Long id) {
User user = new User();
user.setId(id);
user.setName("使用者查詢出現異常!");
return user;
}
}
2)然後在UserFeignClient中,指定剛才編寫的實作類
@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
3)重新開機測試:
我們關閉user-service服務,然後在頁面通路。
請求壓縮
Spring Cloud Feign 支援對請求和響應進行GZIP壓縮,以減少通信過程中的性能損耗。通過下面的參數即可開啟請求與響應的壓縮功能:
feign:
compression:
request:
enabled: true # 開啟請求壓縮
response:
enabled: true # 開啟響應壓縮
同時,我們也可以對請求的資料類型,以及觸發壓縮的大小下限進行設定:
feign:
compression:
request:
enabled: true # 開啟請求壓縮
mime-types: text/html,application/xml,application/json # 設定壓縮的資料類型
min-request-size: 2048 # 設定觸發壓縮的大小下限
注:上面的資料類型、壓縮大小下限均為預設值。
日志級别
前面講過,通過
logging.level.xx=debug
來設定日志級别。然而這個對Fegin用戶端而言不會産生效果。因為
@FeignClient
注解修改的用戶端在被代理時,都會建立一個新的Fegin.Logger執行個體。我們需要額外指定這個日志的級别才可以。
1)設定com.leyou包下的日志級别都為debug
logging:
level:
com.leyou: debug
2)編寫配置類,定義日志級别
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
這裡指定的Level級别是FULL,Feign支援4種級别:
- NONE:不記錄任何日志資訊,這是預設值。
- BASIC:僅記錄請求的方法,URL以及響應狀态碼和執行時間
- HEADERS:在BASIC的基礎上,額外記錄了請求和響應的頭資訊
- FULL:記錄所有請求和響應的明細,包括頭資訊、請求體、中繼資料。
3)在FeignClient中指定配置類:
@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class, configuration = FeignConfig.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
4)重新開機項目,即可看到每次通路的日志:
Zuul網關
通過前面的學習,使用Spring Cloud實作微服務的架構基本成型,大緻是這樣的:
我們使用Spring Cloud Netflix中的Eureka實作了服務注冊中心以及服務注冊與發現;而服務間通過Ribbon或Feign實作服務的消費以及均衡負載;通過Spring Cloud Config實作了應用多環境的外部化配置以及版本管理。為了使得服務叢集更為健壯,使用Hystrix的融斷機制來避免在微服務架構中個别服務出現異常時引起的故障蔓延。
在該架構中,我們的服務叢集包含:内部服務Service A和Service B,他們都會注冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,通過均衡負載公開至服務調用方。我們把焦點聚集在對外服務這塊,直接暴露我們的服務位址,這樣的實作是否合理,或者是否有更好的實作方式呢?
先來說說這樣架構需要做的一些事兒以及存在的不足:
- 首先,破壞了服務無狀态特點。
- 為了保證對外服務的安全性,我們需要實作對服務通路的權限控制,而開放服務的權限控制機制将會貫穿并污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務叢集中REST API無狀态的特點。
- 從具體開發和測試的角度來說,在工作中除了要考慮實際的業務邏輯之外,還需要額外考慮對接口通路的控制處理。
- 其次,無法直接複用既有接口。
- 當我們需要對一個即有的叢集内通路接口,實作外部服務通路時,我們不得不通過在原有接口上增加校驗邏輯,或增加一個代理調用來實作權限控制,無法直接複用原有的接口。
面對類似上面的問題,我們要如何解決呢?答案是:服務網關!
為了解決上面這些問題,我們需要将權限控制這樣的東西從我們的服務單元中抽離出去,而最适合這些邏輯的地方就是處于對外通路最前端的地方,我們需要一個更強大一些的均衡負載器的 服務網關。
服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了
權限控制
等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時将權限控制這些較重的非業務邏輯内容遷移到服務路由層面,使得服務叢集主體能夠具備更高的可複用性和可測試性。
簡介
Zuul是 Netflix開源的微服務網關,它可以和Eureka、Ribbon、Hystrix等元件配合使用。Zuul的核心是一系列的過濾器,這些過濾器可以完成以下功能。
- 身份認證與安全:識别每個資源的驗證要求,并拒絕那些與要求不符的請求。
- 審查與監控:在邊緣位置追蹤有意義的資料和統計結果,進而帶來精确的生産視圖
- 動态路由:動态地将請求路由到不同的後端叢集。
- 壓力測試:逐漸增加指向叢集的流量,以了解性能。
- 負載配置設定:為每一種負載類型配置設定對應容量,并棄用超出限定值的請求
- 靜态響應處理:在邊緣位置直接建立部分響應,進而避免其轉發到内部叢集
- 多區域彈性:跨越AWS Region進行請求路由,旨在實作ELB ( Elastic Load Balancing )
- 使用的多樣化,以及讓系統的邊緣更貼近系統的使用者。
Spring Cloud對Zuul進行了整合與增強。目前,Zuul使用的預設HTTP用戶端是ApacheHTTP Client,也可以使用RestClient或者okhttp3.OkHttpClient。如果想要使用RestClient,可以設定ribbon.restclient.enabled=true;想要使用okhttp3.OkHttpClient,可以設定ribbon.okhttp.enabled=true。
Zuul加入後的架構
- 不管是來自于用戶端(PC或移動端)的請求,還是服務内部調用。一切對服務的請求都會經過Zuul這個網關,然後再由網關來實作 鑒權、動态路由等等操作。Zuul就是我們服務的統一入口。
快速入門
建立工程
建立一個微服務項目,繼承之前建立的SpringCloud Parent項目。
<?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>SpringCloudParent</artifactId>
<groupId>cn.rayfoo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Zuul-Server</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
</project>
編寫啟動類
通過
@EnableZuulProxy
注解開啟Zuul的功能:
@SpringBootApplication
@EnableZuulProxy // 開啟Zuul的網關功能
public class ZuulDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication.class, args);
}
}
編寫配置
server:
port: 10010 #服務端口
spring:
application:
name: api-gateway #指定服務名
編寫路由規則
我們需要用Zuul來代理user-service服務,先看一下控制台中的服務狀态:
- ip為:127.0.0.1
- 端口為:8081
映射規則:
zuul:
routes:
user-service: # 這裡是路由id,随意寫
path: /user-service/** # 這裡是映射路徑
url: http://127.0.0.1:8081 # 映射路徑對應的實際url位址
我們将符合
path
規則的一切請求,都代理到
url
參數指定的位址
本例中,我們将
/user-service/**
開頭的請求,代理到http://127.0.0.1:8081
啟動測試:
通路的路徑中需要加上配置規則的映射路徑,我們通路:http://127.0.0.1:8081/user-service/user/10
面向服務的路由
在剛才的路由規則中,我們把路徑對應的服務位址寫死了!如果同一服務有多個執行個體的話,這樣做顯然就不合理了。
我們應該根據服務的名稱,去Eureka注冊中心查找 服務對應的所有執行個體清單,然後進行動态路由才對!
添加Eureka用戶端依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
開啟Eureka用戶端發現功能
@SpringBootApplication
@EnableZuulProxy // 開啟Zuul的網關功能
@EnableDiscoveryClient
public class ZuulDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication.class, args);
}
}
添加Eureka配置,擷取服務資訊
eureka:
client:
registry-fetch-interval-seconds: 5 # 擷取服務清單的周期:5s
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
修改映射配置,通過服務名稱擷取
因為已經有了Eureka用戶端,我們可以從Eureka擷取服務的位址資訊,是以映射時無需指定IP位址,而是通過服務名稱來通路,而且Zuul已經內建了Ribbon的負載均衡功能。
zuul:
routes:
user-service: # 這裡是路由id,随意寫
path: /user-service/** # 這裡是映射路徑
serviceId: user-service # 指定服務名稱
啟動測試
再次啟動,這次Zuul進行代理時,會利用Ribbon進行負載均衡通路:
簡化的路由配置
在剛才的配置中,我們的規則是這樣的:
-
: 來指定映射路徑。zuul.routes.<route>.path=/xxx/**
是自定義的路由名<route>
-
:來指定服務名。zuul.routes.<route>.serviceId=/user-service
而大多數情況下,我們的
<route>
路由名稱往往和 服務名會寫成一樣的。是以Zuul就提供了一種簡化的配置文法:
zuul.routes.<serviceId>=<path>
比方說上面我們關于user-service的配置可以簡化為一條:
zuul:
routes:
user-service: /user-service/** # 這裡是映射路徑
省去了對服務名稱的配置。
預設的路由規則
在使用Zuul的過程中,上面講述的規則已經大大的簡化了配置項。但是當服務較多時,配置也是比較繁瑣的。是以Zuul就指定了預設的路由規則:
- 預設情況下,一切服務的映射路徑就是服務名本身。
- 例如服務名為:
,則預設的映射路徑就是:user-service
/user-service/**
- 例如服務名為:
路由字首
配置示例:
zuul:
prefix: /api # 添加路由字首
routes:
user-service: # 這裡是路由id,随意寫
path: /user-service/** # 這裡是映射路徑
service-id: user-service # 指定服務名稱
我們通過
zuul.prefix=/api
來指定了路由的字首,這樣在發起請求時,路徑就要以/api開頭。
路徑
/api/user-service/user/1
将會被代理到
/user-service/user/1
過濾器
Zuul作為網關的其中一個重要功能,就是實作請求的鑒權。而這個動作我們往往是通過Zuul提供的過濾器來實作的。
ZuulFilter
ZuulFilter是過濾器的頂級父類。在這裡我們看一下其中定義的4個最重要的方法:
public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 來自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}
-
:傳回一個shouldFilter
值,判斷該過濾器是否需要執行。傳回true執行,傳回false不執行。Boolean
-
:過濾器的具體業務邏輯。run
-
:傳回字元串,代表過濾器的類型。包含以下4種:filterType
-
:請求在被路由之前執行pre
-
:在路由請求時調用routing
-
:在routing和errror過濾器之後調用post
-
:處理請求時發生錯誤調用error
-
-
:通過傳回的int值來定義過濾器的執行順序,數字越小優先級越高。filterOrder
過濾器執行生命周期
這張是Zuul官網提供的請求生命周期圖,清晰的表現了一個請求在各個過濾器的執行順序。
- 正常流程:
- 請求到達首先會經過pre類型過濾器,而後到達routing類型,進行路由,請求就到達真正的服務提供者,執行請求,傳回結果後,會到達post過濾器。而後傳回響應。
- 異常流程:
- 整個過程中,pre或者routing過濾器出現異常,都會直接進入error過濾器,再error處理完畢後,會将請求交給POST過濾器,最後傳回給使用者。
- 如果是error過濾器自己出現異常,最終也會進入POST過濾器,而後傳回。
- 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和routing不同的時,請求不會再到達POST過濾器了。
所有内置過濾器清單:
使用場景
場景非常多:
- 請求鑒權:一般放在pre類型,如果發現沒有通路權限,直接就攔截了
- 異常處理:一般會在error類型和post類型過濾器中結合來處理。
- 服務調用時長統計:pre和post結合使用。
自定義過濾器
接下來我們來自定義一個過濾器,模拟一個登入的校驗。基本邏輯:如果請求中有access-token參數,則認為請求有效,放行。
定義過濾器類
@Component
public class LoginFilter extends ZuulFilter{
@Override
public String filterType() {
// 登入校驗,肯定是在前置攔截
return "pre";
}
@Override
public int filterOrder() {
// 順序設定為1
return 1;
}
@Override
public boolean shouldFilter() {
// 傳回true,代表過濾器生效。
return true;
}
@Override
public Object run() throws ZuulException {
// 登入校驗邏輯。
// 1)擷取Zuul提供的請求上下文對象
RequestContext ctx = RequestContext.getCurrentContext();
// 2) 從上下文中擷取request對象
HttpServletRequest req = ctx.getRequest();
// 3) 從請求中擷取token
String token = req.getParameter("access-token");
// 4) 判斷
if(token == null || "".equals(token.trim())){
// 沒有token,登入校驗失敗,攔截
ctx.setSendZuulResponse(false);
// 傳回401狀态碼。也可以考慮重定向到登入頁。
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
// 校驗通過,可以考慮把使用者資訊放入上下文,繼續向後執行
return null;
}
}
測試
沒有token參數時,通路失敗。有token參數時,通路成功:
負載均衡和熔斷
Zuul中預設就已經內建了Ribbon負載均衡和Hystix熔斷機制。但是所有的逾時政策都是走的預設值,比如熔斷逾時時間隻有1S,很容易就觸發了。是以建議我們手動進行配置:
zuul:
retryable: true
ribbon:
ConnectTimeout: 250 # 連接配接逾時時間(ms)
ReadTimeout: 2000 # 通信逾時時間(ms)
OkToRetryOnAllOperations: true # 是否對所有操作重試
MaxAutoRetriesNextServer: 2 # 同一服務不同執行個體的重試次數
MaxAutoRetries: 1 # 同一執行個體的重試次數
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 熔斷逾時時長:6000ms