日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringCloud实战4-Hystrix线程隔离请求缓存请求合并

發(fā)布時間:2024/9/21 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringCloud实战4-Hystrix线程隔离请求缓存请求合并 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

接著上一篇的Hystrix進行進一步了解。

當(dāng)系統(tǒng)用戶不斷增長時,每個微服務(wù)需要承受的并發(fā)壓力也越來越大,在分布式環(huán)境中,通常壓力來自對依賴服務(wù)的調(diào)用,因為親戚依賴服務(wù)的資源需要通過通信來實現(xiàn),這樣的依賴方式比起進程內(nèi)的調(diào)用方式會引起一部分的性能損失,

在高并發(fā)的場景下,Hystrix?提供了請求緩存的功能,我們可以方便的開啟和使用請求緩存來優(yōu)化系統(tǒng),達到減輕高并發(fā)時的請求線程消耗、降低請求響應(yīng)時間的效果

Hystrix的緩存,這個功能是有點雞肋的,因為這個緩存是基于request的,為什么這么說呢?因為每次請求來之前都必須HystrixRequestContext.initializeContext();進行初始化,每請求一次controller就會走一次filter,上下文又會初始化一次,前面緩存的就失效了,又得重新來。

所以你要是想測試緩存,你得在一次controller請求中多次調(diào)用那個加了緩存的service或HystrixCommand命令。Hystrix的書上寫的是:在同一用戶請求的上下文中,相同依賴服務(wù)的返回數(shù)據(jù)始終保持一致。在當(dāng)次請求內(nèi)對同一個依賴進行重復(fù)調(diào)用,只會真實調(diào)用一次。在當(dāng)次請求內(nèi)數(shù)據(jù)可以保證一致性。

因此。希望大家在這里不要理解錯了。

?

?請求緩存圖,如下:

假設(shè)兩個線程發(fā)起相同的HTTP請求,Hystrix會把請求參數(shù)初始化到ThreadLocal中,兩個Command異步執(zhí)行,每個Command會把請求參數(shù)從ThreadLocal中拷貝到Command所在自身的線程中,Command在執(zhí)行的時候會通過CacheKey優(yōu)先從緩存中嘗試獲取是否已有緩存結(jié)果,

如果命中,直接從HystrixRequestCache返回,如果沒有命中,那么需要進行一次真實調(diào)用,然后把結(jié)果回寫到緩存中,在請求范圍內(nèi)共享響應(yīng)結(jié)果。

