一欣舵、 什么是災(zāi)難性的雪崩效應(yīng)
在微服務(wù)架構(gòu)的項目中止剖,尤其是中大型項目亮靴,肯定會出現(xiàn)一個服務(wù)調(diào)用其他的服務(wù)馍盟,其他服務(wù)又調(diào)用別的服務(wù),服務(wù)和服務(wù)之間形成了一種鏈?zhǔn)降恼{(diào)用關(guān)系茧吊。
當(dāng)少量請求時贞岭,對于整個服務(wù)鏈條是沒有過多的影響的八毯。
雖然每個服務(wù)的請求都是少量的,但是最終都訪問服務(wù)T瞄桨。所以對于服務(wù)T來說請求量就是比較大的话速。所在的服務(wù)器CPU壓力比較高。
當(dāng)其中某一個服務(wù)突然遇到大量請求時芯侥。整個鏈條上所有服務(wù)負(fù)載驟增泊交。
導(dǎo)致服務(wù)U和服務(wù)T的負(fù)載過高。運行性能下降柱查。會導(dǎo)致其他調(diào)用服務(wù)U和服務(wù)T的鏈條出現(xiàn)問題廓俭。從而所有的項目可能都出現(xiàn)的問題。
這種情況就稱為災(zāi)難性的雪崩效應(yīng)唉工。
造成災(zāi)難性雪崩效應(yīng)的原因研乒,可以簡單歸結(jié)為下述三種:
服務(wù)提供者(Application Service)不可用。如:硬件故障淋硝、程序BUG雹熬、緩存擊穿、并發(fā)請求量過大等奖地。
重試加大流量橄唬。如:用戶重試、代碼重試邏輯等参歹。
服務(wù)調(diào)用者(Application Client)不可用仰楚。如:同步請求阻塞造成的資源耗盡等。
雪崩效應(yīng)最終的結(jié)果就是:服務(wù)鏈條中的某一個服務(wù)不可用犬庇,導(dǎo)致一系列的服務(wù)不可用僧界,最終造成服務(wù)邏輯崩潰。這種問題造成的后果臭挽,往往是無法預(yù)料的捂襟。
二、如何防止災(zāi)難性雪崩效應(yīng)
降級
超時降級欢峰、資源不足時(線程或信號量)降級葬荷,降級后可以配合降級接口返回托底數(shù)據(jù)。實現(xiàn)一個fallback方法, 當(dāng)請求后端服務(wù)出現(xiàn)異常的時候, 可以使用fallback方法返回的值.
保證:服務(wù)出現(xiàn)問題整個項目還可以繼續(xù)運行纽帖。
熔斷
當(dāng)失敗率(如因網(wǎng)絡(luò)故障/超時造成的失敗率高)達(dá)到閥值自動觸發(fā)降級宠漩,熔斷器觸發(fā)的快速失敗會進(jìn)行快速恢復(fù)。
通俗理解:熔斷就是具有特定條件的降級懊直,當(dāng)出現(xiàn)熔斷時在設(shè)定的時間內(nèi)容就不在請求Application Service了扒吁。所以在代碼上熔斷和降級都是一個注解
保證:服務(wù)出現(xiàn)問題整個項目還可以繼續(xù)運行。
請求緩存
提供了請求緩存室囊。服務(wù)A調(diào)用服務(wù)B雕崩,如果在A中添加請求緩存魁索,第一次請求后走緩存了,就不在訪問服務(wù)B了盼铁,即使出現(xiàn)大量請求時粗蔚,也不會對B產(chǎn)生高負(fù)載。
請求緩存可以使用Spring Cache實現(xiàn)捉貌。
保證:減少對Application Service的調(diào)用支鸡。
請求合并
提供請求合并。當(dāng)服務(wù)A調(diào)用服務(wù)B時趁窃,設(shè)定在5毫秒內(nèi)所有請求合并到一起,對于服務(wù)B的負(fù)載就會大大減少急前,解決了對于服務(wù)B負(fù)載激增的問題醒陆。
保證:減少對Application Service的調(diào)用。
隔離
隔離分為線程池隔離和信號量隔離裆针。通過判斷線程池或信號量是否已滿刨摩,超出容量的請求直接降級,從而達(dá)到限流的作用世吨。
三澡刹、Hystrix簡介
- 在Spring Cloud中解決災(zāi)難性雪崩效應(yīng)就是通過Spring Cloud Netflix Hystrix實現(xiàn)的。
- Hystrix [h?st'r?ks]耘婚,中文含義是豪豬罢浇,因其背上長滿棘刺,從而擁有了自我保護(hù)的能力沐祷。本文所說的Hystrix(中文:斷路器)是Netflix開源的一款容錯框架嚷闭,同樣具有自我保護(hù)能力。
- 通俗解釋:Hystrix就是保證在高并發(fā)下即使出現(xiàn)問題也可以保證程序繼續(xù)運行的一系列方案赖临。作用包含兩點:容錯和限流胞锰。
- 在Spring cloud中處理服務(wù)雪崩效應(yīng),都需要依賴hystrix組件兢榨。在Application Client應(yīng)用的pom文件中都需要引入下述依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
四嗅榕、降級
- 降級是指,當(dāng)請求超時吵聪、資源不足等情況發(fā)生時進(jìn)行服務(wù)降級處理凌那,不調(diào)用真實服務(wù)邏輯,而是使用快速失斉怠(fallback)方式直接返回一個托底數(shù)據(jù)案怯,保證服務(wù)鏈條的完整,避免服務(wù)雪崩澎办。
- 解決服務(wù)雪崩效應(yīng)嘲碱,都是避免application client請求application service時金砍,出現(xiàn)服務(wù)調(diào)用錯誤或網(wǎng)絡(luò)問題。處理手法都是在application client中實現(xiàn)麦锯。我們需要在application client相關(guān)工程中導(dǎo)入hystrix依賴信息恕稠。并在對應(yīng)的啟動類上增加新的注解@EnableCircuitBreaker,這個注解是用于開啟hystrix熔斷器的扶欣,簡言之鹅巍,就是讓代碼中的hystrix相關(guān)注解生效。
Dome
<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-hystrix</artifactId>
</dependency>
spring:
application:
name: fallback-demo
eureka:
client:
service-url:
defaultZone: http://eurekaserver1:8761/eureka/
server:
port: 8081
@Configuration
public class RibbonConfig {
//此處使用@LoadBalancer方式快捷配置負(fù)載均衡料祠。
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
接下來就是service骆捧、controller、application啟動
public interface DemoService {
String test();
}
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "myFallback")
@Override
public String test() {
String result = restTemplate.postForObject("http://application-service-demo/demo", null, String.class);
System.out.println(result);
return result;
}
public String myFallback(){
return "托底數(shù)據(jù)";
}
}
@Controller
public class FallbackController {
@Autowired
private DemoService demoService;
@RequestMapping("/demo")
@ResponseBody
public String demo(){
return demoService.test();
}
}
@SpringBootApplication
@EnableCircuitBreaker
public class ApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(ApplicationclientdemoApplication.class, args);
}
}
五髓绽、熔斷
- 當(dāng)一定時間內(nèi)敛苇,異常請求比例(請求超時、網(wǎng)絡(luò)故障顺呕、服務(wù)異常等)達(dá)到閥值時枫攀,啟動熔斷器,熔斷器一旦啟動株茶,則會停止調(diào)用具體服務(wù)邏輯来涨,通過fallback快速返回托底數(shù)據(jù),保證服務(wù)鏈的完整启盛。
- 熔斷有自動恢復(fù)機(jī)制蹦掐,如:當(dāng)熔斷器啟動后,每隔5秒驰徊,嘗試將新的請求發(fā)送給Application Service笤闯,如果服務(wù)可正常執(zhí)行并返回結(jié)果,則關(guān)閉熔斷器棍厂,服務(wù)恢復(fù)颗味。如果仍舊調(diào)用失敗,則繼續(xù)返回托底數(shù)據(jù)牺弹,熔斷器持續(xù)開啟狀態(tài)浦马。
-
降級是出錯了返回托底數(shù)據(jù),而熔斷是出錯后如果開啟了熔斷將會一定時間不在訪問application service
1张漂、屬性
HystrixCommand里面的屬性
熔斷的實現(xiàn)是在調(diào)用遠(yuǎn)程服務(wù)的方法上增加@HystrixCommand注解晶默。當(dāng)注解配置滿足則開啟或關(guān)閉熔斷器。
@HystrixProperty的name屬性取值可以使用HystrixPropertiesManager常量航攒,也可以直接使用字符串進(jìn)行操作磺陡。
CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
是否開啟熔斷策略。默認(rèn)值為true。
CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
單位時間內(nèi)(默認(rèn)10s內(nèi))币他,請求超時數(shù)超出則觸發(fā)熔斷策略坞靶。默認(rèn)值為20次請求數(shù)。通俗說明:單位時間內(nèi)容要判斷多少次請求蝴悉。
EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS
"execution.isolation.thread.timeoutInMilliseconds"
設(shè)置單位時間彰阴,判斷circuitBreaker.requestVolumeThreshold的時間單位,默認(rèn)10秒拍冠。單位毫秒尿这。
CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowInMilliseconds";
當(dāng)熔斷策略開啟后,延遲多久嘗試再次請求遠(yuǎn)程服務(wù)庆杜。默認(rèn)為5秒射众。單位毫秒。這5秒直接執(zhí)行fallback方法晃财,不在請求遠(yuǎn)程application service责球。
CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
單位時間內(nèi),出現(xiàn)錯誤的請求百分比達(dá)到限制拓劝,則觸發(fā)熔斷策略。默認(rèn)為50%嘉裤。
CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
是否強(qiáng)制開啟熔斷策略郑临。即所有請求都返回fallback托底數(shù)據(jù)。默認(rèn)為false屑宠。
CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
是否強(qiáng)制關(guān)閉熔斷策略厢洞。即所有請求一定調(diào)用遠(yuǎn)程服務(wù)。默認(rèn)為false典奉。
@HystrixCommand(fallbackMethod = "myFallback",commandProperties = {
// 條件一: 請求數(shù)量到達(dá)3個
@HystrixProperty(name= "circuitBreaker.requestVolumeThreshold",value="3"),
//可以這樣寫@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD ,value="3")
// 判斷時間躺翻,沒10秒作為一個判斷單位
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="10000"),
// 條件二: 失敗了到達(dá)50%
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50"),
// 結(jié)果: 開啟熔斷后,30秒不在請求遠(yuǎn)程服務(wù)
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "30000")
})
六卫玖、請求緩存
Hystrix為了降低訪問服務(wù)的頻率公你,支持將一個請求與返回結(jié)果做緩存處理。如果再次請求的URL沒有變化假瞬,那么Hystrix不會請求服務(wù)陕靠,而是直接從緩存中將結(jié)果返回。這樣可以大大降低訪問服務(wù)的壓力脱茉。
Hystrix自帶緩存剪芥。有兩個缺點:
- 是一個本地緩存。在集群情況下緩存是不能同步的琴许。
- 不支持第三方緩存容器税肪。Redis,memcached不支持的。
所以可以利用spring cache益兄。實現(xiàn)請求緩存锻梳。
在降級處理的代碼基礎(chǔ)上完成下面變化。
添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
修改配置文件
添加redis的配置偏塞,此處使用的是redis單機(jī)版唱蒸。如果是redis集群使用spring.redis.cluster.nodes進(jìn)行配置。
spring:
redis:
host: ipaddress #你的redis的IP地址
注意添加?xùn)|西
- 在啟動類上添加@EnableCaching注解
- 在service實現(xiàn)類方法上面額外在添加一個注解灸叼。
@Cacheable(key = "'key值'",cacheNames = "緩存名字")
七神汹、請求合并
沒有請求合并
Application Service 負(fù)載是Application Client發(fā)送請求的總數(shù)量
請求合并
把一段時間范圍內(nèi)的所有請求合并為一個請求。大大的降低了Application Service 負(fù)載古今。
什么情況下使用請求合并
在微服務(wù)架構(gòu)中屁魏,我們將一個項目拆分成很多個獨立的項目,這些獨立的項目通過遠(yuǎn)程調(diào)用來互相配合工作捉腥,但是氓拼,在高并發(fā)情況下,通信次數(shù)的增加會導(dǎo)致總的通信時間增加抵碟,同時桃漾,線程池的資源也是有限的,高并發(fā)環(huán)境會導(dǎo)致有大量的線程處于等待狀態(tài)拟逮,進(jìn)而導(dǎo)致響應(yīng)延遲撬统,為了解決這些問題,我們需要來了解Hystrix的請求合并敦迄。
請求合并的缺點
設(shè)置請求合并之后恋追,本來一個請求可能5ms就搞定了,但是現(xiàn)在必須再等10ms看看還有沒有其他的請求一起的罚屋,這樣一個請求的耗時就從5ms增加到15ms了苦囱,不過,如果我們要發(fā)起的命令本身就是一個高延遲的命令脾猛,那么這個時候就可以使用請求合并了撕彤,因為這個時候時間窗的時間消耗就顯得微不足道了,另外高并發(fā)也是請求合并的一個非常重要的場景尖滚。
請求合并參數(shù)介紹
- @HystrixCollapser 進(jìn)行請求合并
- batchMethod:處理請求合并的方法
- scope - 合并請求的請求作用域喉刘。可選值有g(shù)lobal和request漆弄。
global代表所有的請求線程都可以等待可合并睦裳。 常用
request代表一個請求線程中的多次遠(yuǎn)程服務(wù)調(diào)用可合 - timerDelayInMilliseconds:等待時長,默認(rèn)10毫秒撼唾。
- maxRequestInBatch:最大請求合并數(shù)量廉邑。
- @HystrixCommand 處理請求合并的方法必須有此注解。
- 實現(xiàn)類中client(String)方法一旦被@HystrixCollapser標(biāo)記,方法就不會被執(zhí)行蛛蒙,方法體中為空即可糙箍。直接執(zhí)行batchMethod對應(yīng)的方法。
- batchMethod方法返回值順序和傳遞進(jìn)來的參數(shù)順序有關(guān)系的牵祟。
注意:
在實際測試中scope使用默認(rèn)值REQUEST會出現(xiàn)空指針異常深夯,請換成GLOBAL
Future<String> client(String name);
//上面是接口
@Override
@HystrixCollapser(batchMethod = "myBatchMethod",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL
,collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value="10"),@HystrixProperty(name="maxRequestsInBatch",value = "200")})
public Future<String> client(String name) {
System.out.println("client方法,有請求合并時將不支持這個方法");
return null;
}
@HystrixCommand
public List<String> myBatchMethod(List<String> name){
System.out.println("傳遞過去的參數(shù):"+name);
List<String> list = restTemplate.postForObject("http://APPLICATION-SERVICE/service2", name, List.class);
return list;
}
八诺苹、隔離
1咕晋、線程隔離
1.1 為什么使用線程池隔離
沒有線程池隔離的時候可能因為某個接口的高并發(fā)導(dǎo)致其他接口也出現(xiàn)問題。
當(dāng)使用線程池隔離收奔。不同接口有著自己獨立的線程池
即使某個線程池都被占用掌呜,也不影響其他線程。
1.2 Hystrix的線程池隔離
Hystrix采用Bulkhead Partition艙壁隔離技術(shù)坪哄。
艙壁隔離指的的是講船體內(nèi)部分為多個隔艙质蕉,一旦其中某幾個隔艙發(fā)生破損進(jìn)水,水流不會在其他艙壁中流動翩肌,從而保證船艙依然具有足夠的浮力和穩(wěn)定性模暗,降低沉船危險。
1.3線程池隔離的優(yōu)缺點
- 優(yōu)點:
- 任何一個服務(wù)都會被隔離在自己的線程池內(nèi)念祭,即使自己的線程池資源填滿也不會影響其他服務(wù)汰蓉。
- 當(dāng)依賴的服務(wù)重新恢復(fù)時,可通過清理線程池棒卷,瞬間恢復(fù)服務(wù)的調(diào)用。但是如果是tomcat線程池被填滿祝钢,再恢復(fù)就會很麻煩比规。
- 每個都是獨立線程池。一定程度上解決了高并發(fā)問題拦英。
- 由于線程池中線程個數(shù)是有限制蜒什,所以也解決了限流問題。
- 缺點:
- 增加了CPU開銷疤估。因為不僅僅有Tomcat的線程池灾常,還需要有Hystrix線程池。
- 每個操作都是獨立的線程铃拇,就有排隊钞瀑、調(diào)度和上下文切換等問題。
代碼:
//代碼只是把這個注解加到實現(xiàn)類的方法上面就好了
//注解里面的意思是一個服務(wù)名為groupKey = "jqk"慷荔,服務(wù)接口是commandKey = "abc",雖然自定義但是一般為方法名,線程名稱是threadPoolKey = "jqk"
@HystrixCommand(groupKey = "jqk",commandKey = "abc",threadPoolKey = "jqk",threadPoolProperties = {
//threadPoolProperties線程池里面的配置在刺,最大并發(fā)也是最大數(shù)量name="coreSize",value="8"
@HystrixProperty(name="coreSize",value="8"),
//設(shè)置了最大隊列長度name="maxQueueSize",value="5"
@HystrixProperty(name="maxQueueSize",value="5"),
//設(shè)置了線程最大存活時間name="keepAliveTimeMinutes",value="2"單位分鐘
@HystrixProperty(name="keepAliveTimeMinutes",value="2"),
//設(shè)置了拒絕請求臨界值為name="queueSizeRejectionThreshold",value="5",在maxQueueSize的基礎(chǔ)之上再加這個值設(shè)置數(shù)量超過就拒絕請求響應(yīng)了
@HystrixProperty(name="queueSizeRejectionThreshold",value="5")
})
@Override
public String thread() {
System.out.println(Thread.currentThread().getName());
return "thread1";
}
@Override
public String thread2() {
System.out.println(Thread.currentThread().getName());
return "thread2";
}
參數(shù)說明
2壹士、信號量隔離
2.1信號量是什么
- java.util.concurrent.Semaphore用來控制可同時并發(fā)的線程數(shù)。通過構(gòu)造方法指定內(nèi)部虛擬許可的數(shù)量偿警。每次線程執(zhí)行操作時先通過acquire方法獲得許可躏救,執(zhí)行完畢再通過release方法釋放許可。如果無可用許可螟蒸,那么acquire方法將一直阻塞盒使,直到其它線程釋放許可。
- 如果采用信號量隔離技術(shù)尿庐,每接收一個請求忠怖,都是服務(wù)自身線程去直接調(diào)用依賴服務(wù),信號量就相當(dāng)于一道關(guān)卡抄瑟,每個線程通過關(guān)卡后凡泣,信號量數(shù)量減1,當(dāng)為0時不再允許線程通過皮假,而是直接執(zhí)行fallback邏輯并返回鞋拟,說白了僅僅做了一個限流。
代碼演示
@HystrixCommand(commandProperties = {
//.EXECUTION_ISOLATION_STRATEGY默認(rèn)就是線程池隔離惹资,現(xiàn)在改為SEMAPHORE信號隔離
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value="SEMAPHORE"),
//信號最大并發(fā)度
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="10")
},fallbackMethod = "jqk")
@Override
public String semaphore() {
System.out.println("執(zhí)行了贺纲,訪問service");
參數(shù)說明
線程池隔離和信號量隔離
限流說明
- 在高并發(fā)的系統(tǒng)中,往往需要在系統(tǒng)中做限流褪测,一方面是為了防止大量的請求使服務(wù)器過載猴誊,導(dǎo)致服務(wù)不可用,另一方面是為了防止網(wǎng)絡(luò)攻擊侮措。
- 通過Hystrix的線程池隔離和信號量隔離控制了線程數(shù)量也就實現(xiàn)了限流效果懈叹。