微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#

人生必须的知识就是引人向光明方面的明灯。这篇文章主要讲述微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#相关的知识,希望能为你提供帮助。
@[TOC](6.1 使用 Zuul 进行服务路由)
前言Zuul 是 Netflix 开源的一个 API Gateway 服务器, 本质上是一个 Web servlet 应用;
Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门;
1. Zuul 基础知识 1.1 Zuul 是什么

  • Zuul 是一种提供动态路由、监视、弹性、安全性等功能的边缘服务;
  • Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器;
1.2 Zuul 提供的功能
  • 将应用程序中的所有服务的路由映射到一个 URL,即所有客户端调用都经过这个路口;
  • 构建可以对通过网关的请求进行检查和操作的过滤器;
  • 简单来说就是:代理、路由、过滤;
1.3 Zuul 支持的三种类型过滤器
  • 前置过滤器:在请求被路由到目标服务前执行;
    • 用于:统一消息格式、用户验证和授权、打印日志等;
  • 路由过滤器:在请求被路由到目标服务时执行;
    • 用于:确定是否需要进行某些级别的动态路由;(灰度发布等)
  • 后置过滤器:在请求被路由到目标服务后执行;
    • 用于:记录响应信息、给目标服务的响应添加头信息、收集统计数据;
微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#

文章图片

2. 构建 Zuul 网关服务 2.1 引入 pom.xml 依赖
< !-- zuul相关依赖jar包 --> < dependency> < groupId> org.springframework.cloud< /groupId> < artifactId> spring-cloud-starter-zuul< /artifactId> < /dependency> < !-- 监控相关,用于查看路由信息相关 --> < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-actuator< /artifactId> < /dependency>

2.2 修改 bootstrap.yml 配置文件
  • 使用自动映射路由不需要做其他特殊配置;
  • 其他特殊配置参考本篇《2.4 配置路由》;
eureka: instance: preferIpAddress: true client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/#开启查看路由的端点 management: endpoints: web: exposure: include: routes

2.3 在主启动类上添加注解
  • @EnableZuulProxy:表示该服务为一个 Zuul 服务器(常用);
    • 开发人员需要使用 Zuul 与 Eureka 等服务注册中心(如 Consul )进行集成的时使用;
  • @EnableZuulServer:也是表示该服务为一个 Zuul 服务器(不加载任何 Zuul 反向代理过滤器,也不使用 Nettlix Eureka 进行服务发现);
    • 开发人员想要构建自己的路由服务,而不使用任何 Zuul 预置的功能时使用;
2.4 配置路由 2.4.1 自动映射路由
  • 不需要做额外配置;
  • 访问 http://localhost:zuul-port/routes
  • 通过 Zuul 注册的服务映射展示在左边,路由映射到实际的 Eureka 服务 ID 展示在右边;
  • Zuul 将基于 Eureka 服务的 ID 来公开服务,如果服务的实例没有在运行 , Zuul 将不会公开该服务的路由;
