好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

//----------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服务发现的路由的

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

//----------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请求上下文

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

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

原文链接:https://HdhCmsTestcnblogs测试数据/trust-freedom/p/9982680.html

查看更多关于SpringCloud Zuul在何种情况下使用Hystrix及问题小结的详细内容...

  阅读:14次