引子
幸福很簡單:
今天項目半年規划被通過,終於可以早點下班。先坐公交,全程開着燈,買了了幾天的書竟然有時間看了。半小時后,公交到站,換乘大巴車。車還等着上人的功夫,有昏暗的燈光,可以繼續看會兒書。過會兒車跑起來了,燈關了。我合上書,頭靠着車窗,眼睛看着窗外,腦子想着怎么把書里的東西用到工作中進行知行合一。想着想着出了神,突然聽到報我們小區的名字,趕緊下了車,剛好沒坐過站。
回家一看,那個聲稱今天會下班很晚的人果然比我還晚。邊洗漱邊想着上周末,和小鮮肉一起看了動畫片。小鮮肉非要我買培根。因為他想像動畫片里一樣用兩個荷包蛋做眼睛,培根做嘴巴。於是晚上睡覺的時候,我下單買了培根,早上7點快遞送了來。我就和小鮮肉一起做早餐。他說想吃糖口味的蛋,於是我們改良了一下把荷包蛋眼睛變成攤雞蛋大臉。本來小鮮肉說只要一片培根。我想一包都打開了,干脆一起煎了。結果因為是自己動手,小鮮肉只好多吃,結果吃培根吃的一天都感覺油膩。想着這周,一包培根要分到一天三頓里,加上配菜,和小鮮肉一起做出三種花樣來。關鍵是小鮮肉到周末比我還忙,8點開始跆拳道,一天各種興趣班。他想自己做早餐得早起。
洗漱完,吹完頭發。看看洗澡水又燒的差不多了。這次終於輪到我發那條消息:“洗澡水夠了”。然后拔掉燒水的電源確保安全。竟然有時間貼上面膜再看會書,滿足。
原來幸福就是:其實很忙,但是能擠出點時間干點自己的事情,提升提升自己。
hystrix隔離原理
hystrix可以完成隔離、限流、熔斷、降級這些常用保護功能。這四個功能可以這么來理解:
hystrix的隔離分為線程池隔離和信號量隔離。
信號量隔離原理
信號量隔離就是hystrix的限流功能。雖然名字叫隔離,實際上它是通過信號量來實現的。而信號量說白了就是個計數器。計數器計算達到設定的閾值,直接就做異常處理。
ratelimiter的令牌桶算法和漏桶算法,都是直接對請求量來計數。只是令牌桶算法可以將前面一段時間沒有用掉的請求量允許余額拿過繼續用。而漏桶算法一段時間就允許這么多,前面沒用掉的也不能用了。
而hystrix信號量隔離限制的是tomcat等Web容器線程數,一段時間僅僅能支持這么多。多余的請求再來請求線程資源,就被拒絕了。所以是一種“曲徑通幽”的限流方式。因為實際是通過隔離了部分容器線程資源,也算是一種隔離方式。
線程池隔離原理
信號量隔離只是起了個限制作用,它的保護能力有限:如果下游服務有問題,長時間不返回結果。本身信號量隔離對這個單個請求是起不到任何作用的。它只能限制這樣的請求太多了就拒絕,不讓整個服務掛。
為了解決這個問題,hystrix又產生了線程池隔離。這種隔離方式是通過引入額外線程的方式。對原來的web容器線程做管理控制:如果一個線程超時未返回,則熔斷。既然引入額外的線程就涉及線程池管理、線程的上下文切換這些額外的開銷。所以相比信號量隔離,線程池隔離成本更高。
熔斷原理
隔離不但可以做保護,還可以做統計:成功了多少失敗了多少。既然有統計數據了,它就可以進一步處理:失敗太多了,說明現在有問題,執行完了再發現失敗太浪費資源,干脆就先不讓worker線程執行了。過段時間再試試。這就是斷路器模式,也就是熔斷的原理。
降級原理
任何異常需要熔斷的場景,為的都是反正都是錯,干脆把這資源省了。直接返回一個預定的錯誤。這個熔斷后返回設定錯誤的過程就是降級。
資源保護的流程
從上面來看所謂的hystrix的四大功能:限流、隔離、熔斷和降級。只是完成了一整個對資源保護的生命周期。來看看對應的BPMN流程圖:
信號量隔離的流程
線程池隔離的流程
現在大家請回答我一個問題:我上面說的是對的嗎?
hystrix隔離驗證
采用淘金式的思維,不要別人說什么都信。網上很多技術博客里說的都是錯的。我的文章也有可能是錯的,事實上我說的很多東西都帶有自己腦補的成分。
既然不確定是否是對的,就要去驗證。先驗證
1>hystrix隔離確實能限制資源
2>信號量隔離采用的Web容器的線程池,而線程池隔離采用的是自己獨立的線程池。
本次驗證,Web容器使用的是spring boot內嵌的jetty。代碼已經上傳github:
https://github.com/xiexiaojing/yuna
信號量隔離驗證
隔離hystrix配置
import com.netflix.hystrix.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
public class DemoHystrixCommand extends HystrixCommand<String> {
private static final Logger logger = LoggerFactory.getLogger(DemoHystrixCommand.class);
private String poolName;
public DemoHystrixCommand() {
super(Setter.withGroupKey(
//服務分組
HystrixCommandGroupKey.Factory.asKey("DemoGroup"))
//線程分組
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("DemoPool"))
//線程池配置
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withCoreSize(2)
.withKeepAliveTimeMinutes(5)
.withMaxQueueSize(2)
.withQueueSizeRejectionThreshold(10))
//線程池隔離
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
)
);
}
protected String run() throws Exception {
logger.info(poolName + ":我傷心我無奈,可是我默默等待");
TimeUnit.MILLISECONDS.sleep(100);
return poolName + "-run:緣分就是一生的等待";
}
public void setPoolName(String poolName) {
this.poolName = poolName;
}
}
調用方
import com.brmayi.yuna.util.DemoHystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
public class HystrixController {
private ApplicationContext applicationContext;
public String hystrix(String poolName) throws Exception {
DemoHystrixCommand demoHystrixCommand = applicationContext.getBean(DemoHystrixCommand.class);
demoHystrixCommand.setPoolName(poolName);
return demoHystrixCommand.execute();
}
}
驗證
多點幾次
多點幾次
看日志,日志前綴里Web容器的其他日志線程號和請求hystrix的線程號規則一致,可說明是Web容器的線程。
注意看,不管是倩倩還是萍兒,它們的線程數都沒有超過最大線程數
線程池隔離驗證
只要將隔離hystrix配置的
HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE
改成
HystrixCommandProperties.ExecutionIsolationStrategy.THREAD
重啟后重復上面驗證步驟
看線程池名變成了隔離hystrix配置的線程名規則!並且不管是倩倩還是萍兒,它們的線程數都沒有超過最大線程數。
以上實驗說明
1>hystrix隔離確實能限制資源
2>信號量隔離采用的Web容器的線程池,而線程池隔離采用的是自己獨立的線程池。
成立。
其他部分限於篇幅,我就不驗證了。這里用到了打日志的方法驗證線程池情況。如果在生產環境,實際上我們是需要對線程池情況做監控的。可以使用java.lang.management包里的工具注冊監控。我們平時使用falcon這樣的工具來查看,原理也是先通過java.lang.management包里的工具注冊上報信息到服務端采集的。如果自己想查看監控結果,可以用jdk自帶的jvisualvm安裝一個com-sun-tools-visualvm-modules-mbeans.nbm插件來看。
總結
本篇文章的驗證部分很粗糙,限於篇幅,沒有把所有需要驗證的點覆蓋全。想驗證我花的hystrix資源保護生命周期的圖,至少要結合源碼和驗證兩方面。先當留作業了,有時間我把詳細過程補上。