【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的更多相关文章
- C#关键字var是什么,在何种情况下使用
从.NET 3.0开始,在方法内部可以使用var关键字声明局部变量.var关键字到底是什么?在何种情况下使用呢? □ var关键字用来隐式地声明一个数据类型,变量类型是在编译期确定的,而不是在运行时确 ...
- SpringCloud之Zuul高并发情况下接口限流(十二)
高并发下接口限流技术gauva(谷歌的框架) MySql最大连接数3000: 原理:框架每秒向桶里放100个令牌,接口请求来了先去拿令牌,拿到令牌后才能继续向后走,否则不允许向后执行:当接口请求太频繁 ...
- 小D课堂 - 新版本微服务springcloud+Docker教程_6-05 高级篇幅之高并发情况下
笔记 5.高级篇幅之高并发情况下接口限流特技 简介:谷歌guava框架介绍,网关限流使用 1.nginx层限流 2.网关层限流 开始 mysql最大的连接数就是3千多.如果想把应用搞好 ...
- SpringCloud Zuul 路由映射规则配置
阅读目录 前言 快速入门 路由详解 Cookie与头信息 本地跳转 Hystrix和Ribbon支持 过滤器解释 动态加载 后记 回到目录 前言 本文起笔于2018-06-26周二,接了一个这周要完成 ...
- 使用springcloud zuul构建接口网关
一 微服务网关背景及简介 不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才能完成一个业务需求.比如一个电影购票的收集APP,可能回调用电影分类微服务,用户微服务,支付微服 ...
- springcloud Zuul中路由配置细节
上篇文章我们介绍了API网关的基本构建方式以及请求过滤,小伙伴们对Zuul的作用应该已经有了一个基本的认识,但是对于路由的配置我们只是做了一个简单的介绍,本文我们就来看看路由配置的其他一些细节. 首先 ...
- spring-cloud zuul网关
API Gateway 是随着微服务(Microservice)这个概念一起兴起的一种架构模式,它用于解决微服务过于分散,没有一个统一的出入口进行流量管理的问题. 使用 Zuul 实现 API Gat ...
- springcloud -zuul(2-执行流程及源码)
官方图 1.Servlet zuul.servletPath默认配置为/zuul,故请求为/zuul开头的会跳过dispatcherServlet直接进入ZuulServlet,该配置可以自定义配置, ...
- SpringCloud学习笔记(七):Hystrix断路器
概述 什么时候需要断路器?熔断? 举个简单的例子:小明喜欢小美,可是小美没有电话,小美给了小明家里的座机,小明打给座机,这个时候小美的妈妈接到了,小明怕妈妈知道自己喜欢小美,就跟小美妈妈说让小美哥接电 ...
随机推荐
- 三色GDOI
前面说点什么.. 翻了翻别人的游记.. 发现自己从来没写过gdoi的游记.. 那就.. 一起写完它吧.. GDOI2015 肇庆 没啥印象,只有复联2的首映.. 那时候真的是什么都不懂.. 就是外出打 ...
- Unity-修改Debug日志文本颜色
Unity开发过程中Debug信息是非常重要的,但是千篇一律的白色字符不能迅速找出想要的信息,添加些字体颜色是个很好的办法,比如: AppDebug.Log("<color=#ff84 ...
- Matlab调用遗传工具箱复现论文模型求解部分
原文转载自:https://blog.csdn.net/robert_chen1988/article/details/52431594 论文来源: https://www.sciencedirect ...
- RTN 实操
创建房间 test-rtn 10001 e2uii6r7r 8LfwOcreM76OiV1V1y8jXrMG_BNa-cmktpWUznRa:kdYdsEpcYLc5ceWEHPaK0ZDI7Qc=: ...
- IDEA环境下SSM整合------注解开发
根据前一篇文章的步骤,目前项目进度应该是:核心过滤器配置完成.DispatcherServlet和ContextLoader配置完成.数据库dataSource配置完成.视图解析器配置完成.Mappe ...
- FTP出现PORT模式成功, 请更新你的站点配置文件
最近用FTP连接站点,经常出现连接不上或者连接失败,提示以PASV模式连接失败,正在使用PORT模式连接,最后才能连接成功,连接时间也是相当长,又慢又不稳定. 工具/原料 FlashFXP等F ...
- Mesos源码分析(12): Mesos-Slave接收到RunTask消息
在前文Mesos源码分析(8): Mesos-Slave的初始化中,Mesos-Slave接收到RunTaskMessage消息,会调用Slave::runTask. void Slave::ru ...
- [Swift]LeetCode336. 回文对 | Palindrome Pairs
Given a list of unique words, find all pairs of distinct indices (i, j) in the given list, so that t ...
- [Swift]LeetCode516. 最长回文子序列 | Longest Palindromic Subsequence
Given a string s, find the longest palindromic subsequence's length in s. You may assume that the ma ...
- [Java]LeetCode690. 员工的重要性 | Employee Importance
You are given a data structure of employee information, which includes the employee's unique id, his ...