服務(wù)器架構(gòu)-hystrix

微服務(wù)

微服務(wù)就是一個(gè)獨(dú)立的可部署的占有自己進(jìn)程的一個(gè)實(shí)體, 我們一般把這個(gè)實(shí)體稱之為服務(wù)。 微服務(wù)的核心思維和SRP原則是一樣的: 聚合所有因?yàn)橄嗤蚨淖兊脑兀?分割那些因?yàn)椴挥迷蚨淖兊脑亍?只不過是站在更加宏觀的角度來思考的。 一個(gè)微服務(wù)是完全自治的,它對(duì)外提供API, 并且自身的改變(不包括API的改變)不會(huì)影響依賴者的改變。

微服務(wù)架構(gòu)風(fēng)格是一種使用一套小服務(wù)來開發(fā)單個(gè)應(yīng)用的方式途徑,每個(gè)服務(wù)運(yùn)行在自己的進(jìn)程中,并使用輕量級(jí)機(jī)制通信,通常是HTTP API, 也可以是RPC(Remote Procedure Call—遠(yuǎn)程過程調(diào)用),這些服務(wù)基于業(yè)務(wù)能力構(gòu)建,并能夠通過自動(dòng)化部署機(jī)制來獨(dú)立部署,這些服務(wù)使用不同的編程語言實(shí)現(xiàn),以及不同數(shù)據(jù)存儲(chǔ)技術(shù),并保持最低限度的集中式管理。

一個(gè)微服務(wù)一般完成某個(gè)特定的功能,比如下單管理、客戶管理等等。每一個(gè)微服務(wù)都是微型六角形應(yīng)用,都有自己的業(yè)務(wù)邏輯和適配器。一些微服務(wù)還 會(huì)發(fā)布API給其它微服務(wù)和應(yīng)用客戶端使用。

微服務(wù)的好處:

  • 服務(wù)的異構(gòu)性 - 不同的服務(wù)可以使用不同的且適合當(dāng)前服務(wù)的技術(shù)棧,這樣的話公司也有機(jī)會(huì)在某些服務(wù)上嘗試一些新的技術(shù)

  • 彈性 - 可以對(duì)錯(cuò)誤進(jìn)行隔離, 可以通過用多臺(tái)機(jī)器增加可用性。

  • 縮放 - 可以通過用多臺(tái)機(jī)器分擔(dān)LOAD

  • 易于部署 - 部署的最小單元縮減為單個(gè)服務(wù)

  • 組織架構(gòu)的安 - 就算是大公司,人員年分布在不同的地點(diǎn)甚至是國家,通過微服務(wù),我們可以把開發(fā)相同微服務(wù)的人員放在一塊。

  • 可組合性 - 通過編排微服務(wù),可以很方便的搭建客戶化的需求。

  • 可替代性 - 微服務(wù)一般都很小,重寫的難度沒有那么大

