溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringCloud Zuul在何種情況下使用Hystrix的示例分析

發(fā)布時間:2021-08-09 11:16:20 來源:億速云 閱讀:168 作者:小新 欄目:編程語言

這篇文章主要為大家展示了“SpringCloud Zuul在何種情況下使用Hystrix的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“SpringCloud Zuul在何種情況下使用Hystrix的示例分析”這篇文章吧。

首先,引入spring-cloud-starter-zuul之后會間接引入:

SpringCloud Zuul在何種情況下使用Hystrix的示例分析

hystrix依賴已經(jīng)引入,那么何種情況下使用hystrix呢?

在Zuul的自動配置類ZuulServerAutoConfiguration和ZuulProxyAutoConfiguration中總共會向Spring容器注入3個Zuul的RouteFilter,分別是

?SimpleHostRoutingFilter

簡單路由,通過HttpClient向預(yù)定的URL發(fā)送請求

生效條件:

RequestContext.getCurrentContext().getRouteHost() != null
  && RequestContext.getCurrentContext().sendZuulResponse()

1、RequestContext中的routeHost不為空,routeHost就是URL,即使用URL直連

2、RequestContext中的sendZuulResponse為true,即是否將response發(fā)送給客戶端,默認(rèn)為true

?RibbonRoutingFilter

使用Ribbon、Hystrix和可插入的http客戶端發(fā)送請求

生效條件:

(RequestContext.getRouteHost() == null && RequestContext.get(SERVICE_ID_KEY) != null
  && RequestContext.sendZuulResponse())

1、RequestContext中的routeHost為空,即URL為空

2、RequestContext中的serviceId不為空

3、RequestContext中的sendZuulResponse為true,即是否將response發(fā)送給客戶端,默認(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是否運行過了”,在SendForwardFilter#run()時會ctx.set(SEND_FORWARD_FILTER_RAN, true)

綜上所述,在使用serviceId映射的方法路由轉(zhuǎn)發(fā)的時候,會使用Ribbon+Hystrix

而哪種路由配置方式是“URL映射”,哪種配置方式又是“serviceId映射”呢?

Zuul有一個前置過濾器PreDecorationFilter用于通過RouteLocator路由定位器決定在何時以何種方式路由轉(zhuǎn)發(fā)

RouteLocator是用于通過請求地址匹配到Route路由的,之后PreDecorationFilter再通過Route信息設(shè)置RequestContext上下文,決定后續(xù)使用哪個RouteFilter做路由轉(zhuǎn)發(fā)

所以就引出以下問題:

?什么是Route
?RouteLocator路由定位器如何根據(jù)請求路徑匹配路由
?匹配到路由后,PreDecorationFilter如何設(shè)置RequestContext請求上下文

什么是Route

我總共見到兩個和Route相關(guān)的類

ZuulProperties.ZuulRoute,用于和zuul配置文件關(guān)聯(lián),保存相關(guān)信息

org.springframework.cloud.netflix.zuul.filters.Route, RouteLocator找到的路由信息就是這個類,用于路由轉(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(); //不會傳遞給下游請求的敏感標(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ù)不同的情況,可能是一個具體的URL,可能是一個serviceId

RouteLocator路由定位器如何根據(jù)請求路徑匹配路由

Zuul在自動配置加載時注入了2個RouteLocator

?CompositeRouteLocator: 組合的RouteLocator,在getMatchingRoute()時會依次調(diào)用其它的RouteLocator,先找到先返回;CompositeRouteLocator的routeLocators集合中只有DiscoveryClientRouteLocator
?DiscoveryClientRouteLocator: 可以將靜態(tài)的、已配置的路由與來自DiscoveryClient服務(wù)發(fā)現(xiàn)的路由組合在一起,來自DiscoveryClient的路由優(yōu)先;SimpleRouteLocator的子類(SimpleRouteLocator 基于加載到ZuulProperties中的配置定位Route路由信息)

其中CompositeRouteLocator是 @Primary 的,它是組合多個RouteLocator的Locator,其getMatchingRoute()方法會分別調(diào)用其它所有RouteLocator的getMatchingRoute()方法,通過請求路徑匹配路由信息,只要匹配到了就馬上返回

默認(rèn)CompositeRouteLocator混合路由定位器的routeLocators只有一個DiscoveryClientRouteLocator,故只需分析DiscoveryClientRouteLocator#getMatchingRoute(path)

//----------DiscoveryClientRouteLocator是SimpleRouteLocator子類,其實是調(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,如果此時還未加載,調(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());
 }
 /**
  * 下面的方法主要是先對path做微調(diào)
  * 再根據(jù)path到routes中匹配到ZuulRoute
  * 最后根據(jù) ZuulRoute 和 adjustedPath 生成 Route
  */
 String adjustedPath = adjustPath(path);
 ZuulRoute route = getZuulRoute(adjustedPath);
 return getRoute(route, adjustedPath);
}

下面我們來看看locateRoutes()是如何加載靜態(tà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)用父類SimpleRouteLocator#locateRoutes()
 //加載ZuulProperties中的所有配置文件中的路由信息
 routesMap.putAll(super.locateRoutes());
 //如果服務(wù)發(fā)現(xiàn)客戶端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(); //到注冊中心找到所有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不在忽略范圍內(nèi),且routesMap中還沒有包含,添加到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;
}

此方法運行后就已經(jīng)加載了配置文件中所有路由信息,以及注冊中心中的服務(wù)路由信息,有的通過URL路由,有的通過serviceId路由

只需根據(jù)本次請求的requestURI與 路由的pattern匹配找到對應(yīng)的路由

匹配到路由后,PreDecorationFilter如何設(shè)置RequestContext請求上下文

//----------PreDecorationFilter前置過濾器
@Override
public Object run() {
 RequestContext ctx = RequestContext.getCurrentContext();
 final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
 Route route = this.routeLocator.getMatchingRoute(requestURI); //找到匹配的路由
 //----------------到上面為止是已經(jīng)分析過的,根據(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開頭的,RequestContext設(shè)置 routeHost:URL
   //如果location是 forward:開頭的,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()));
   }
  }
 }
 // ==== 沒有匹配到路由信息
 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;
}

總結(jié):

?只要引入了spring-cloud-starter-zuul就會間接引入Ribbon、Hystrix
?路由信息可能是從配置文件中加載的,也可能是通過DiscoveryClient從注冊中心加載的
?zuul是通過前置過濾器PreDecorationFilter找到與當(dāng)前requestURI匹配的路由信息,并在RequestContext中設(shè)置相關(guān)屬性的,后續(xù)的Route Filter會根據(jù)RequestContext中的這些屬性判斷如何路由轉(zhuǎn)發(fā)
?Route Filter主要使用 SimpleHostRoutingFilter 和 RibbonRoutingFilter
?當(dāng)RequestContext請求上下文中存在routeHost,即URL直連信息時,使用SimpleHostRoutingFilter簡單Host路由
?當(dāng)RequestContext請求上下文中存在serviceId,即服務(wù)id時(可能會與注冊中心關(guān)聯(lián)獲取服務(wù)列表,或者讀取配置文件中serviceId.ribbon.listOfServers的服務(wù)列表),使用RibbonRoutingFilter,會使用Ribbon、Hystrix

以上是“SpringCloud Zuul在何種情況下使用Hystrix的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI