在hystrixcommand
之前可以使用請求合并器(hystrixcollapser
就是一個抽象的父類)來把多個請求合并成一個然后對后端依賴系統(tǒng)發(fā)起調用。
下圖顯示了兩種情況下線程的數(shù)量和網絡的連接數(shù)的情況:第一種是不使用合并器,第二種是使用請求合并器(假設所有的鏈接都是在一個短的時間窗口內并行的,比如10ms內)。
為什么要使用請求合并?
使用請求合并來減少執(zhí)行并發(fā)hystrixcommand
執(zhí)行所需的線程數(shù)和網絡連接數(shù),請求合并是自動執(zhí)行的,不會強制開發(fā)人員手動協(xié)調批處理請求。
全局上下文-global context(跨越所有tomcat線程)
這種合并類型是在全局應用級別上完成的,因此任何tomcat線程上的任何用戶的請求都可以一起合并。
例如,如果您配置一個hystrixcommand
支持任何用戶請求依賴關系來檢索電影評級,那么當同一個jvm中的任何用戶線程發(fā)出這樣的請求時,hystrix會將其請求與任何其他請求一起添加到同一個已折疊網絡通話。
用戶請求上下文-request context(單個tomcat線程)
如果你配置一個hystrixcommand
僅僅為一個單個用戶處理批量請求,hystrix可以在一個tomcat線程(請求)中合并請求。
例如,一個用戶想要加載300個視頻對象的書簽,不是去執(zhí)行300次網絡請求,hystrix能夠將他們合并成為一個。
hystrix默認是的就是request-scope,要使用request-scoped的功能(request caching,request collapsing, request log)你必須管理hystrixrequestcontext
的生命周期(或者實現(xiàn)一個可替代的hystrixconcurrencystrategy
)
這就意味你在執(zhí)行一個請求之前需要執(zhí)行以下的代碼:
并且在請求的結束位置執(zhí)行:
1
|
context.shutdown(); |
在標準的javaweb應用中,你也可以使用一個servlet過濾器來初始化這個生命周期
1
2
3
4
5
6
7
8
9
10
11
12
|
public class hystrixrequestcontextservletfilter implements filter { public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception { hystrixrequestcontext context = hystrixrequestcontext.initializecontext(); try { chain.dofilter(request, response); } finally { context.shutdown(); } } } |
然后將它配置在web.xml中
1
2
3
4
5
6
7
8
9
|
<filter> <display-name>hystrixrequestcontextservletfilter</display-name> <filter-name>hystrixrequestcontextservletfilter</filter-name> <filter- class >com.netflix.hystrix.contrib.requestservlet.hystrixrequestcontextservletfilter</filter- class > </filter> <filter-mapping> <filter-name>hystrixrequestcontextservletfilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
如果你是springboot開發(fā)的話代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@webfilter (filtername = "hystrixrequestcontextservletfilter" ,urlpatterns = "/*" ) public class hystrixrequestcontextservletfilter implements filter { @override public void init(filterconfig filterconfig) throws servletexception { } @override public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception { hystrixrequestcontext context = hystrixrequestcontext.initializecontext(); try { filterchain.dofilter(servletrequest,servletresponse); } finally { context.shutdown(); } } @override public void destroy() { } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@springbootapplication @enablediscoveryclient @enablefeignclients @enablehystrix //這個是必須的,否則filter無效 @servletcomponentscan public class application { public static void main(string[] args) { new springapplicationbuilder(application. class ).web( true ).run(args); } } |
請求合并的成本是多少?
啟用請求合并的成本是在執(zhí)行實際命令之前的延遲。最大的成本是批處理窗口的大小,默認是10ms。
如果你有一個命令需要花費5ms去執(zhí)行并且有一個10ms的批處理窗口,執(zhí)行的時間最壞的情況是15ms,一般情況下,請求不會在批處理窗口剛打開的時候發(fā)生,所以時間窗口的中間值是時間窗口的一半,在這種情況下是5ms。
這個成本是否值得取決于正在執(zhí)行的命令,高延遲命令不會受到少量附加平均延遲的影響。而且,給定命令的并發(fā)量也是關鍵:如果很少有超過1個或2個請求被組合在一起,那么這個成本就是不值得的。事實上,在一個單線程的順序迭代請求合并將會是一個主要的性能瓶頸,每一次迭代都會等待10ms的窗口等待時間。
但是,如果一個特定的命令同時被大量使用,并且可以同時批量打幾十個甚至幾百個呼叫,那么成本通常遠遠超過所達到的吞吐量的增加,因為hystrix減少了它所需的線程數(shù)量,依賴。(這段話不太好理解,其實就是說如果并發(fā)比較高,這個成本是值得的,因為hystrix可以節(jié)省很多線程和連接資源)。
請求合并的流程(如下圖)
理論知識已經講完了,下面來看看例子,下面的例子集成了eureka+feign+hystrix,完整的例子請查看:https://github.com/jingangwang/micro-service
實體類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public class user { private integer id; private string username; private integer age; public user() { } public user(integer id, string username, integer age) { this .id = id; this .username = username; this .age = age; } public integer getid() { return id; } public void setid(integer id) { this .id = id; } public string getusername() { return username; } public void setusername(string username) { this .username = username; } public integer getage() { return age; } public void setage(integer age) { this .age = age; } @override public string tostring() { final stringbuffer sb = new stringbuffer( "user{" ); sb.append( "id=" ).append(id); sb.append( ", username='" ).append(username).append(' '' ); sb.append( ", age=" ).append(age); sb.append( '}' ); return sb.tostring(); } } |
服務提供者代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@restcontroller @requestmapping ( "user" ) public class usercontroller { @requestmapping ( "getuser" ) public user getuser(integer id) { return new user(id, "test" , 29 ); } @requestmapping ( "getalluser" ) public list<user> getalluser(string ids){ string[] split = ids.split( "," ); return arrays.aslist(split) .stream() .map(id -> new user(integer.valueof(id), "test" +id, 30 )) .collect(collectors.tolist()); } } |
消費者代碼
userfeignclient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@feignclient (name = "eureka-provider" ,configuration = feignconfiguration. class ) public interface userfeignclient { /** * 根據id查找用戶 * @param id 用戶id * @return user */ @requestmapping (value = "user/getuser.json" ,method = requestmethod.get) user finduserbyid( @requestparam ( "id" ) integer id); /** * 超找用戶列表 * @param ids id列表 * @return 用戶的集合 */ @requestmapping (value = "user/getalluser.json" ,method = requestmethod.get) list<user> findalluser( @requestparam ( "ids" ) string ids); } |
userservice(設置為全局上下文)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@service public class userservice { @autowired private userfeignclient userfeignclient; /** * maxrequestsinbatch 該屬性設置批量處理的最大請求數(shù)量,默認值為integer.max_value * timerdelayinmilliseconds 該屬性設置多長時間之內算一次批處理,默認為10ms * @param id * @return */ @hystrixcollapser (collapserkey = "findcollapserkey" ,scope = com.netflix.hystrix.hystrixcollapser.scope.global,batchmethod = "findalluser" ,collapserproperties = { @hystrixproperty (name = "timerdelayinmilliseconds" ,value = "5000" ), @hystrixproperty (name = "maxrequestsinbatch" ,value = "5" ) }) public future<user> find(integer id){ return null ; } @hystrixcommand (commandkey = "findalluser" ) public list<user> findalluser(list<integer> ids){ return userfeignclient.findalluser(stringutils.join(ids, "," )); } } |
feigncollapsercontroller
1
2
3
4
5
6
7
8
9
|
@requestmapping ( "user" ) @restcontroller public class feigncollapsercontroller { @autowired private userservice userservice; @requestmapping ( "finduser" ) public user getuser(integer id) throws executionexception, interruptedexception { return userservice.find(id).get(); } |
上面的代碼我們這是的是全局上下文(所有tomcat的線程的請求都可以合并),合并的時間窗口為5s(每一次請求都得等5s才發(fā)起請求),最大合并數(shù)為5。我們在postman中,5s之內發(fā)起兩次請求,用戶id不一樣。
localhost:8082/user/finduser.json?id=123189891
localhost:8082/user/finduser.json?id=222222
結果如下圖所示,兩次請求合并為一次請求批量請求。
我們再來測試一下請求上下文(request-scope)的情況,加入上面所提到的hystrixrequestcontextservletfilter
,并修改userservice
hystrixrequestcontextservletfilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/** * @author wjg * @date 2017/12/22 15:15 */ @webfilter (filtername = "hystrixrequestcontextservletfilter" ,urlpatterns = "/*" ) public class hystrixrequestcontextservletfilter implements filter { @override public void init(filterconfig filterconfig) throws servletexception { } @override public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception { hystrixrequestcontext context = hystrixrequestcontext.initializecontext(); try { filterchain.dofilter(servletrequest,servletresponse); } finally { context.shutdown(); } } @override public void destroy() { } } |
userservice(設置為請求上下文)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@service public class userservice { @autowired private userfeignclient userfeignclient; /** * maxrequestsinbatch 該屬性設置批量處理的最大請求數(shù)量,默認值為integer.max_value * timerdelayinmilliseconds 該屬性設置多長時間之內算一次批處理,默認為10ms * @param id * @return */ @hystrixcollapser (collapserkey = "findcollapserkey" ,scope = com.netflix.hystrix.hystrixcollapser.scope.request,batchmethod = "findalluser" ,collapserproperties = { @hystrixproperty (name = "timerdelayinmilliseconds" ,value = "5000" ), @hystrixproperty (name = "maxrequestsinbatch" ,value = "5" ) }) public future<user> find(integer id){ return null ; } @hystrixcommand (commandkey = "findalluser" ) public list<user> findalluser(list<integer> ids){ return userfeignclient.findalluser(stringutils.join(ids, "," )); } } |
feigncollapser2controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@requestmapping ( "user" ) @restcontroller public class feigncollapser2controller { @autowired private userservice userservice; @requestmapping ( "finduser2" ) public list<user> getuser() throws executionexception, interruptedexception { future<user> user1 = userservice.find( 1989 ); future<user> user2= userservice.find( 1990 ); list<user> users = new arraylist<>(); users.add(user1.get()); users.add(user2.get()); return users; } } |
我們在postman中輸入:localhost:8082/user/finduser2.json
可以看到一個請求內的兩次連續(xù)調用被合并了。這個地方要注意,不能直接使用userserver.find(1989).get(),否則直接按同步執(zhí)行處理,不會合并。如果兩個tab頁同時調用上述地址,發(fā)現(xiàn)發(fā)起了兩次批量請求,說明作用域是request范圍。
參考資料如下:
https://github.com/netflix/hystrix/wiki/how-to-use
http://www.ythuaji.com.cn/article/160601.html
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/heartroll/article/details/78872436