微服務(wù)架構(gòu)的不足

  • 『微服務(wù)』強(qiáng)調(diào)了服務(wù)大小,實(shí)際上,有一些開發(fā)者鼓吹建立稍微大一些的,10-100 LOC服務(wù)組。盡管小服務(wù)更樂于被采用,但是不要忘了這只是終端的選擇而不是最終的目的。微服務(wù)的目的是有效的拆分應(yīng)用,實(shí)現(xiàn)敏捷開發(fā)和部署。

  • 微服務(wù)應(yīng)用是分布式系統(tǒng),由此會(huì)帶來固有的復(fù)雜性。開發(fā)者需要在RPC或者消息傳遞之間選擇并完成進(jìn)程間通訊機(jī)制。更甚于,他們必須寫代碼來處理消息傳遞中速度過慢或者不可用等局部失效問題。當(dāng)然這并不是什么難事,但相對(duì)于單體式應(yīng)用中通過語言層級(jí)的方法或者進(jìn)程調(diào)用,微服務(wù)下這種技術(shù)顯得更復(fù)雜一些。

  • 另一個(gè)關(guān)于微服務(wù)的挑戰(zhàn)來自于分區(qū)的數(shù)據(jù)庫架構(gòu)。商業(yè)交易中同時(shí)給多個(gè)業(yè)務(wù)分主體更新消息很普遍。這種交易對(duì)于單體式應(yīng)用來說很容易,因?yàn)橹挥幸粋€(gè)數(shù)據(jù)庫。在微服務(wù)架構(gòu)應(yīng)用中,需要更新不同服務(wù)所使用的不同的數(shù)據(jù)庫。使用分布式交易并不一定是好的選擇,不僅僅是因?yàn)镃AP理論,還因?yàn)榻裉旄邤U(kuò)展性的NoSQL數(shù)據(jù)庫和消息傳遞中間件并不支持這一需求。最終你不得不使用一個(gè)最終一致性的方法,從而對(duì)開發(fā)者提出了更高的要求和挑戰(zhàn)。

  • 測(cè)試一個(gè)基于微服務(wù)架構(gòu)的應(yīng)用也是很復(fù)雜的任務(wù)。比如,采用流行的Spring Boot架構(gòu),對(duì)一個(gè)單體式web應(yīng)用,測(cè)試它的REST API,是很容易的事情。反過來,同樣的服務(wù)測(cè)試需要啟動(dòng)和它有關(guān)的所有服務(wù)(至少需要這些服務(wù)的stubs)。再重申一次,不能低估了采用微服務(wù)架構(gòu)帶來的復(fù)雜性。

  • 微服務(wù)架構(gòu)模式應(yīng)用的改變將會(huì)波及多個(gè)服務(wù)。比如,假設(shè)你在完成一個(gè)案例,需要修改服務(wù)A、B、C,而A依賴B,B依賴C。在單體式應(yīng)用中,你只需要改變相關(guān)模塊,整合變化,部署就好了。對(duì)比之下,微服務(wù)架構(gòu)模式就需要考慮相關(guān)改變對(duì)不同服務(wù)的影響。比如,你需要更新服務(wù)C,然后是B,最后才是A,幸運(yùn)的是,許多改變一般只影響一個(gè)服務(wù),而需要協(xié)調(diào)多服務(wù)的改變很少。

hystrix 原理與應(yīng)用

微服務(wù)是解決復(fù)雜服務(wù)的一個(gè)方案,在功能不變的情況下,對(duì)一個(gè)復(fù)雜的單體服務(wù)分解為多個(gè)可管理的分支。每個(gè)服務(wù)作為輕量的子服務(wù),通過RPC實(shí)現(xiàn)服務(wù)間的關(guān)聯(lián),將服務(wù)簡單化。每個(gè)服務(wù)根據(jù)自己的需要選擇技術(shù)棧,互不影響,方便開發(fā)、維護(hù)。例如S劃分為a,b,c。微服務(wù)的好處是有效的拆分應(yīng)用,實(shí)現(xiàn)敏捷開發(fā)和部署。
微服務(wù)一系列優(yōu)勢(shì)下,也給微服務(wù)的管理和穩(wěn)定性帶來挑戰(zhàn),比如一個(gè)服務(wù)依賴30個(gè)微服務(wù),每個(gè)微服務(wù)的可用性是99.999%,在不加任何管理的情況下,該聚合服務(wù)的可用性將是99.999%的30次方=99.97%,系統(tǒng)的可用性直接降了兩個(gè)數(shù)量級(jí)達(dá)到三個(gè)九。
且由于依賴的傳遞性,很容易產(chǎn)生雪崩效應(yīng)。

