雪崩問題
微服務中,服務間調用關係錯綜複雜,一個請求,可能需要調用多個微服務接口才能實現,會形成非常複雜的調用鏈路:
如圖,一次業務請求,需要調用A、P、 H、I 四個服務,這四個服務又可能調用其它服務。如果此時,某個服務出現問題。例如微服務 I 發生異常,請求阻塞,用戶不會得到響應,則tomcat的這個線程不會釋放,於是越來越多的用戶請求到來,越來越多的線程會阻塞:
服務器支持的線程和併發數有限,請求一直阻塞, 會導致服務器資源耗盡,從而導致所有其它服務都不可用,形成雪崩效應。
這就好比,一個汽車生產線,生產不同的汽車,需要使用不同的零件,如果某個零件因爲種種原因無法使用,那麼就會造成整臺車無法裝配,陷入等待零件的狀態,直到零件到位,才能繼續組裝。此時如果有很多 個車型都需要這個零件,那麼整個工廠都將陷入等待的狀態,導致所有生產都陷入癱疾。一個零件的波及範圍不斷擴大。
Hystix解決雪崩問題
Hystix解決雪崩問題的手段有兩個
- 線程隔離
- 服務熔斷
- Hystrix爲每個依賴服務調用分配一個小的線程池, 如果線程池已滿調用將被立即拒絕,默認不採用排隊加速失敗判定時間。
用戶的請求將不再直接訪問服務,而是通過線程池中的空閒線程來訪問服務,如果線程池已滿,或者請求超時,則會進行降級處理,什麼是服務降級? - 服務降級:優先保證核心服務,而非核心服務不可用或弱可用。
用戶的請求故障時,不會被阻塞,更不會無休止的等待或者看到系統崩潰,至少可以看到一個執行結果(例如返回友好的提示信息:服務器正忙等)。
服務降級雖然會導致請求失敗,但是不會導致阻塞,而且最多會影響這個依賴服務對應的線程池中的資源,對其它服務沒有響應。 - 觸發Hystix服務降級的情況:
線程池已滿
請求超時
開啓Hystrix
首先在做降級處理之前,應該明確是給那個服務降級,是服務的提供方還是消費方?一般情況是服務的消費方去調用服務的提供方的,服務的提供方出現問題,纔會導致服務的消費方出現問題。所以應該是對服務的消費方進行服務的降級處理,例如在調用一個服務時,出現了問題,消費方可以快速的給用戶處理響應或一個友好提示。
服務的消費方進行降級處理,不會讓服務提供方的的錯誤影響自己錯誤,這樣自己就不會出現錯誤了,當然用戶在調用服務的消費方時就不會出現錯誤。
環境準備
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
啓動類中,開啓@EnableCircuitBreaker
功能
//@EnableCircuitBreaker
//@EnableDiscoveryClient
//@SpringBootApplication
@SpringCloudApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){return new RestTemplate();}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
@SpringCloudApplication
註解源碼
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
controller改寫
編寫降級邏輯當目標服務的調用出現故障,我們希望快速失敗,給用戶一個友好提示。 因此需要提前編寫好失敗時的降級處理邏輯,要使用@HystixCommond
來完成:要注意,因爲熔斷的降級邏輯方法必須跟正常邏輯方法保證:相同的參數列表和返回值聲的。返回User對象沒有太大意義,一 般會返回友好提示。所以我們把findById
的方法改造爲返回String
, 反正也是Json數據。這樣失敗邏輯中返回一個錯誤說明會比較方便。
@HystrixCommand(fallbackMethod = "findByIdFallback")
用來聲明一一個降級邏輯的方法當user-service正常提供服務時,訪問與以前一致。但是當我們將user-service停機時,會發現頁面返回了降級處理
@RestController
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "findByIdFallback")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id")
//失敗容錯指令,開啓服務降級線程隔離的處理,要求這兩個方法返回值和參數列表必須相同,因爲服務降級的處理邏輯必須和原因的處理保持一致
//@HystrixCommand(fallbackMethod = "findByIdFallback")
//不同接口不同方法的超時等待時長應該是不一樣的,@HystrixCommand可以設置,配置在類上,則是此類的所有方法生效,要想全局起作用,那就在application.yml中配置
@HystrixCommand(fallbackMethod = "findByIdFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})
public String findById(@PathVariable("id")Integer id){
String url="http://user-service/user/findById/"+id;
String user = restTemplate.getForObject(url, String.class);
return user;
}
public String findByIdFallback(Integer id){
return "不好意思!服務器正忙!!!!";
}
//默認fallback方法,不需要再有參數了
public String findByIdFallback(){
return "不好意思!服務器正忙!!!!";
}
}
application.yaml配置失敗容錯指令
# 配置全局等待超時時長爲3秒,默認是1秒
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 只針對某個服務或方法進行配置等待超時時長,這樣就不用在方法中或類中配置
user-service:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
明確:線程隔離是在內部實現,內部已經利用線程池進行了處理,我們只需要去配置他的超時時長,並且開啓他的功能即可。
測試
服務提供方user-service線程休眠,模擬線程出現異常。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findById(Integer id){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}//模擬等待時長2秒
return userMapper.selectByPrimaryKey(id);
}
public List<User> findAll(){ return userMapper.selectAll(); }
}
啓動服務,一切正常。
顯示一個友好的提示信息。