返回介绍

6.7 构建动态路由过滤器

发布于 2025-04-22 21:54:08 字数 8273 浏览 0 评论 0 收藏 0

本章要介绍的最后一个 Zuul 过滤器是 Zuul 路由过滤器。如果没有自定义的路由过滤器,Zuul 将根据本章前面的映射定义来完成所有路由。通过构建 Zuul 路由过滤器,可以为服务客户端的调用添加智能路由。

在本节中,我们将通过构建一个路由过滤器来学习 Zuul 的路由过滤器,从而允许对新版本的服务进行 A/B 测试。A/B 测试是推出新功能的地方,在这里有一定比例的用户能够使用新功能,而其余的用户仍然使用旧服务。在本例中,我们将模拟出一个新的组织服务版本,并希望 50%的用户使用旧服务,另外 50%的用户使用新服务。

为此,需要构建一个名为 SpecialRoutesFilter 的路由过滤器。该过滤器将接收由 Zuul 调用的服务的 Eureka 服务 ID,并调用另一个名为 SpecialRoutes 的微服务。 SpecialRoutes 服务将检查内部数据库以查看服务名称是否存在。如果目标服务名称存在,它将返回服务的权重以及替代位置的目的地。 SpecialRoutesFilter 将接收返回的权重,并根据权重随机生成一个值,用于确定用户的调用是否将被路由到替代组织服务或 Zuul 路由映射中定义的组织服务。图 6-15 展示了使用 SpecialRoutesFilter 时所发生的流程。

图 6-15 通过 SpecialRoutesFilter 调用组织服务的流程

在图 6-15 中,在服务客户端调用 Zuul 背后的服务时, SpecialRoutesFilter 会执行以下操作。

(1) SpecialRoutesFilter 检索被调用服务的服务 ID。

(2) SpecialRoutesFilter 调用 SpecialRoutes 服务。 SpecialRoutes 服务将查询是否有针对目标端点定义的替代端点。如果找到一条记录,那么这条记录将包含一个权重,它将告诉 Zuul 应该发送到旧服务和新服务的服务调用的百分比。

(3)然后 SpecialRoutesFilter 生成一个随机数,并将它与 SpecialRoutes 服务返回的权重进行比较。如果随机生成的数字大于替代端点权重的值,那么 SpecialRoutesFilter 会将请求发送到服务的新版本。

(4)如果 SpecialRoutesFilter 将请求发送到服务的新版本,Zuul 会维持最初的预定义管道,并通过已定义的后置过滤器将响应从替代服务端点发送回来。

6.7.1 构建路由过滤器的骨架

本节将介绍用于构建 SpecialRoutesFilter 的代码。在迄今为止所看到的所有过滤器中,实现 Zuul 路由过滤器所需进行的编码工作最多,因为通过路由过滤器,开发人员将接管 Zuul 功能的核心部分——路由,并使用自己的功能替换掉它。本节不会详细介绍整个类,而会讨论相关的细节。

SpecialRoutesFilter 遵循与其他 Zuul 过滤器相同的基本模式。它扩展 ZuulFilter 类,并设置了 filterType() 方法来返回“route”的值。本节不会再进一步解释 filterOrder()shouldFilter() 方法,因为它们与本章前面讨论过的过滤器没有任何区别。代码清单 6-14 展示了路由过滤器的骨架。

代码清单 6-14 路由过滤器的骨架

package com.thoughtmechanix.zuulsvr.filters;

@Component
public class SpecialRoutesFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return filterUtils.ROUTE_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {}

    @Override
    public boolean shouldFilter() {}

    @Override
    public Object run() {}
}

6.7.2 实现 run() 方法

SpecialRoutesFilter 的实际工作从代码的 run() 方法开始。代码清单 6-15 展示了此方法的代码。

代码清单 6-15 SpecialRoutesFilterrun() 方法是工作开始的地方

public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();

    AbTestingRoute abTestRoute =
    →  getAbRoutingInfo( filterUtils.getServiceId() );  ⇽---  执行对 SpecialRoutes 服务的调用,以确定该服务 ID 是否有路由记录

    if (abTestRoute!=null &&useSpecialRoute(abTestRoute)) {  ⇽---  useSpecialRoute() 方法将会接受路径的权重,生成一个随机数,并确定是否将请求转发到替代服务
        String route =  ⇽---  如果有路由记录,则将完整的 URL(包含路径)构建到由 specialroutes 服务指定的服务位置
        →  buildRouteString(
            →  ctx.getRequest().getRequestURI(),
            →  abTestRoute.getEndpoint(),
            →  ctx.get("serviceId").toString());
        forwardToSpecialRoute(route);  ⇽---  forwardToSpecialRoute() 方法完成转发到其他服务的工作
    }

    return null;
}

代码清单 6-15 中代码的一般流程是,当路由请求触发 SpecialRoutesFilter 中的 run() 方法时,它将对 SpecialRoutes 服务执行 REST 调用。该服务将执行查找,并确定是否存在针对被调用的目标服务的 Eureka 服务 ID 的路由记录。对 SpecialRoutes 服务的调用是在 getAbRoutingInfo() 方法中完成的。 getAbRoutingInfo() 方法如代码清单 6-16 所示。

代码清单 6-16 调用 SpecialRouteservice 以查看路由记录是否存在