![隔離.png](https://upload-images.jianshu.io/upload_images/4248951-6adc1d85fa228338.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

一個(gè)應(yīng)用中,任意一個(gè)點(diǎn)的不可用或者響應(yīng)延時(shí)都有可能造成服務(wù)不可用
更可怕的是,被hang住的請(qǐng)求會(huì)很快耗盡系統(tǒng)的資源,當(dāng)該類請(qǐng)求越來越多,占用的計(jì)算機(jī)資源越來越多的時(shí)候,會(huì)導(dǎo)致系統(tǒng)瓶頸出現(xiàn),造成其他的請(qǐng)求同樣不可用,最終導(dǎo)致業(yè)務(wù)系統(tǒng)崩潰,又稱:雪崩效應(yīng)
造成雪崩原因可以歸結(jié)為以下三個(gè):
? 服務(wù)提供者不可用(硬件故障,程序Bug,緩存擊穿,用戶大量請(qǐng)求)
? 重試加大流量(用戶重試,代碼邏輯重試)
? 服務(wù)調(diào)用者不可用(同步等待造成的資源耗盡)
? 最終的結(jié)果就是一個(gè)服務(wù)不可用導(dǎo)致一系列服務(wù)的不可用,而往往這種后果往往無法預(yù)料的。

hystrix實(shí)現(xiàn)原理

hystrix語義為“豪豬”,具有自我保護(hù)的能力。hystrix的出現(xiàn)即為解決雪崩效應(yīng),它通過四個(gè)方面的機(jī)制來解決這個(gè)問題
? 隔離(線程池隔離和信號(hào)量隔離):限制調(diào)用分布式服務(wù)的資源使用,某一個(gè)調(diào)用的服務(wù)出現(xiàn)問題不會(huì)影響其他服務(wù)調(diào)用。
? 優(yōu)雅的降級(jí)機(jī)制:超時(shí)降級(jí)、資源不足時(shí)(線程或信號(hào)量)降級(jí),降級(jí)后可以配合降級(jí)接口返回托底數(shù)據(jù)。
? 融斷:當(dāng)失敗率達(dá)到閥值自動(dòng)觸發(fā)降級(jí)(如因網(wǎng)絡(luò)故障/超時(shí)造成的失敗率高),熔斷器觸發(fā)的快速失敗會(huì)進(jìn)行快速恢復(fù)。
? 緩存:提供了請(qǐng)求緩存、請(qǐng)求合并實(shí)現(xiàn)。
? 支持實(shí)時(shí)監(jiān)控、報(bào)警、控制(修改配置)

隔離

隔離.png

(1)線程池隔離模式:使用一個(gè)線程池來存儲(chǔ)當(dāng)前的請(qǐng)求,線程池對(duì)請(qǐng)求作處理,設(shè)置任務(wù)返回處理超時(shí)時(shí)間,堆積的請(qǐng)求堆積入線程池隊(duì)列。這種方式需要為每個(gè)依賴的服務(wù)申請(qǐng)線程池,有一定的資源消耗,好處是可以應(yīng)對(duì)突發(fā)流量(流量洪峰來臨時(shí),處理不完可將數(shù)據(jù)存儲(chǔ)到線程池隊(duì)里慢慢處理)
(2)信號(hào)量隔離模式:使用一個(gè)原子計(jì)數(shù)器(或信號(hào)量)來記錄當(dāng)前有多少個(gè)線程在運(yùn)行,請(qǐng)求來先判斷計(jì)數(shù)器的數(shù)值,若超過設(shè)置的最大線程個(gè)數(shù)則丟棄改類型的新請(qǐng)求,若不超過則執(zhí)行計(jì)數(shù)操作請(qǐng)求來計(jì)數(shù)器+1,請(qǐng)求返回計(jì)數(shù)器-1。這種方式是嚴(yán)格的控制線程且立即返回模式,無法應(yīng)對(duì)突發(fā)流量(流量洪峰來臨時(shí),處理的線程超過數(shù)量,其他的請(qǐng)求會(huì)直接返回,不繼續(xù)去請(qǐng)求依賴的服務(wù))

區(qū)別(兩種隔離方式只能選其一):

... 線程池隔離 信號(hào)量隔離
線程 與調(diào)用線程非相同線程 與調(diào)用線程相同(jetty線程)
開銷 排隊(duì)、調(diào)度、上下文開銷等 無線程切換,開銷低
異步 支持 不支持
并發(fā)支持 支持(最大線程池大小) 支持(最大信號(hào)量上限)

融斷

正常狀態(tài)下,電路處于關(guān)閉狀態(tài)(Closed),如果調(diào)用持續(xù)出錯(cuò)或者超時(shí),電路被打開進(jìn)入熔斷狀態(tài)(Open),后續(xù)一段時(shí)間內(nèi)的所有調(diào)用都會(huì)被拒絕(Fail Fast),一段時(shí)間以后,保護(hù)器會(huì)嘗試進(jìn)入半熔斷狀態(tài)(Half-Open),允許少量請(qǐng)求進(jìn)來嘗試,如果調(diào)用仍然失敗,則回到熔斷狀態(tài),如果調(diào)用成功,則回到電路閉合狀態(tài);

熔斷.png

降級(jí)

在股票市場,熔斷這個(gè)詞大家都不陌生,是指當(dāng)股指波幅達(dá)到某個(gè)點(diǎn)后,交易所為控制風(fēng)險(xiǎn)采取的暫停交易措施。相應(yīng)的,服務(wù)熔斷一般是指軟件系統(tǒng)中,由于某些原因使得服務(wù)出現(xiàn)了過載現(xiàn)象,為防止造成整個(gè)系統(tǒng)故障,從而采用的一種保護(hù)措施,所以很多地方把熔斷亦稱為過載保護(hù)。
大家都見過女生旅行吧,大號(hào)的旅行箱是必備物,平常走走近處綽綽有余,但一旦出個(gè)遠(yuǎn)門,再大的箱子都白搭了,怎么辦呢?常見的情景就是把物品拿出來分分堆,比了又比,最后一些非必需品的就忍痛放下了,等到下次箱子夠用了,再帶上用一用。而服務(wù)降級(jí),就是這么回事,整體資源快不夠了,忍痛將某些服務(wù)先關(guān)掉,待渡過難關(guān),再開啟回來。
二者的目標(biāo)是一致的,目的都是保證上游服務(wù)的穩(wěn)定性。但其關(guān)注的重點(diǎn)并不一樣,融斷對(duì)下層依賴的服務(wù)并不級(jí)(或者說孰輕孰重),一旦產(chǎn)生故障就斷掉;而降級(jí)需要對(duì)下層依賴的業(yè)務(wù)分級(jí),把產(chǎn)生故障的丟了,換一個(gè)輕量級(jí)的方案,是一種退而求其次的方法。

根據(jù)業(yè)務(wù)場景的不同,一般采用以下兩種模式:
第一種(最常用)如果服務(wù)失敗,則我們通過fallback進(jìn)行降級(jí),返回靜態(tài)值。
第二種采用服務(wù)級(jí)聯(lián)的模式,如果第一個(gè)服務(wù)失敗,則調(diào)用備用服務(wù),例如失敗重試或者訪問緩存失敗再去取數(shù)據(jù)庫。服務(wù)級(jí)聯(lián)的目的則是盡最大努力保證返回?cái)?shù)據(jù)的成功性,但如果考慮不充分,則有可能導(dǎo)致級(jí)聯(lián)的服務(wù)崩潰(比如,緩存失敗了,把全部流量打到數(shù)據(jù)庫,瞬間導(dǎo)致數(shù)據(jù)庫掛掉)。因此級(jí)聯(lián)模式,也要慎用,增加了管理的難度。

