首先,引入spring-cloud-starter-zuul
之后會間接引入:
hystrix依賴已經引入,那么何種情況下使用hystrix呢?
在zuul的自動配置類zuulserverautoconfiguration和zuulproxyautoconfiguration中總共會向spring容器注入3個zuul的routefilter,分別是
•simplehostroutingfilter
簡單路由,通過httpclient向預定的url發送請求
生效條件:
requestcontext.getcurrentcontext().getroutehost() != null
? && requestcontext.getcurrentcontext().sendzuulresponse()1、requestcontext中的routehost不為空,routehost就是url,即使用url直連
2、requestcontext中的sendzuulresponse為true,即是否將response發送給客戶端,默認為true
•ribbonroutingfilter
使用ribbon、hystrix和可插入的http客戶端發送請求
生效條件:
(requestcontext.getroutehost() == null && requestcontext.get(service_id_key) != null
? && requestcontext.sendzuulresponse())1、requestcontext中的routehost為空,即url為空
2、requestcontext中的serviceid不為空
3、requestcontext中的sendzuulresponse為true,即是否將response發送給客戶端,默認為true
•sendforwardfilter
forward到本地url
生效條件:
requestcontext.containskey(forward_to_key)
? && !requestcontext.getboolean(send_forward_filter_ran, false)1、requestcontext中包含forward_to_key,即url使用 forward: 映射
2、requestcontext中send_forward_filter_ran為false,send_forward_filter_ran意為“send forward是否運行過了”,在sendforwardfilter#run()時會ctx.set(send_forward_filter_ran, true)
綜上所述,在使用serviceid映射的方法路由轉發的時候,會使用ribbon+hystrix
而哪種路由配置方式是“url映射”,哪種配置方式又是“serviceid映射”呢?
zuul有一個前置過濾器predecorationfilter用于通過routelocator路由定位器決定在何時以何種方式路由轉發
routelocator是用于通過請求地址匹配到route路由的,之后predecorationfilter再通過route信息設置requestcontext上下文,決定后續使用哪個routefilter做路由轉發
所以就引出以下問題:
•什么是route
•routelocator路由定位器如何根據請求路徑匹配路由
•匹配到路由后,predecorationfilter如何設置requestcontext請求上下文
什么是route
我總共見到兩個和route相關的類
zuulproperties.zuulroute,用于和zuul配置文件關聯,保存相關信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
org.springframework.cloud.netflix.zuul.filters.route, routelocator找到的路由信息就是這個類,用于路由轉發 public static class zuulroute { private string id; //zuulroute的id private string path; //路由的pattern,如 /foo/** private string serviceid; //要映射到此路由的服務id private string url; //要映射到路由的完整物理url private boolean stripprefix = true ; //用于確定在轉發之前是否應剝離此路由前綴的標志位 private boolean retryable; //此路由是否可以重試,通常重試需要serviceid和ribbon private set<string> sensitiveheaders = new linkedhashset(); //不會傳遞給下游請求的敏感標頭列表 private boolean customsensitiveheaders = false ; //是否自定義了敏感頭列表 } public class route { private string id; private string fullpath; private string path; private string location; //可能是 url 或 serviceid private string prefix; private boolean retryable; private set<string> sensitiveheaders = new linkedhashset<>(); private boolean customsensitiveheaders; } |
可以看到org.springframework.cloud.netflix.zuul.filters.route和zuulproperties.zuulroute基本一致,只是route用于路由轉發定位的屬性location根據不同的情況,可能是一個具體的url,可能是一個serviceid
routelocator路由定位器如何根據請求路徑匹配路由
zuul在自動配置加載時注入了2個routelocator
•compositeroutelocator: 組合的routelocator,在getmatchingroute()時會依次調用其它的routelocator,先找到先返回;compositeroutelocator的routelocators集合中只有discoveryclientroutelocator
•discoveryclientroutelocator: 可以將靜態的、已配置的路由與來自discoveryclient服務發現的路由組合在一起,來自discoveryclient的路由優先;simpleroutelocator的子類(simpleroutelocator 基于加載到zuulproperties中的配置定位route路由信息)
其中compositeroutelocator是 @primary 的,它是組合多個routelocator的locator,其getmatchingroute()方法會分別調用其它所有routelocator的getmatchingroute()方法,通過請求路徑匹配路由信息,只要匹配到了就馬上返回
默認compositeroutelocator混合路由定位器的routelocators只有一個discoveryclientroutelocator,故只需分析discoveryclientroutelocator#getmatchingroute(path)
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
|
//----------discoveryclientroutelocator是simpleroutelocator子類,其實是調用的simpleroutelocator##getmatchingroute(path) @override public route getmatchingroute( final string path) { return getsimplematchingroute(path); } protected route getsimplematchingroute( final string path) { if (log.isdebugenabled()) { log.debug( "finding route for path: " + path); } // routes是保存路由信息的map,如果此時還未加載,調用locateroutes() if ( this .routes.get() == null ) { this .routes.set(locateroutes()); } if (log.isdebugenabled()) { log.debug( "servletpath=" + this .dispatcherservletpath); log.debug( "zuulservletpath=" + this .zuulservletpath); log.debug( "requestutils.isdispatcherservletrequest()=" + requestutils.isdispatcherservletrequest()); log.debug( "requestutils.iszuulservletrequest()=" + requestutils.iszuulservletrequest()); } /** * 下面的方法主要是先對path做微調 * 再根據path到routes中匹配到zuulroute * 最后根據 zuulroute 和 adjustedpath 生成 route */ string adjustedpath = adjustpath(path); zuulroute route = getzuulroute(adjustedpath); return getroute(route, adjustedpath); } |
下面我們來看看locateroutes()是如何加載靜態的、已配置的路由與來自discoveryclient服務發現的路由的
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
//----------discoveryclientroutelocator#locateroutes() 服務發現路由定位器的locateroutes() @override protected linkedhashmap<string, zuulroute> locateroutes() { //保存zuulroute的linkedhashmap linkedhashmap<string, zuulroute> routesmap = new linkedhashmap<string, zuulroute>(); //調用父類simpleroutelocator#locateroutes() //加載zuulproperties中的所有配置文件中的路由信息 routesmap.putall( super .locateroutes()); //如果服務發現客戶端discovery存在 if ( this .discovery != null ) { //將routesmap已經存在的配置文件中的zuulroute放入staticservices<serviceid, zuulroute> map<string, zuulroute> staticservices = new linkedhashmap<string, zuulroute>(); for (zuulroute route : routesmap.values()) { string serviceid = route.getserviceid(); //如果serviceid為null,以id作為serviceid,此情況適合 zuul.routes.xxxx=/xxxx/** 的情況 if (serviceid == null ) { serviceid = route.getid(); } if (serviceid != null ) { staticservices.put(serviceid, route); } } // add routes for discovery services by default list<string> services = this .discovery.getservices(); //到注冊中心找到所有service string[] ignored = this .properties.getignoredservices() .toarray( new string[ 0 ]); //遍歷services for (string serviceid : services) { // ignore specifically ignored services and those that were manually // configured string key = "/" + maproutetoservice(serviceid) + "/**" ; //如果注冊中心的serviceid在staticservices集合中,并且此路由沒有配置url //那么,更新路由的location為serviceid if (staticservices.containskey(serviceid) && staticservices.get(serviceid).geturl() == null ) { // explicitly configured with no url, cannot be ignored // all static routes are already in routesmap // update location using serviceid if location is null zuulroute staticroute = staticservices.get(serviceid); if (!stringutils.hastext(staticroute.getlocation())) { staticroute.setlocation(serviceid); } } //如果注冊中心的serviceid不在忽略范圍內,且routesmap中還沒有包含,添加到routesmap if (!patternmatchutils.simplematch(ignored, serviceid) && !routesmap.containskey(key)) { // not ignored routesmap.put(key, new zuulroute(key, serviceid)); } } } // 如果routesmap中有 /** 的默認路由配置 if (routesmap.get(default_route) != null ) { zuulroute defaultroute = routesmap.get(default_route); // move the defaultserviceid to the end routesmap.remove(default_route); routesmap.put(default_route, defaultroute); } //將routesmap中的數據微調后,放到values<string, zuulroute>,返回 linkedhashmap<string, zuulroute> values = new linkedhashmap<>(); for (entry<string, zuulroute> entry : routesmap.entryset()) { string path = entry.getkey(); // prepend with slash if not already present. if (!path.startswith( "/" )) { path = "/" + path; } if (stringutils.hastext( this .properties.getprefix())) { path = this .properties.getprefix() + path; if (!path.startswith( "/" )) { path = "/" + path; } } values.put(path, entry.getvalue()); } return values; } |
此方法運行后就已經加載了配置文件中所有路由信息,以及注冊中心中的服務路由信息,有的通過url路由,有的通過serviceid路由
只需根據本次請求的requesturi與 路由的pattern匹配找到對應的路由
匹配到路由后,predecorationfilter如何設置requestcontext請求上下文
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
//----------predecorationfilter前置過濾器 @override public object run() { requestcontext ctx = requestcontext.getcurrentcontext(); final string requesturi = this .urlpathhelper.getpathwithinapplication(ctx.getrequest()); route route = this .routelocator.getmatchingroute(requesturi); //找到匹配的路由 //----------------到上面為止是已經分析過的,根據requesturi找到匹配的route信息 // ==== 匹配到路由信息 if (route != null ) { string location = route.getlocation(); if (location != null ) { ctx.put(request_uri_key, route.getpath()); //requestcontext設置 requesturi:路由的pattern路徑 ctx.put(proxy_key, route.getid()); //requestcontext設置 proxy:路由id //設置需要忽略的敏感頭信息,要么用全局默認的,要么用路由自定義的 if (!route.iscustomsensitiveheaders()) { this .proxyrequesthelper .addignoredheaders( this .properties.getsensitiveheaders().toarray( new string[ 0 ])); } else { this .proxyrequesthelper.addignoredheaders(route.getsensitiveheaders().toarray( new string[ 0 ])); } //設置重試信息 if (route.getretryable() != null ) { ctx.put(retryable_key, route.getretryable()); } //如果location是 http/https開頭的,requestcontext設置 routehost:url //如果location是 forward:開頭的,requestcontext設置 forward信息、routehost:null //其它 requestcontext設置 serviceid、routehost:null、x-zuul-serviceid if (location.startswith(http_scheme+ ":" ) || location.startswith(https_scheme+ ":" )) { ctx.setroutehost(geturl(location)); ctx.addoriginresponseheader(service_header, location); } else if (location.startswith(forward_location_prefix)) { ctx.set(forward_to_key, stringutils.cleanpath(location.substring(forward_location_prefix.length()) + route.getpath())); ctx.setroutehost( null ); return null ; } else { // set serviceid for use in filters.route.ribbonrequest ctx.set(service_id_key, location); ctx.setroutehost( null ); ctx.addoriginresponseheader(service_id_header, location); } //是否添加代理頭信息 x-forwarded-for if ( this .properties.isaddproxyheaders()) { addproxyheaders(ctx, route); string xforwardedfor = ctx.getrequest().getheader(x_forwarded_for_header); string remoteaddr = ctx.getrequest().getremoteaddr(); if (xforwardedfor == null ) { xforwardedfor = remoteaddr; } else if (!xforwardedfor.contains(remoteaddr)) { // prevent duplicates xforwardedfor += ", " + remoteaddr; } ctx.addzuulrequestheader(x_forwarded_for_header, xforwardedfor); } //是否添加host頭信息 if ( this .properties.isaddhostheader()) { ctx.addzuulrequestheader(httpheaders.host, tohostheader(ctx.getrequest())); } } } // ==== 沒有匹配到路由信息 else { log.warn( "no route found for uri: " + requesturi); string fallbackuri = requesturi; string fallbackprefix = this .dispatcherservletpath; // default fallback // servlet is // dispatcherservlet if (requestutils.iszuulservletrequest()) { // remove the zuul servletpath from the requesturi log.debug( "zuulservletpath=" + this .properties.getservletpath()); fallbackuri = fallbackuri.replacefirst( this .properties.getservletpath(), "" ); log.debug( "replaced zuul servlet path:" + fallbackuri); } else { // remove the dispatcherservlet servletpath from the requesturi log.debug( "dispatcherservletpath=" + this .dispatcherservletpath); fallbackuri = fallbackuri.replacefirst( this .dispatcherservletpath, "" ); log.debug( "replaced dispatcherservlet servlet path:" + fallbackuri); } if (!fallbackuri.startswith( "/" )) { fallbackuri = "/" + fallbackuri; } string forwarduri = fallbackprefix + fallbackuri; forwarduri = forwarduri.replaceall( "//" , "/" ); ctx.set(forward_to_key, forwarduri); } return null ; } |
總結:
•只要引入了spring-cloud-starter-zuul就會間接引入ribbon、hystrix
•路由信息可能是從配置文件中加載的,也可能是通過discoveryclient從注冊中心加載的
•zuul是通過前置過濾器predecorationfilter找到與當前requesturi匹配的路由信息,并在requestcontext中設置相關屬性的,后續的route filter會根據requestcontext中的這些屬性判斷如何路由轉發
•route filter主要使用 simplehostroutingfilter 和 ribbonroutingfilter
•當requestcontext請求上下文中存在routehost,即url直連信息時,使用simplehostroutingfilter簡單host路由
•當requestcontext請求上下文中存在serviceid,即服務id時(可能會與注冊中心關聯獲取服務列表,或者讀取配置文件中serviceid.ribbon.listofservers的服務列表),使用ribbonroutingfilter,會使用ribbon、hystrix
總結
以上所述是小編給大家介紹的springcloud zuul在何種情況下使用hystrix,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:https://www.cnblogs.com/trust-freedom/p/9982680.html