RequestCache主要有三個優(yōu)點:

  • 在當(dāng)次請求內(nèi)對同一個依賴進行重復(fù)調(diào)用,只會真實調(diào)用一次。

  • 在當(dāng)次請求內(nèi)數(shù)據(jù)可以保證一致性。

  • 可以減少不必要的線程開銷。

  • ?

    例子還是接著上篇的HelloServiceCommand來進行演示,我們只需要實現(xiàn)HystrixCommand的一個緩存方法名為getCacheKey()即可

    代碼如下:

    /*** Created by cong on 2018/5/9.*/ public class HelloServiceCommand extends HystrixCommand<String> {private RestTemplate restTemplate;protected HelloServiceCommand(String commandGroupKey,RestTemplate restTemplate) {
        //根據(jù)commandGroupKey進行線程隔離的super(HystrixCommandGroupKey.Factory.asKey(commandGroupKey));
    this.restTemplate = restTemplate;}@Overrideprotected String run() throws Exception {System.out.println(Thread.currentThread().getName());return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();}@Overrideprotected String getFallback() {return "error";}//Hystrix的緩存 @Overrideprotected String getCacheKey() {//一般動態(tài)的取緩存Key,比如userId,這里為了做實驗寫死了,寫為helloreturn "hello";} }

    ?

    Controller代碼如下:

    ?

    /*** Created by cong on 2018/5/8.*/ @RestController public class ConsumerController {@Autowiredprivate RestTemplate restTemplate;@RequestMapping("/consumer")public String helloConsumer() throws ExecutionException, InterruptedException {//Hystrix的緩存實現(xiàn),這功能有點雞肋。 HystrixRequestContext.initializeContext();HelloServiceCommand command = new HelloServiceCommand("hello",restTemplate);String execute = command.execute();//清理緩存 // HystrixRequestCache.getInstance("hello").clear();return null;}}

    在原來的兩個provider模塊都增加增加一條輸出語句,如下:

    provider1模塊:

    /*** Created by cong on 2018/5/8.*/ @RestController public class HelloController {@RequestMapping("/hello")public String hello(){System.out.println("訪問來1了......");return "hello1";}}

    ?

    provider2模塊:

    /*** Created by cong on 2018/5/8.*/ @RestController public class HelloController {@RequestMapping("/hello")public String hello(){System.out.println("訪問來2了......");return "hello1";}}

    ?

    瀏覽器輸入localhost:8082/consumer?

    運行結(jié)果如下:

    可以看到你刷新一次請求,上下文又會初始化一次,前面緩存的就失效了,又得重新來,這時候根本就沒有緩存了。因此,你無論刷新多少次請求都是出現(xiàn)“訪問來了”,緩存都是失效的。如果是從緩存來的話,根本就不會輸出“訪問來了”。

    ?

    但是,你如你在一起請求多次調(diào)用同一個業(yè)務(wù),這時就是從緩存里面取的數(shù)據(jù)。不理解可以看一下Hystrix的緩存解釋:在同一用戶請求的上下文中,相同依賴服務(wù)的返回數(shù)據(jù)始終保持一致。在當(dāng)次請求內(nèi)對同一個依賴進行重復(fù)調(diào)用,只會真實調(diào)用一次。在當(dāng)次請求內(nèi)數(shù)據(jù)可以保證一致性。

    Controller代碼修改如下:

    /*** Created by cong on 2018/5/8.*/ @RestController public class ConsumerController {@Autowiredprivate RestTemplate restTemplate;@RequestMapping("/consumer")public String helloConsumer() throws ExecutionException, InterruptedException {//Hystrix的緩存實現(xiàn),這功能有點雞肋。 HystrixRequestContext.initializeContext();HelloServiceCommand command = new HelloServiceCommand("hello",restTemplate);String execute = command.execute();
         HelloServiceCommand command1 = new HelloServiceCommand("hello",restTemplate); String execute1 = command1.execute();   //清理緩存
      // HystrixRequestCache.getInstance("hello").clear();

      return null;
    }

    ?

    接著運行,運行結(jié)果如下:

    可以看到只有一個”訪問來了“,并沒有出現(xiàn)兩個”訪問來了“。

    之所以沒出現(xiàn)第二個,是因為是從緩存中取了。

    ?

    刪除緩存 例如刪除key名為hello的緩存:

    HystrixRequestCache.getInstance("hello").clear();

    你要寫操作的時候,你把一條數(shù)據(jù)給給刪除了,這時候你就必須把緩存清空了。

    ?

    ?

    下面進行請求的合并。

    ?為什么要進行請求合并?舉個例子,有個礦山,每過一段時間都會生產(chǎn)一批礦產(chǎn)出來(質(zhì)量為卡車載重量的1/100),卡車可以一等到礦產(chǎn)生產(chǎn)出來就馬上運走礦產(chǎn),也可以等到卡車裝滿再運走礦產(chǎn),

    前者一次生產(chǎn)對應(yīng)卡車一次往返,卡車需要往返100次,而后者只需要往返一次,可以大大減少卡車往返次數(shù)。顯而易見,利用請求合并可以減少線程和網(wǎng)絡(luò)連接,開發(fā)人員不必單獨提供一個批量請求接口就可以完成批量請求。

    ?在Hystrix中進行請求合并也是要付出一定代價的,請求合并會導(dǎo)致依賴服務(wù)的請求延遲增高,延遲的最大值是合并時間窗口的大小,默認(rèn)為10ms,當(dāng)然我們也可以通過hystrix.collapser.default.timerDelayInMilliseconds屬性進行修改,

    如果請求一次依賴服務(wù)的平均響應(yīng)時間是20ms,那么最壞情況下(合并窗口開始是請求加入等待隊列)這次請求響應(yīng)時間就會變成30ms。在Hystrix中對請求進行合并是否值得主要取決于Command本身,高并發(fā)度的接口通過請求合并可以極大提高系統(tǒng)吞吐量,

    從而基本可以忽略合并時間窗口的開銷,反之,并發(fā)量較低,對延遲敏感的接口不建議使用請求合并。

    請求合并的流程圖如下:

    ?可以看出Hystrix會把多個Command放入Request隊列中,一旦滿足合并時間窗口周期大小,Hystrix會進行一次批量提交,進行一次依賴服務(wù)的調(diào)用,通過充寫HystrixCollapser父類的mapResponseToRequests方法,將批量返回的請求分發(fā)到具體的每次請求中。

    ?

    例子如下:

    首先我們先自定義一個BatchCommand類來繼承Hystrix給我們提供的HystrixCollapser類,代碼如下:

    /*** Created by cong on 2018/5/13.*/ public class HjcBatchCommand extends HystrixCollapser<List<String>,String,Long> {private Long id;private RestTemplate restTemplate;//在200毫秒內(nèi)進行請求合并,不在的話,放到下一個200毫秒public HjcBatchCommand(RestTemplate restTemplate,Long id) {super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("hjcbatch")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(200)));this.id = id;this.restTemplate = restTemplate;}//獲取每一個請求的請求參數(shù) @Overridepublic Long getRequestArgument() {return id;}//創(chuàng)建命令請求合并 @Overrideprotected HystrixCommand<List<String>> createCommand(Collection<CollapsedRequest<String, Long>> collection) {List<Long> ids = new ArrayList<>(collection.size());ids.addAll(collection.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()));HjcCommand command = new HjcCommand("hjc",restTemplate,ids);return command;}//合并請求拿到了結(jié)果,將請求結(jié)果按請求順序分發(fā)給各個請求 @Overrideprotected void mapResponseToRequests(List<String> results, Collection<CollapsedRequest<String, Long>> collection) {System.out.println("分配批量請求結(jié)果。。。。");int count = 0;for (CollapsedRequest<String,Long> collapsedRequest : collection){String result = results.get(count++);collapsedRequest.setResponse(result);}} }

    ?

    接著用自定義個HjcCommand來繼承Hystrix提供的HystrixCommand來進行服務(wù)請求

    /*** Created by cong on 2018/5/13.*/ public class HjcCommand extends HystrixCommand<List<String>> {private RestTemplate restTemplate;private List<Long> ids;public HjcCommand(String commandGroupKey, RestTemplate restTemplate,List<Long> ids) {
          //根據(jù)commandGroupKey進行線程隔離super(HystrixCommandGroupKey.Factory.asKey(commandGroupKey));
    this.restTemplate = restTemplate;this.ids = ids;}@Overrideprotected List<String> run() throws Exception {System.out.println("發(fā)送請求。。。參數(shù)為:"+ids.toString()+Thread.currentThread().getName());String[] result = restTemplate.getForEntity("http://HELLO-SERVICE/hjcs?ids={1}",String[].class, StringUtils.join(ids,",")).getBody();return Arrays.asList(result);} }

    但是注意一點:你請求合并必須要異步,因為你如果用同步,是一個請求完成后,另外的請求才能繼續(xù)執(zhí)行,所以必須要異步才能請求合并。

    所以Controller層代碼如下:

    @RestController public class ConsumerController {@Autowiredprivate RestTemplate restTemplate;@RequestMapping("/consumer")public String helloConsumer() throws ExecutionException, InterruptedException {//請求合并HystrixRequestContext context = HystrixRequestContext.initializeContext();HjcBatchCommand command = new HjcBatchCommand(restTemplate,1L);HjcBatchCommand command1 = new HjcBatchCommand(restTemplate,2L);HjcBatchCommand command2 = new HjcBatchCommand(restTemplate,3L);//這里你必須要異步,因為同步是一個請求完成后,另外的請求才能繼續(xù)執(zhí)行,所以必須要異步才能請求合并Future<String> future = command.queue();Future<String> future1 = command1.queue();String r = future.get();String r1 = future1.get();Thread.sleep(2000);//可以看到前面兩條命令會合并,最后一條會單獨,因為睡了2000毫秒,而你請求設(shè)置要求在200毫秒內(nèi)才合并的。Future<String> future2 = command2.queue();String r2 = future2.get();System.out.println(r);System.out.println(r1);System.out.println(r2);context.close();return null;}}

    兩個服務(wù)提供者provider1,provider2新增加一個方法來模擬數(shù)據(jù)庫數(shù)據(jù),代碼如下:

    /*** Created by cong on 2018/5/8.*/ @RestController public class HelloController {@RequestMapping("/hello")public String hello(){System.out.println("訪問來2了......");return "hello2";}@RequestMapping("/hjcs")public List<String> laowangs(String ids){List<String> list = new ArrayList<>();list.add("laowang1");list.add("laowang2");list.add("laowang3");return list;}}

    啟動Ribbon模塊,運行結(jié)果如下:

    ?可以看到上圖的兩個線程是隔離的。

    當(dāng)請求非常多的時候,你合并請求就變得非常重要了,如果你不合并,一個請求都1 到2秒,這明顯不能忍的,會造成效率緩慢,如果你合并后,這時就可以并行處理,降低延遲,但是如果請求不多的時候,只有單個請求,這時候合并也會出現(xiàn)

    效率緩慢的,因為如果請求一次依賴服務(wù)的平均響應(yīng)時間是200ms,那么最壞情況下(合并窗口開始是請求加入等待隊列)這次請求響應(yīng)時間就會變成300ms。所以說要看場合而定的。

    ?

    下面用注解的代碼來實現(xiàn)請求合并。代碼如下:‘

    /*** Created by cong on 2018/5/15.*/ @Service public class HjcService {@Autowiredprivate RestTemplate restTemplate;@HystrixCollapser(batchMethod = "getLaoWang",collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "200")})public Future<String> batchGetHjc(long id){return null;}@HystrixCommandpublic List<String> getLaoWang(List<Long> ids){System.out.println("發(fā)送請求。。。參數(shù)為:"+ids.toString()+Thread.currentThread().getName());String[] result = restTemplate.getForEntity("http://HELLO-SERVICE/hjcs?ids={1}",String[].class, StringUtils.join(ids,",")).getBody();return Arrays.asList(result);}}

    ?

    如果我們還要進行服務(wù)的監(jiān)控的話,那么我們需要在Ribbon模塊,和兩個服務(wù)提供者模塊提供如下依賴:

    Ribbon模塊依賴如下:

        <!--儀表盤--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-hystrix-dashboard</artifactId><version>1.4.0.RELEASE</version></dependency><!--監(jiān)控--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator</artifactId></dependency>

    兩個provider模塊依賴如下:

        <!--監(jiān)控--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator</artifactId></dependency>

    接著在Ribbon啟動類打上@EnableHystrixDashboard注解,然后啟動,localhost:8082/hystrix,如下圖:

    每次訪問都有記錄:如下:

    ?

    ?

    接下來我們看一下常用的Hystrix屬性:

    hystrix.command.default和hystrix.threadpool.default中的default為默認(rèn)CommandKey

    Command Properties:

    1.Execution相關(guān)的屬性的配置:

    • hystrix.command.default.execution.isolation.strategy 隔離策略,默認(rèn)是Thread, 可選Thread|Semaphore

    • hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令執(zhí)行超時時間,默認(rèn)1000ms

    • hystrix.command.default.execution.timeout.enabled 執(zhí)行是否啟用超時,默認(rèn)啟用true
    • hystrix.command.default.execution.isolation.thread.interruptOnTimeout 發(fā)生超時是是否中斷,默認(rèn)true
    • hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并發(fā)請求數(shù),默認(rèn)10,該參數(shù)當(dāng)使用ExecutionIsolationStrategy.SEMAPHORE策略時才有效。如果達到最大并發(fā)請求數(shù),請求會被拒絕。理論上選擇semaphore size的原則和選擇thread size一致,但選用semaphore時每次執(zhí)行的單元要比較小且執(zhí)行速度快(ms級別),否則的話應(yīng)該用thread。
      semaphore應(yīng)該占整個容器(tomcat)的線程池的一小部分。

    2.Fallback相關(guān)的屬性

    這些參數(shù)可以應(yīng)用于Hystrix的THREAD和SEMAPHORE策略

    • hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 如果并發(fā)數(shù)達到該設(shè)置值,請求會被拒絕和拋出異常并且fallback不會被調(diào)用。默認(rèn)10
    • hystrix.command.default.fallback.enabled 當(dāng)執(zhí)行失敗或者請求被拒絕,是否會嘗試調(diào)用hystrixCommand.getFallback() 。默認(rèn)true

    3.Circuit Breaker相關(guān)的屬性

    • hystrix.command.default.circuitBreaker.enabled 用來跟蹤circuit的健康性,如果未達標(biāo)則讓request短路。默認(rèn)true
    • hystrix.command.default.circuitBreaker.requestVolumeThreshold 一個rolling window內(nèi)最小的請求數(shù)。如果設(shè)為20,那么當(dāng)一個rolling window的時間內(nèi)(比如說1個rolling window是10秒)收到19個請求,即使19個請求都失敗,也不會觸發(fā)circuit break。默認(rèn)20
    • hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 觸發(fā)短路的時間值,當(dāng)該值設(shè)為5000時,則當(dāng)觸發(fā)circuit break后的5000毫秒內(nèi)都會拒絕request,也就是5000毫秒后才會關(guān)閉circuit。默認(rèn)5000
    • hystrix.command.default.circuitBreaker.errorThresholdPercentage錯誤比率閥值,如果錯誤率>=該值,circuit會被打開,并短路所有請求觸發(fā)fallback。默認(rèn)50
    • hystrix.command.default.circuitBreaker.forceOpen 強制打開熔斷器,如果打開這個開關(guān),那么拒絕所有request,默認(rèn)false
    • hystrix.command.default.circuitBreaker.forceClosed 強制關(guān)閉熔斷器 如果這個開關(guān)打開,circuit將一直關(guān)閉且忽略circuitBreaker.errorThresholdPercentage

    4.Metrics相關(guān)參數(shù)

    • hystrix.command.default.metrics.rollingStats.timeInMilliseconds 設(shè)置統(tǒng)計的時間窗口值的,毫秒值,circuit break 的打開會根據(jù)1個rolling window的統(tǒng)計來計算。若rolling window被設(shè)為10000毫秒,則rolling window會被分成n個buckets,每個bucket包含success,failure,timeout,rejection的次數(shù)的統(tǒng)計信息。默認(rèn)10000
    • hystrix.command.default.metrics.rollingStats.numBuckets 設(shè)置一個rolling window被劃分的數(shù)量,若numBuckets=10,rolling window=10000,那么一個bucket的時間即1秒。必須符合rolling window % numberBuckets == 0。默認(rèn)10
    • hystrix.command.default.metrics.rollingPercentile.enabled 執(zhí)行時是否enable指標(biāo)的計算和跟蹤,默認(rèn)true
    • hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 設(shè)置rolling percentile window的時間,默認(rèn)60000
    • hystrix.command.default.metrics.rollingPercentile.numBuckets 設(shè)置rolling percentile window的numberBuckets。邏輯同上。默認(rèn)6
    • hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若這10s里有500次執(zhí)行,只有最后100次執(zhí)行會被統(tǒng)計到bucket里去。增加該值會增加內(nèi)存開銷以及排序的開銷。默認(rèn)100
    • hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 記錄health 快照(用來統(tǒng)計成功和錯誤綠)的間隔,默認(rèn)500ms

    5.Request Context 相關(guān)參數(shù)

    hystrix.command.default.requestCache.enabled 默認(rèn)true,需要重載getCacheKey(),返回null時不緩存
    hystrix.command.default.requestLog.enabled 記錄日志到HystrixRequestLog,默認(rèn)true

    6.Collapser Properties 相關(guān)參數(shù)

    hystrix.collapser.default.maxRequestsInBatch 單次批處理的最大請求數(shù),達到該數(shù)量觸發(fā)批處理,默認(rèn)Integer.MAX_VALUE
    hystrix.collapser.default.timerDelayInMilliseconds 觸發(fā)批處理的延遲,也可以為創(chuàng)建批處理的時間+該值,默認(rèn)10
    hystrix.collapser.default.requestCache.enabled 是否對HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默認(rèn)true

    7.ThreadPool 相關(guān)參數(shù)

    線程數(shù)默認(rèn)值10適用于大部分情況(有時可以設(shè)置得更小),如果需要設(shè)置得更大,那有個基本得公式可以follow:
    requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room
    每秒最大支撐的請求數(shù)?(99%平均響應(yīng)時間 + 緩存值)
    比如:每秒能處理1000個請求,99%的請求響應(yīng)時間是60ms,那么公式是:
    1000?(0.060+0.012)

    基本得原則時保持線程池盡可能小,他主要是為了釋放壓力,防止資源被阻塞。
    當(dāng)一切都是正常的時候,線程池一般僅會有1到2個線程激活來提供服務(wù)

      • hystrix.threadpool.default.coreSize 并發(fā)執(zhí)行的最大線程數(shù),默認(rèn)10
      • hystrix.threadpool.default.maxQueueSize BlockingQueue的最大隊列數(shù),當(dāng)設(shè)為-1,會使用SynchronousQueue,值為正時使用LinkedBlcokingQueue。該設(shè)置只會在初始化時有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默認(rèn)-1。
      • hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize沒有達到,達到queueSizeRejectionThreshold該值后,請求也會被拒絕。因為maxQueueSize不能被動態(tài)修改,這個參數(shù)將允許我們動態(tài)設(shè)置該值。if maxQueueSize == -1,該字段將不起作用
      • hystrix.threadpool.default.keepAliveTimeMinutes 如果corePoolSize和maxPoolSize設(shè)成一樣(默認(rèn)實現(xiàn))該設(shè)置無效。如果通過plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定義實現(xiàn),該設(shè)置才有用,默認(rèn)1.
      • hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 線程池統(tǒng)計指標(biāo)的時間,默認(rèn)10000
      • hystrix.threadpool.default.metrics.rollingStats.numBuckets 將rolling window劃分為n個buckets,默認(rèn)10

    總結(jié)

    以上是生活随笔為你收集整理的SpringCloud实战4-Hystrix线程隔离请求缓存请求合并的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。