緩存

不建議使用,對(duì)問題排查會(huì)造成很大的困擾

hystrix應(yīng)用

流程.png
  • 兩個(gè)核心代理HystrixCommand,HystrixObservableCommand,任何依賴的服務(wù)只需要繼承這兩個(gè)類就可以了。其中HystrixObservableCommand使用觀察者模式(不在此介紹范圍之內(nèi),了解請(qǐng)移步RxJava)
  • HystrixCommand 可以采用同步調(diào)用和異步調(diào)用,異步返回Future對(duì)象(還未直接支持CompletebleFuture)
    如果開啟了緩存,則會(huì)根據(jù)GroupKey,Commandkey以及cachedKey確定是否存在緩存(不建議使用)
  • 判斷斷路器是否開啟,開啟則直接調(diào)用getFallback,
  • 判斷是否滿足信號(hào)量隔離或線程池隔離的條件,如果隔離則拋異常
  • 執(zhí)行run方法
  • metrics包含了一個(gè)計(jì)數(shù)器,用來計(jì)算當(dāng)前服務(wù)的狀態(tài),無論是成* 功調(diào)用,還是拋異常都會(huì)記錄數(shù)據(jù)(接下來再詳細(xì)講)
    執(zhí)行降級(jí)策略

代碼實(shí)現(xiàn)