private AbTestingRoute getAbRoutingInfo(String serviceName){
    ResponseEntity<AbTestingRoute> restExchange = null;
    try {
        restExchange = restTemplate.exchange(  ⇽---  调用 SpecialRoutesService 端点
        →  "http://specialroutesservice/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();
}

一旦确定目标服务的路由记录存在,就需要确定是否应该将目标服务请求路由到替代服务位置,或者路由到由 Zuul 路由映射静态管理的默认服务位置。为了做出这个决定,需要调用 useSpecialRoute() 方法。代码清单 6-17 展示了这个方法。

代码清单 6-17 决定是否使用替代服务路由

public boolean useSpecialRoute(AbTestingRoute testRoute){
    Random random = new Random();

    if (testRoute.getActive().equals("N"))  ⇽---  检查路由是否为活跃状态
        return false;

    int value = random.nextInt((10 - 1) + 1) + 1;  ⇽---  确定是否应该使用替代服务路由

    if (testRoute.getWeight()<value)
        return true;

    return false;
}

这个方法做了两件事。首先,该方法检查从 SpecialRoutes 服务返回的 AbTestingRoute 记录中的 active 字段。如果该记录设置为 "N" ,则 useSpecialRoute() 方法不应该执行任何操作,因为现在不希望进行任何路由。其次,该方法生成 1 到 10 之间的随机数。然后,该方法将检查返回路由的权重是否小于随机生成的数。如果条件为 true ,则 useSpecialRoute() 方法将返回 true ,表示确实希望使用该路由。

一旦确定要路由进入 SpecialRoutesFilter 的服务请求,就需要将请求转发到目标服务。

6.7.3 转发路由

SpecialRoutesFilter 中出现的大部分工作是到下游服务的路由的实际转发。虽然 Zuul 确实提供了辅助方法来使这项任务更容易,但开发人员仍然需要负责大部分工作。 forwardToSpecialRoute() 方法负责转发工作。该方法中的代码大量借鉴了 Spring Cloud 的 SimpleHostRoutingFilter 类的源代码。虽然本章不会介绍 forwardToSpecialRoute() 方法中调用的所有辅助方法,但是会介绍该方法中的代码,如代码清单 6-18 所示。

代码清单 6-18 forwardToSpecialRoute 调用替代服务

private ProxyRequestHelper helper =   ⇽--- helper 变量是类 ProxyRequestHelper 类型的一个实例变量。这是 Spring Cloud 提供的类,带有用于代理服务请求的辅助方法
→  new ProxyRequestHelper ();

private void forwardToSpecialRoute(String route) {
    RequestContext context =
    →  RequestContext.getCurrentContext();
    HttpServletRequest request = context.getRequest();

    MultiValueMap<String, String>headers =
    →  helper.buildZuulRequestHeaders(request);  ⇽--- 创建将发送到服务的所有 HTTP 请求首部的副本

    MultiValueMap<String, String> params =
    →  helper.buildZuulRequestQueryParams(request);  ⇽--- 创建所有 HTTP 请求参数的副本

    String verb = getVerb(request);
    InputStream requestEntity = getRequestBody(request);  ⇽--- 创建将被转发到替代服务的 HTTP 主体的副本
    if (request.getContentLength() < 0)
        context.setChunkedRequestBody();

    this.helper.addIgnoredHeaders();
    CloseableHttpClient httpClient = null;
    HttpResponse response = null;s

    try {
        httpClient = HttpClients.createDefault();
        response = forward(  ⇽--- 使用 forward() 辅助方法(未显示)调用替代服务
        →  httpClient,
        →  verb,
        →  route,
        →  request,
        →  headers,
        →  params,
        →  requestEntity);
        setResponse(response);  ⇽--- 通过 setResponse() 辅助方法将服务调用的结果保存回 Zuul 服务器
    }
    catch (Exception ex ) {// 为了简洁,省略了其余的代码}

}

代码清单 6-18 中的关键要点是,我们将传入的 HTTP 请求(首部参数、HTTP 动词和主体)中的所有值复制到将在目标服务上调用的新请求。然后 forwardToSpecialRoute() 方法从目标服务返回响应,并将响应设置在 Zuul 使用的 HTTP 请求上下文中。上述过程通过 setResponse() 辅助方法(未显示)完成。Zuul 使用 HTTP 请求上下文从调用服务客户端返回响应。

6.7.4 整合

既然已经实现了 SpecialRoutesFilter ,我们就可以通过调用许可证服务来查看它的动作。读者可能还记得,在前面的几章中,许可证服务调用组织服务来检索组织的联系人数据。

在代码示例中, specialroutesservice 具有用于组织服务的数据库记录,该数据库记录指示有 50%的概率把对组织服务的请求路由到现有的组织服务(Zuul 中映射的那个),50%的概率路由到替代组织服务。从 SpecialRoutes 服务返回的替代组织服务路径是 http://orgservice-new ,并且不能直接从 Zuul 访问。为了区分这两个服务,我修改了组织服务,将文本“ OLD:: ”和“ NEW:: ”添加到组织服务返回的联系人姓名的前面。

如果现在通过 Zuul 访问许可证服务端点,应该看到从许可证服务调用返回的 contactNameOLD::NEW:: 值之间变化。

http://localhost:5555/api/licensing/v1/organizations/e254f8c-c442-4ebe-a82a-
→  e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a

图 6-16 展示了这一点。

图 6-16 当访问替代组织服务时,将会看到 NEW 被添加到 contactName 前面

实现 Zuul 路由过滤器确实比实现前置过滤器或后置过滤器需要更多的工作,但它也是 Zuul 最强大的部分之一,因为开发人员可以轻松地让服务路由方式变得智能。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。