栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringCloud Zuul在何种情况下使用Hystrix及问题小结

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringCloud Zuul在何种情况下使用Hystrix及问题小结

首先,引入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配置文件关联,保存相关信息

org.springframework.cloud.netflix.zuul.filters.Route, RouteLocator找到的路由信息就是这个类,用于路由转发
public static class ZuulRoute {
 private String id; //ZuulRoute的id
 private String path; //路由的pattern,如 /foo
 String adjustedPath = adjustPath(path);
 ZuulRoute route = getZuulRoute(adjustedPath);
 return getRoute(route, adjustedPath);
}

下面我们来看看locateRoutes()是如何加载静态的、已配置的路由与来自DiscoveryClient服务发现的路由的

//----------DiscoveryClientRouteLocator#locateRoutes() 服务发现路由定位器的locateRoutes()
@Override
protected linkedHashMap locateRoutes() {
 //保存ZuulRoute的linkedHashMap
 linkedHashMap routesMap = new linkedHashMap();
 //调用父类SimpleRouteLocator#locateRoutes()
 //加载ZuulProperties中的所有配置文件中的路由信息
 routesMap.putAll(super.locateRoutes());
 //如果服务发现客户端discovery存在
 if (this.discovery != null) {
  //将routesMap已经存在的配置文件中的ZuulRoute放入staticServices
  Map staticServices = new linkedHashMap();
  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 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,返回
 linkedHashMap values = new linkedHashMap<>();
 for (Entry 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请求上下文

//----------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,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对考高分网网站的支持!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/139645.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号