public class GetInfoFromSinaiCommand extends HystrixCommand<List<PoiInfo>> {
    private PoiClient poiClient;
    private List<Integer> poiIds;
    private static final List<String> FIELDS = ImmutableList.of("id", "cate", "subcate");

    public GetInfoFromSinaiCommand(PoiClient poiClient, List<Integer> poiIds) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("sinai"))
                //command配置
                .andCommandKey(HystrixCommandKey.Factory.asKey("GetInfoFromSinaiCommand"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withRequestCacheEnabled(true))

                //融斷器配置
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerEnabled(true))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(20))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds(5000))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerErrorThresholdPercentage(50))

                //ThreadPool配置
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("GetInfoFromSinaiCommand"))
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10))
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(-1))

        );
        this.poiClient = poiClient;
        this.poiIds = poiIds;

    }

    @Override
    public List<PoiInfo> run() throws Exception {
        if (poiIds.isEmpty()) {
            return Lists.newArrayList();
        }
        List<PoiModel> pioModels = poiClient.listPois(poiIds, FIELDS);
        return parseResult(pioModels);
    }

    @Override
    protected String getCacheKey() {
        return String.valueOf(poiIds);
    }

    @Override
    protected List<PoiInfo> getFallback() {
        return Lists.newArrayList();
    }

    private List<PoiInfo> parseResult(List<PoiModel> poiModels) {
        if (poiModels == null || poiModels.isEmpty()) {
            return Lists.newArrayList();
        }
        List<PoiInfo> res = Lists.newArrayList();
        for (PoiModel poiModel : poiModels) {
            PoiInfo poiInfo = new PoiInfo();
            poiInfo.setPoiId(poiModel.getId());

            if (poiModel.getCate() != null) {
                poiInfo.setCate(poiModel.getCate());
            }
            if (poiModel.getSubcate() != null) {
                poiInfo.setSubcate(poiModel.getSubcate());
            }
            res.add(poiInfo);
        }
        return res;
    }
}

參數(shù)說明

參數(shù)類型 參數(shù)名 默認(rèn)值 說明

1.command配置 executionIsolationStrategy ExecutionIsolationStrategy.THREAD 信號(hào)隔離或線程隔離,默認(rèn):采用線程隔離,

