SpringCloud Zuul在何種情況下使用Hystrix及問(wèn)題小結(jié)
首先,引入spring-cloud-starter-zuul
之后會(huì)間接引入:
hystrix依賴(lài)已經(jīng)引入,那么何種情況下使用hystrix呢?
在Zuul的自動(dòng)配置類(lèi)ZuulServerAutoConfiguration和ZuulProxyAutoConfiguration中總共會(huì)向Spring容器注入3個(gè)Zuul的RouteFilter,分別是
•SimpleHostRoutingFilter
簡(jiǎn)單路由,通過(guò)HttpClient向預(yù)定的URL發(fā)送請(qǐng)求
生效條件:
RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse()1、RequestContext中的routeHost不為空,routeHost就是URL,即使用URL直連
2、RequestContext中的sendZuulResponse為true,即是否將response發(fā)送給客戶(hù)端,默認(rèn)為true
•RibbonRoutingFilter
使用Ribbon、Hystrix和可插入的http客戶(hù)端發(fā)送請(qǐng)求
生效條件:
(RequestContext.getRouteHost() == null && RequestContext.get(SERVICE_ID_KEY) != null
&& RequestContext.sendZuulResponse())1、RequestContext中的routeHost為空,即URL為空
2、RequestContext中的serviceId不為空
3、RequestContext中的sendZuulResponse為true,即是否將response發(fā)送給客戶(hù)端,默認(rèn)為true
•SendForwardFilter
forward到本地URL
生效條件:
RequestContext.containsKey(FORWARD_TO_KEY)
&& !RequestContext.getBoolean(SEND_FORWARD_FILTER_RAN, false)1、RequestContext中包含F(xiàn)ORWARD_TO_KEY,即URL使用 forward: 映射
2、RequestContext中SEND_FORWARD_FILTER_RAN為false,SEND_FORWARD_FILTER_RAN意為“send forward是否運(yùn)行過(guò)了”,在SendForwardFilter#run()時(shí)會(huì)ctx.set(SEND_FORWARD_FILTER_RAN, true)
綜上所述,在使用serviceId映射的方法路由轉(zhuǎn)發(fā)的時(shí)候,會(huì)使用Ribbon+Hystrix
而哪種路由配置方式是“URL映射”,哪種配置方式又是“serviceId映射”呢?
Zuul有一個(gè)前置過(guò)濾器PreDecorationFilter用于通過(guò)RouteLocator路由定位器決定在何時(shí)以何種方式路由轉(zhuǎn)發(fā)
RouteLocator是用于通過(guò)請(qǐng)求地址匹配到Route路由的,之后PreDecorationFilter再通過(guò)Route信息設(shè)置RequestContext上下文,決定后續(xù)使用哪個(gè)RouteFilter做路由轉(zhuǎn)發(fā)
所以就引出以下問(wèn)題:
•什么是Route
•RouteLocator路由定位器如何根據(jù)請(qǐng)求路徑匹配路由
•匹配到路由后,PreDecorationFilter如何設(shè)置RequestContext請(qǐng)求上下文
什么是Route
我總共見(jiàn)到兩個(gè)和Route相關(guān)的類(lèi)
ZuulProperties.ZuulRoute,用于和zuul配置文件關(guān)聯(lián),保存相關(guān)信息
org.springframework.cloud.netflix.zuul.filters.Route, RouteLocator找到的路由信息就是這個(gè)類(lèi),用于路由轉(zhuǎn)發(fā) public static class ZuulRoute { private String id; //ZuulRoute的id private String path; //路由的pattern,如 /foo/** private String serviceId; //要映射到此路由的服務(wù)id private String url; //要映射到路由的完整物理URL private boolean stripPrefix = true; //用于確定在轉(zhuǎn)發(fā)之前是否應(yīng)剝離此路由前綴的標(biāo)志位 private Boolean retryable; //此路由是否可以重試,通常重試需要serviceId和ribbon private Set<String> sensitiveHeaders = new LinkedHashSet(); //不會(huì)傳遞給下游請(qǐng)求的敏感標(biāo)頭列表 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用于路由轉(zhuǎn)發(fā)定位的屬性location根據(jù)不同的情況,可能是一個(gè)具體的URL,可能是一個(gè)serviceId
RouteLocator路由定位器如何根據(jù)請(qǐng)求路徑匹配路由
Zuul在自動(dòng)配置加載時(shí)注入了2個(gè)RouteLocator
•CompositeRouteLocator: 組合的RouteLocator,在getMatchingRoute()時(shí)會(huì)依次調(diào)用其它的RouteLocator,先找到先返回;CompositeRouteLocator的routeLocators集合中只有DiscoveryClientRouteLocator
•DiscoveryClientRouteLocator: 可以將靜態(tài)的、已配置的路由與來(lái)自DiscoveryClient服務(wù)發(fā)現(xiàn)的路由組合在一起,來(lái)自DiscoveryClient的路由優(yōu)先;SimpleRouteLocator的子類(lèi)(SimpleRouteLocator 基于加載到ZuulProperties中的配置定位Route路由信息)
其中CompositeRouteLocator是 @Primary 的,它是組合多個(gè)RouteLocator的Locator,其getMatchingRoute()方法會(huì)分別調(diào)用其它所有RouteLocator的getMatchingRoute()方法,通過(guò)請(qǐng)求路徑匹配路由信息,只要匹配到了就馬上返回
默認(rèn)CompositeRouteLocator混合路由定位器的routeLocators只有一個(gè)DiscoveryClientRouteLocator,故只需分析DiscoveryClientRouteLocator#getMatchingRoute(path)
//----------DiscoveryClientRouteLocator是SimpleRouteLocator子類(lèi),其實(shí)是調(diào)用的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,如果此時(shí)還未加載,調(diào)用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()); } /** * 下面的方法主要是先對(duì)path做微調(diào) * 再根據(jù)path到routes中匹配到ZuulRoute * 最后根據(jù) ZuulRoute 和 adjustedPath 生成 Route */ String adjustedPath = adjustPath(path); ZuulRoute route = getZuulRoute(adjustedPath); return getRoute(route, adjustedPath); }
下面我們來(lái)看看locateRoutes()是如何加載靜態(tài)的、已配置的路由與來(lái)自DiscoveryClient服務(wù)發(fā)現(xiàn)的路由的
//----------DiscoveryClientRouteLocator#locateRoutes() 服務(wù)發(fā)現(xiàn)路由定位器的locateRoutes() @Override protected LinkedHashMap<String, ZuulRoute> locateRoutes() { //保存ZuulRoute的LinkedHashMap LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>(); //調(diào)用父類(lèi)SimpleRouteLocator#locateRoutes() //加載ZuulProperties中的所有配置文件中的路由信息 routesMap.putAll(super.locateRoutes()); //如果服務(wù)發(fā)現(xiàn)客戶(hù)端discovery存在 if (this.discovery != null) { //將routesMap已經(jīng)存在的配置文件中的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(); //到注冊(cè)中心找到所有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) + "/**"; //如果注冊(cè)中心的serviceId在staticServices集合中,并且此路由沒(méi)有配置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); } } //如果注冊(cè)中心的serviceId不在忽略范圍內(nèi),且routesMap中還沒(méi)有包含,添加到routesMap if (!PatternMatchUtils.simpleMatch(ignored, serviceId) && !routesMap.containsKey(key)) { // Not ignored routesMap.put(key, new ZuulRoute(key, serviceId)); } } } // 如果routesMap中有 /** 的默認(rèn)路由配置 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中的數(shù)據(jù)微調(diào)后,放到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; }
此方法運(yùn)行后就已經(jīng)加載了配置文件中所有路由信息,以及注冊(cè)中心中的服務(wù)路由信息,有的通過(guò)URL路由,有的通過(guò)serviceId路由
只需根據(jù)本次請(qǐng)求的requestURI與 路由的pattern匹配找到對(duì)應(yīng)的路由
匹配到路由后,PreDecorationFilter如何設(shè)置RequestContext請(qǐng)求上下文
//----------PreDecorationFilter前置過(guò)濾器 @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest()); Route route = this.routeLocator.getMatchingRoute(requestURI); //找到匹配的路由 //----------------到上面為止是已經(jīng)分析過(guò)的,根據(jù)requestURI找到匹配的Route信息 // ==== 匹配到路由信息 if (route != null) { String location = route.getLocation(); if (location != null) { ctx.put(REQUEST_URI_KEY, route.getPath());//RequestContext設(shè)置 requestURI:路由的pattern路徑 ctx.put(PROXY_KEY, route.getId());//RequestContext設(shè)置 proxy:路由id //設(shè)置需要忽略的敏感頭信息,要么用全局默認(rèn)的,要么用路由自定義的 if (!route.isCustomSensitiveHeaders()) { this.proxyRequestHelper .addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0])); } else { this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0])); } //設(shè)置重試信息 if (route.getRetryable() != null) { ctx.put(RETRYABLE_KEY, route.getRetryable()); } //如果location是 http/https開(kāi)頭的,RequestContext設(shè)置 routeHost:URL //如果location是 forward:開(kāi)頭的,RequestContext設(shè)置 forward信息、routeHost:null //其它 RequestContext設(shè)置 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())); } } } // ==== 沒(méi)有匹配到路由信息 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("http://", "/"); ctx.set(FORWARD_TO_KEY, forwardURI); } return null; }
總結(jié):
•只要引入了spring-cloud-starter-zuul就會(huì)間接引入Ribbon、Hystrix
•路由信息可能是從配置文件中加載的,也可能是通過(guò)DiscoveryClient從注冊(cè)中心加載的
•zuul是通過(guò)前置過(guò)濾器PreDecorationFilter找到與當(dāng)前requestURI匹配的路由信息,并在RequestContext中設(shè)置相關(guān)屬性的,后續(xù)的Route Filter會(huì)根據(jù)RequestContext中的這些屬性判斷如何路由轉(zhuǎn)發(fā)
•Route Filter主要使用 SimpleHostRoutingFilter 和 RibbonRoutingFilter
•當(dāng)RequestContext請(qǐng)求上下文中存在routeHost,即URL直連信息時(shí),使用SimpleHostRoutingFilter簡(jiǎn)單Host路由
•當(dāng)RequestContext請(qǐng)求上下文中存在serviceId,即服務(wù)id時(shí)(可能會(huì)與注冊(cè)中心關(guān)聯(lián)獲取服務(wù)列表,或者讀取配置文件中serviceId.ribbon.listOfServers的服務(wù)列表),使用RibbonRoutingFilter,會(huì)使用Ribbon、Hystrix
總結(jié)
以上所述是小編給大家介紹的SpringCloud Zuul在何種情況下使用Hystrix,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Spring?Cloud?Alibaba實(shí)現(xiàn)服務(wù)的無(wú)損下線功能(案例講解)
這篇文章主要介紹了Spring?Cloud?Alibaba實(shí)現(xiàn)服務(wù)的無(wú)損下線功能?,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03二叉樹(shù)基本操作之遞歸和非遞歸遍歷、分支節(jié)點(diǎn)數(shù)詳解
這篇文章主要介紹了二叉樹(shù)基本操作之遞歸和非遞歸遍歷、分支節(jié)點(diǎn)數(shù)詳解,二叉樹(shù)是由n(n>=0)個(gè)結(jié)點(diǎn)的有限集合構(gòu)成,此集合或者為空集,或者由一個(gè)根結(jié)點(diǎn)及兩棵互不相交的左右子樹(shù)組成,并且左右子樹(shù)都是二叉樹(shù),需要的朋友可以參考下2023-09-09SpringBoot配置多個(gè)數(shù)據(jù)源超簡(jiǎn)單步驟(連接多個(gè)數(shù)據(jù)庫(kù))
公司項(xiàng)目有連接多個(gè)不同數(shù)據(jù)庫(kù)的需求,特研究了一下,根據(jù)網(wǎng)上的資料,這篇文章主要給大家介紹了關(guān)于SpringBoot配置多個(gè)數(shù)據(jù)源(連接多個(gè)數(shù)據(jù)庫(kù))的相關(guān)資料,需要的朋友可以參考下2024-05-05jstack配合top命令分析CPU飆高、程序死鎖問(wèn)題
記得前段時(shí)間,同事說(shuō)他們測(cè)試環(huán)境的服務(wù)器cpu使用率一直處于100%,本地又沒(méi)有什么接口調(diào)用,為什么會(huì)這樣?cpu使用率居高不下,自然是有某些線程一直占用著cpu資源,那又如何查看占用cpu較高的線程2021-09-09mybatis-plus實(shí)現(xiàn)邏輯刪除的示例代碼
本文主要介紹了mybatis-plus實(shí)現(xiàn)邏輯刪除的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05