2.4.2 手动映射路由
  • 【微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#】需要在 application.yml 里进行配置:
    zuul: routes: xxxservice: /xxx/**

  • 配置前原接口:/xxxservice/**
  • 配置后的接口:/xxx/**
  • 可以配置不存在的服务路由,但当尝试为不存在的服务调用路由时会返回 500 错误;
  • 上述路由将包括自动路由和手动路由两种路由规则,如果要排除自动路由需要添加 ignored-services 属性:
    zuul: ignored-services: xxxservice routes: xxxservice: /xxx/**

    • ignored-services: * 将排除所有自动映射路由;
  • 还可以给给路由添加前缀,用来区分 API 路由与内容路由:
    zuul: ignored-services: xxxservice prefix: /api routes: xxxservice: /xxx/**

  • 上述配置后,路由接口将变为:/api/xxxservice/**
2.4.3 使用静态 URL 手动映射路由
  • 用来路由那些不受 Eureka 管理的服务,可以建立 Zuul 直接路由到一个静态定义的 URL;
    zuul: routes: yyy:#Zuul 用于在内部识别服务的关键字 path: /yyy/**#许可证服务的静态路由 url: http://yyyservice-ip:yyyservice-port#已建立许可证服务的实例,它将被直接调用,而不是由 Zuul 通过 Eureka 调用

  • 由于不使用 Eureka,因此需要手动配置 Zuul 来禁用 Ribbon 与 Eureka 集成,列出 Ribbon 将进行负载均衡的各个服务实例。配置如下:
    zuul: routes: yyy: path: /yyy/** serviceId: yyy#定义一个服务 ID,该 ID 将用于在 Ribbon 中查找服务 ribbon: eureka: enabled: false#在 Ribbon 中禁用 Eureka 支持 yyy: ribbon: listOfServers: http://yyyservice-ip-1:yyyservice-port-1, http://yyyservice-ip-2:yyyservice-port-2#指定请求会路由到服务器列表

    • 为什么要使用 Ribbon:支持 Ribbon 的服务客户端会在本地缓存服务实例的位置,不需要每次解析服务位置时都调用 Eureka;
2.4.4 动态重新加载路由配置
  • 其允许在不回收 Zuul 服务器的情况下更改路由的映射。现有的路由可以被快速修改,以及添加新的路由;
  • 动态配置相关需要结合《2.1 使用 Spring Cloud Config 管理服务配置项》篇里的使用 git 外部化微服务配置;
  • 将配置文件交给 git 后,我们直接修改配置文件即可,不需要做额外配置;
2.4.5 配置服务超时
  • Zuul 使用 Netflix 的 Hystrix 和 Ribbon 库;
    • Hystrix 会对需要超过 1s 的服务请求会返回一个 HTTP 500 错误;
    • Ribbon 则会处理超过 5s 的服务请求;
  • 可以使用如下配置进行覆盖:
    • 修改所有服务的 Hystrix 默认超时为 2.5s:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 2500
    • 修改特定服务(xxx)的 Hystrix 默认超时为 2.5s:hystrix.command.xxx.execution.isolation.thread.timeoutInMilliseconds: 2500
    • 修改特定服务(xxx)的 Ribbon 默认超时为 7s:xxx.ribbon.ReadTimeout: 7000
3. 使用 Zuul 的三种类型过滤器示例3.1 前置过滤器 TrackingFilter 示例模板
//所有 Zuul 过滤器必须扩展 ZuulFilter 类,并覆盖四个方法:filterType()、filterOrder()、shouldFilter() 和 run() @Component public class TrackingFilter extends ZuulFilter private static final int FILTER_ORDER =1; private static final boolean SHOULD_FILTER=true; private static final Logger logger = LoggerFactory.getLogger(TrackingFilter.class); //所有过滤器的常用方法都封装进 FilterUtils 类中 @Autowired FilterUtils filterUtils; //告诉的是那种类型过滤器 @Override public String filterType() return FilterUtils.PRE_FILTER_TYPE; //返回整数值,指示不同类型的过滤器执行顺序 @Override public int filterOrder() return FILTER_ORDER; //是否执行过滤器 @Override public boolean shouldFilter() return SHOULD_FILTER; //每次服务通过过滤器时执行的方法 @Override public Object run() ...

  • 其他两种 Zuul 过滤器与示例类似;
4. 使用 Zuul 的路由过滤器实现灰度发布示例
微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#

文章图片

  • 需要将 50% 的用户请求路由到新服务,50% 的用户请求路由到旧服务。即 A/B 测试;
  • 本例中先需要查询目标服务是否存在,只有存在才进行 A/B 测试;
  • 注意区分目标服务、新服务和旧服务的概念:
    • 这里的目标服务指该次请求既定的归属地,用来判断 Zuul 是否能路由过去;
    • 只有判断有目标服务后,才根据一定策略决定路由到新服务还是旧服务;
    • 路由到新服务这里称转发路由;
4.1 扩展 ZuulFilter 类
  • 与本篇《3.1 前置过滤器 TrackingFilter 示例模板》一样,需要扩展 ZuulFilter 类,并覆盖 4 个方法;
  • 其中重点在 run() 方法;
4.2 自定义 run() 方法
  • 主要的路由逻辑,先查询目标服务是否存在,再根据返回值确定路由到新版本还是旧版本;
@Override public Object run() RequestContext ctx = RequestContext.getCurrentContext(); //确定目标服务是否存在 AbTestingRoute abTestRoute = getAbRoutingInfo( filterUtils.getServiceId() ); //接受路径权重 abTestRoute,生成随机数,确定是否路由到新服务 if (abTestRoute!=null & & useNewRoute(abTestRoute)) //构建新服务的路由路径 String route = buildRouteString(ctx.getRequest().getRequestURI(), abTestRoute.getEndpoint(), ctx.get("serviceId").toString()); //完成转发到新版本服务的工作 forwardToNewRoute(route); return null;

4.3 getAbRoutingInfo() 查找目标服务
  • 该方法的作用是:确定目标服务是否存在;
private AbTestingRoute getAbRoutingInfo(String serviceName) ResponseEntity< AbTestingRoute> restExchange = null; try //尝试调用目标服务 restExchange = restTemplate.exchange( "http://newroutesservice/v1/route/abtesting/serviceName", HttpMethod.GET, null, AbTestingRoute.class, serviceName); catch(HttpClientErrorException ex) //如果没有找到目标服务,将报错 HTTP 404,并返回空值 if (ex.getStatusCode()== HttpStatus.NOT_FOUND) return null; throw ex; return restExchange.getBody();

4.4 useNewRoute() 将决定是否路由到新服务
  • 该方法的作用是:使用随机数确定调用到新版本还是旧版本;
  • 返回 true 说明路由到新服务,反之不转发;
    public boolean useNewRoute(AbTestingRoute testRoute) Random random = new Random(); //检查路由是否为活跃状态 if (testRoute.getActive().equals("N")) return false; //使用随机数确定调用到新版本还是旧版本 int value = https://www.songbingjia.com/android/random.nextInt((10 - 1) + 1) + 1; if (testRoute.getWeight()< value) return true; return false;

4.5 转发路由的逻辑
  • 在目标服务存在,且实施路由到新服务的策略后,最后执行的路由到新服务的逻辑;
//Spring Cloud 提供,用于代理服务请求的辅助方法 private ProxyRequestHelper helper = new ProxyRequestHelper(); private void forwardToNewRoute(String route) RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); //创建发送到服务的所有 HTTP 请求首部的副本 MultiValueMap< String, String> headers = this.helper.buildZuulRequestHeaders(request); //创建所有 HTTP 请求参数的副本 MultiValueMap< String, String> params = this.helper.buildZuulRequestQueryParams(request); String verb = getVerb(request); //创建新服务的 HTTP 主体的副本 InputStream requestEntity = getRequestBody(request); if (request.getContentLength() < 0) context.setChunkedRequestBody(); this.helper.addIgnoredHeaders(); CloseableHttpClient httpClient = null; HttpResponse response = null; try httpClient= HttpClients.createDefault(); //使用 forward() 方法调用新服务 response = forward(httpClient, verb, route, request, headers,params, requestEntity); //将服务调用的结果保存会 Zuul 服务器 setResponse(response); catch (Exception ex ) ex.printStackTrace(); finally try httpClient.close(); catch(IOException ex)

最后::: hljs-center
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center
微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#

文章图片

:::

    推荐阅读