2.熔斷器配置
executionIsolationThreadTimeoutInMillisecond 1s 隔離時(shí)間大,即多長時(shí)間后進(jìn)行重試
executionIsolationSemaphoreMaxConcurrentRequests 10 使用信號(hào)量隔離時(shí),命令調(diào)用最大的并發(fā)數(shù),默認(rèn):10
fallbackIsolationSemaphoreMaxConcurrentRequests 10 使用信號(hào)量隔離時(shí),命令fallback(降級(jí))調(diào)用最大的并發(fā)數(shù),默認(rèn):10
fallbackEnabled true 是否開啟fallback降級(jí)策略
executionIsolationThreadInterruptOnTimeout true 使用線程隔離時(shí),是否對(duì)命令執(zhí)行超時(shí)的線程調(diào)用中斷(Thread.interrupt())操作
metricsRollingStatisticalWindowInMilliseconds 10000ms 統(tǒng)計(jì)滾動(dòng)的時(shí)間窗口,默認(rèn):10s
metricsRollingStatisticalWindowBuckets 10 統(tǒng)計(jì)窗口的Buckets的數(shù)量,默認(rèn):10個(gè)
metricsRollingPercentileEnabled true 是否開啟監(jiān)控統(tǒng)計(jì)功能,默認(rèn):true
requestLogEnabled true 是否開啟請(qǐng)求日志
requestCacheEnabled true 是否開啟請(qǐng)求緩存
circuitBreakerRequestVolumeThreshold 20 主要用在小流量
circuitBreakerSleepWindowInMilliseconds 5000ms 熔斷器默認(rèn)工作時(shí)間,默認(rèn):5秒.熔斷器中斷請(qǐng)求5秒后會(huì)進(jìn)入半打開狀態(tài),放部分流量過去重試
circuitBreakerEnabled true 是否啟用熔斷器,默認(rèn)true. 啟動(dòng)
circuitBreakerErrorThresholdPercentage 50 默認(rèn):50%。當(dāng)出錯(cuò)率超過50%后熔斷器啟動(dòng)
circuitBreakerForceOpen false 是否強(qiáng)制開啟熔斷器阻斷所有請(qǐng)求,默認(rèn):false,不開啟
circuitBreakerForceClosed false 是否允許熔斷器忽略錯(cuò)誤,默認(rèn)false, 不開啟

3.線程池配置 HystrixThreadPoolProperties.Setter().withCoreSize(int value) 10 配置線程池大小,默認(rèn)值10個(gè)
HystrixThreadPoolProperties.Setter().withMaxQueueSize(int value) -1 配置線程值等待隊(duì)列長度

監(jiān)控上報(bào)

Hystrix高級(jí)特性

RequestCache和RequestCollapsing是Hystrix高級(jí)的特性,一般來說,我們會(huì)在Request 范圍內(nèi)使用這些高級(jí)特性,需要先初始化HystrixRequestContext進(jìn)行生命周期的管理,在標(biāo)準(zhǔn)Web應(yīng)用中我們可以借助過濾器來解決這個(gè)問題 。

RequestCache

RequestCache實(shí)現(xiàn)及使用方式:
假設(shè)兩個(gè)線程發(fā)起相同的HTTP請(qǐng)求,Hystrix會(huì)把請(qǐng)求參數(shù)初始化到ThreadLocal中,兩個(gè)Command異步執(zhí)行,每個(gè)Command會(huì)把請(qǐng)求參數(shù)從ThreadLocal中拷貝到Command所在自身的線程中,Command在執(zhí)行的時(shí)候會(huì)通過CacheKey優(yōu)先從緩存中嘗試獲取是否已有緩存結(jié)果,如果命中,直接從HystrixRequestCache返回,如果沒有命中,那么需要進(jìn)行一次真實(shí)調(diào)用,然后把結(jié)果回寫到緩存中,在請(qǐng)求范圍內(nèi)共享響應(yīng)結(jié)果。

主要有三個(gè)優(yōu)點(diǎn):

  • 在當(dāng)次請(qǐng)求內(nèi)對(duì)同一個(gè)依賴進(jìn)行重復(fù)調(diào)用,只會(huì)真實(shí)調(diào)用一次。
  • 在當(dāng)次請(qǐng)求內(nèi)數(shù)據(jù)可以保證一致性。
  • 可以減少不必要的線程開銷。

Hystrix通過實(shí)現(xiàn)getCacheKey方法來激活緩存機(jī)制;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容