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/** 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)
//----------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服务发现的路由的
//----------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请求上下文
//----------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,希望对大家有所帮助