05从零开始学习微服务之服务路由

1 服务网关

服务网关充当服务客户端和被调用的服务之间的中介。服务客户端仅与服务网关管理的单个URL进行对话。服务网关从服务客户端调用中分离出路径,并确定服务客户端正在尝试调用哪个服务。

由于服务网关位于客户端到各个服务的所有调用之间,因此它还充当服务调用的中央策略执行点(PEP)。使用集中式PEP意味着横切服务关注点可以在一个地方实现,而无须各个开发团队来实现这些关注点。举例来说,可以在服务网关中实现的横切关注点包括以下几个。

  • 静态路由
  • 动态路由
  • 验证和授权
  • 度量数据收集和日志记录

2 Spring Cloud和Netflix Zuul 简介

Spring Cloud 集成了Netflix开源项目Zuul 。Zuul是一个服务网关,它非常容易通过Spring Cloud注解进行创建和使用。

要开始使用Zuul,需要完成下面3件事。

  1. 建立一个Zuul Spring Boot项目,并配置适当的Maven依赖项。
  2. 使用Spring Cloud 注解修改这个SpringBoot项目,将其声明为Zuul 服务。
  3. 配置Zuul以便Eureka进行通信(可选)。

3 建立一个Zuul Spring Boot 项目

Zuul的所有路由映射都是通过在application.yml文件中定义路由来完成的。但是,Zuul可以根据其服务ID自动路由请求,而不需要配置。如果没有指定任何路由,Zuul将自动使用正在调用的服务的Eureka服务ID,并将其映射到下游服务实例。

3.1 pom依赖

在Maven 中建立Zuul只帘要很少的步骤,只需要在pom.xml 文件中定义一个依赖项:

  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  </dependency>

3.2 为Zuul服务使用Spring Cloud注解

这里只需要一个注解:@EnableZuulProxy,它可以使服务成为一个Zuul服务器。

@SpringBootApplication
@EnableZuulProxy//此工程是一个zuul网关
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

3.3 配置Zuul与Eureka进行通信

Zuul代理服务器默认设计为在Spring产品上工作。因此,Zuul 将自动使用Eureka来通过服务ID 查找服务,然后使用Netflix Ribbon对来自Zuul的请求进行客户端负载均衡。配置application.yml:

server:
  port: ${PORT:50201}
spring:
  application:
    name: zuul_gateway
eureka:
  client:
    registerWithEureka: true #服务注册开关
    fetchRegistry: true #服务发现开关
    serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
      defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/}

4 在Zuul中配置路由

在微服务架构的情况下, Zuul(反向代理)从客户端接收微服务调用并将其转发给下游服务。服务客户端认为它只与Zuul通信。Zuul要与下游服务进行沟通, Zuul 必须知道如何将进来的调用映射到下游路由。Zuul 有几种机制来做到这一点,包括:

  • 通过服务发现自动映射路由;
  • 使用服务发现手动映射路由;
  • 使用静态URL手动映射路由。

4.1 通过服务发现自动映射路由

Zuul的所有路由映射都是通过在application.yml 文件中定义路由来完成的。但是,Zuul可以根据其服务ID自动路由请求,而不需要配置。如果没有指定任何路由,Zuul将自动使用正在调用的服务的Eureka服务ID,并将其映射到下游服务实例。

4.2 使用服务发现手动映射路由

开发人员可以通过缩短组织名称来简化路由。开发人员可以通过在application.yml中手动定义路由映射来做到这一点。

zuul:
  routes:
    userservice: /user/**

通过添加上述配置,现在我们就可以通过访问/user/name路由来访问组织服务了。

服务网关的一种常见模式是通过使用/api之类的标记来为所有的服务调用添加前缀,从而区分API 路由与内容路由。Zuul通过在Zuul 配置中使用prefix属性来支持这项功能。

zuul:
  routes:
    userservice: /user/**
  prefix: /api

4.3 使用静态URL手动映射路由

Zuul可以用来路由那些不受Eureka管理的服务。在这种情况下,可以建立Zuul 直接路由到一个静态定义的URL。

user: Zuul用于在内部识别服务的关键字

path: 服务的静态路由

url: 已建立服务的静态实例,它将被直接调用,而不是由Zuul通过Eureka调

zuul:
  prefix: /api
  routes:
    user:
      path: /user/**
      url: http://localhost:31001

这里存在一个问题,那就是通过绕过Eureka,只有一条路径可以用来指向请求。幸运的是,开发人员可以手动配置Zuul来禁用Ribbon与Eureka集成,然后列出Ribbon将进行负载均衡的各个服务实例。

zuul:
  prefix: /api
  routes:
    user:
      path: /user/**
      serviceid: userservice
ribbon:
  eureka:
    enabled: false
user:
  ribbon:
    listOfServers: http://localhost:31001

5 过滤器

虽然通过Zuul网关代理所有请求确实可以简化服务调用,但是在想要编写应用于所有流经网关的服务调用的自定义逻辑时,Zuul的真正威力才发挥出来。在大多数情况下,这种自定义逻辑用于强制执行一组一致的应用程序策略,如安全性、日志记录和对所有服务的跟踪。

Zuul支持以下3 种类型的过滤器:

  • 前置过滤器:前置过滤器在Zuul将实际请求发送到目的地之前被调用。
  • 后置过滤器:后置过滤器在目标服务被调用并将响应发送回客户端后被调用。
  • 路由过滤器:路由过滤器用于在调用目标服务之前拦截调用。

如果遵循上图中所列出的流程,将会看到所有的事情都是从服务客户端调用服务网关公开的服务开始的。从这里开始,发生了以下活动。

  1. 在请求进入Zuul网关时,Zuul调用所有在Zuul网关中定义的前置过滤器。前置过滤器可以在HTTP请求到达实际服务之前对HTTP 请求进行检查和修改。前置过滤器不能将用户重定向到不同的端点或服务。
  2. 在针对Zuul 的传人请求执行前置过滤器之后,Zuul 将执行已定义的路由过滤器。路由过滤器可以更改服务所指向的目的地。
  3. 路由过滤器可以将服务调用重定向到Zuul 服务器被配置的发送路由以外的位置。但Zuul路由过滤器不会执行HTTP重定向而是会终止传入的HTTP 请求然后代表原始调用者调用路由。这意味着路由过滤器必须完全负责动态路由的调用, 并且不能执行HTTP重定向。
  4. 如果路由过滤器没有动态、地将调用者重定向到新路由, Zuul服务器将发送到最初的目标服务的路由。
  5. 目标服务被调用后,Zuul后置过滤器将被调用。后置过滤器可以检查和修改来自被调用服务的响应。

5.1 前置过滤器

我们首先将构建一个名为TrackingFilter的Zuul前置过滤器,该过滤器将检查所有到网关的传入请求,并确定请求中是否存在名为tmx-correlation-id 的HTTP首部。tmx-correlation-id首部将包含一个唯一的全局通用ID (Globally Universal ID , GUID),它可用于跨多个微服务来跟踪用户请求(关联ID记录请求经过了哪些服务)。

如果在HTTP首部中不存在tmx-correlation-id ,那么Zuul TrackingFilter将生成并设置该关联ID 。如果已经存在关联ID,那么Zuul将不会对该关联ID进行任何操作(关联ID是通过HTTP进行传播的)。

什么是关联ID,后面会详细解释。

5.1.1 TrackingFilter (前置过滤器)

要在Zuul 中实现过滤器,必须扩展ZuulFilter 类,然后覆盖4 个方法,即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);
    @Autowired
    FilterUtils filterUtils;

    @Override
    //该过滤器是前置、路由还是后置过滤器
    public String filterType() {
        return filterUtils.PRE_FILTER_TYPE;
    }

    @Override
    //指示不同的过滤器的执行顺序
    public int filterOrder() {
        return FILTER_ORDER;
    }
    //返回一个布尔值,指示该过滤器是否要执行
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    //判断关联id是否存在
    private boolean isCorrelationIdPresent(){
        if (filterUtils.getCorrelationId() !=null){
            return true;
        }
        return false;
    }

    //借助工具类生成GUID,GUID:关联id首部包含的唯一的全局通用ID
    private String generateCorrelationId(){
        return java.util.UUID.randomUUID().toString();
    }

    public Object run() {
        //把关联id写入日志
        if (isCorrelationIdPresent()) {
            logger.debug("tmx-correlation-id found in tracking filter: {}. ", filterUtils.getCorrelationId());
        }
        else{
            filterUtils.setCorrelationId(generateCorrelationId());
            logger.debug("tmx-correlation-id generated in tracking filter: {}.", filterUtils.getCorrelationId());
        }

        RequestContext ctx = RequestContext.getCurrentContext();
        logger.debug("Processing incoming request for {}.",  ctx.getRequest().getRequestURI());
        return null;
    }
}

5.1.2 FilterUtils类

我们已经实现了一个名为FilterUtils 的类。这个类用于封装所有过滤器使用的常用功能。


@Component
public class FilterUtils {

    public static final String CORRELATION_ID = "tmx-correlation-id";
    public static final String AUTH_TOKEN     = "tmx-auth-token";
    public static final String USER_ID        = "tmx-user-id";
    public static final String ORG_ID         = "tmx-org-id";
    public static final String PRE_FILTER_TYPE = "pre";
    public static final String POST_FILTER_TYPE = "post";
    public static final String ROUTE_FILTER_TYPE = "route";

    public String getCorrelationId(){
        RequestContext ctx = RequestContext.getCurrentContext();

        if (ctx.getRequest().getHeader(CORRELATION_ID) !=null) {
            return ctx.getRequest().getHeader(CORRELATION_ID);
        }
        else{
            return  ctx.getZuulRequestHeaders().get(CORRELATION_ID);
        }
    }

    public void setCorrelationId(String correlationId){
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader(CORRELATION_ID, correlationId);
    }

    public  final String getOrgId(){
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.getRequest().getHeader(ORG_ID) !=null) {
            return ctx.getRequest().getHeader(ORG_ID);
        }
        else{
            return  ctx.getZuulRequestHeaders().get(ORG_ID);
        }
    }

    public void setOrgId(String orgId){
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader(ORG_ID,  orgId);
    }

    public final String getUserId(){
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.getRequest().getHeader(USER_ID) !=null) {
            return ctx.getRequest().getHeader(USER_ID);
        }
        else{
            return  ctx.getZuulRequestHeaders().get(USER_ID);
        }
    }

    public void setUserId(String userId){
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader(USER_ID,  userId);
    }

    public final String getAuthToken(){
        RequestContext ctx = RequestContext.getCurrentContext();
        return ctx.getRequest().getHeader(AUTH_TOKEN);
    }

    public String getServiceId(){
        RequestContext ctx = RequestContext.getCurrentContext();

        //We might not have a service id if we are using a static, non-eureka route.
        if (ctx.get("serviceId")==null) return "";
        return ctx.get("serviceId").toString();
    }
}

5.2 在服务调用中使用关联ID

既然已经确保每个流经Zuul的微服务调用都添加了关联ID,那么如何确保:

  • 正在被调用的微服务可以很容易访问关联ID;
  • 下游服务调用微服务时可能也会将关联ID传播到下游调用中。

要实现这一点,需要为每个微服务构建一组3个类。这些类将协同工作,从传人的HTTP请求中读取关联ID(以及稍后添加的其他信息),并将它映射到可以由应用程序中的业务逻辑轻松访问和使用的类,然后确保关联ID被传播到任何下游服务调用。

如下图所示,生成和获取关联ID的步骤:

  1. 当通过Zuul网关对许可证服务进行调用时,TrackingFilter会为所有进入Zuul的调用在传入的HTTP首部中注入一个关联ID 
  2. UserContextFilter 类是一个自定义的HTTP servlet过滤器。它将关联ID映射到UserContext类。UserContext 存储在本地线程存储中,以便稍后在调用中使用。
  3. 客户端需要执行对服务的调用。
  4. RestTernplate用于调用服务。Rest Template 将使用自定义的Spring 拦截器类(UserContextinterceptor)将关联ID作为HTTP首部注入出站调用。

5.2.1 UserContextFilter:拦截传入的HTTP请求

要构建的第一个类是UserContextFilter类。这个类是一个HTTP servlet过滤器,它将拦截进入服务的所有传人Hπp请求,并将关联ID(和其他一些值) 从HTTP请求映射到UserContext类。

@Component
public class UserContextFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(UserContextFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        //过滤器从首部中检索关联ID,并将值设置在UserContext类
        UserContextHolder.getContext().setCorrelationId(  httpServletRequest.getHeader(UserContext.CORRELATION_ID) );
        UserContextHolder.getContext().setUserId(httpServletRequest.getHeader(UserContext.USER_ID));
        UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(UserContext.AUTH_TOKEN));
        UserContextHolder.getContext().setOrgId(httpServletRequest.getHeader(UserContext.ORG_ID));

        logger.debug("Special Routes Service Incoming Correlation id: {}", UserContextHolder.getContext().getCorrelationId());

        filterChain.doFilter(httpServletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}
}

5.2.2 UserContexts:使服务易于访问HTTP首部

UserContext类用于保存由微服务处理的单个服务客户端请求的HTTP首部值。

@Component
public class UserContext {
    public static final String CORRELATION_ID = "tmx-correlation-id";
    public static final String AUTH_TOKEN     = "tmx-auth-token";
    public static final String USER_ID        = "tmx-user-id";
    public static final String ORG_ID         = "tmx-org-id";

    private String correlationId= new String();
    private String authToken= new String();
    private String userId = new String();
    private String orgId = new String();

    public String getCorrelationId() { return correlationId;}
    public void setCorrelationId(String correlationId) {
        this.correlationId = correlationId;
    }

    public String getAuthToken() {
        return authToken;
    }

    public void setAuthToken(String authToken) {
        this.authToken = authToken;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getOrgId() {
        return orgId;
    }

    public void setOrgId(String orgId) {
        this.orgId = orgId;
    }

5.2.3 自定义RestTemplate和UserContextlnteceptor: 确保关联ID被传播

为了使用UserContextinterceptor,我们需要定义一个Rest Template bean,然后将UserContextlnterceptcir添加进去。

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        RestTemplate template = new RestTemplate();
        List interceptors = template.getInterceptors();
        if (interceptors == null) {
            template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
        } else {
            interceptors.add(new UserContextInterceptor());
            template.setInterceptors(interceptors);
        }

        return template;
    }

5.3 后置过滤器

Zuul代表服务客户端执行实际的HTTP 调用。Zuul有机会从目标服务调用中检查响应, 然后修改响应或以额外的信息装饰它。当与以前置过滤器捕获数据相结合时,Zuul后置过滤器是收集指标井完成与用户事务相关联的日志记录的理想场所。

我们将使用Zuul后置过滤器将关联ID注入HTTP 响应首部中,该HTTP响应首部传回给服务调用者。这样,就可以将关联ID传回给调用者,而无需接触消息体。

@Component
public class ResponseFilter extends ZuulFilter{
    private static final int  FILTER_ORDER=1;
    private static final boolean  SHOULD_FILTER=true;
    private static final Logger logger = LoggerFactory.getLogger(ResponseFilter.class);
    
    @Autowired
    FilterUtils filterUtils;

    @Override
    public String filterType() {
        return FilterUtils.POST_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

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

        logger.debug("Adding the correlation id to the outbound headers. {}", filterUtils.getCorrelationId());
        System.out.println(filterUtils.getCorrelationId());
        //获取原始HTTP请求中传入的关联ID,并将它注入到响应中
        ctx.getResponse().addHeader(FilterUtils.CORRELATION_ID, filterUtils.getCorrelationId());
        //记录传出请求URI,这样就可以显示Zuul的用户请求的传入和传出条目
        logger.debug("Completing outgoing request for {}.", ctx.getRequest().getRequestURI());

        return null;
    }
}

5.4 测试

从下图可以看出,调用的HTTP响应首部上看到一个tmx-correlation-id 。

6 动态路由过滤器

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

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

如下图所示,在服务客户端调用Zuul 背后的服务时,SpecialRoutesFilter 会执行以下操作。

  1. SpecialRoutesFilter 检索被调用服务的服务ID 。
  2. SpecialRoutesFilter 调用SpecialRoutes 服务。SpecialRoutes 服务将查询是否有针对目标端点定义的替代端点。如果找到一条记录,那么这条记录将包含一个权重,它将告诉Zuul应该发送到旧服务和新服务的服务调用的百分比。
  3. 然后SpecialRoutesFilter生成一个随机数,并将它与SpecialRoutes服务返回的权重进行比较。如果随机生成的数字大于替代端点权重的值,那么SpecialRoutesFilter会将请求发送到服务的新版本。
  4. 如果SpecialRoutesFilter 将请求发送到服务的新版本,Zuul会维持最初的预定义管道,并通过已定义的后置过滤器将响应从替代服务端点发送回来。

6.1 路由过滤器 

@Component
public class SpecialRoutesFilter extends ZuulFilter {
    private static final int FILTER_ORDER =  1;
    private static final boolean SHOULD_FILTER =true;

    @Autowired
    FilterUtils filterUtils;

    @Autowired
    RestTemplate restTemplate;

    @Override
    public String filterType() {
        return filterUtils.ROUTE_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    private ProxyRequestHelper helper = new ProxyRequestHelper();

    private AbTestingRoute getAbRoutingInfo(String serviceName){
        ResponseEntity<AbTestingRoute> restExchange = null;
        try {
            restExchange = restTemplate.exchange(
                             "http://specialroutesservice/v1/route/abtesting/{serviceName}",
                             HttpMethod.GET,
                             null, AbTestingRoute.class, serviceName);
        }
        catch(HttpClientErrorException ex){
            if (ex.getStatusCode()== HttpStatus.NOT_FOUND) return null;
            throw ex;
        }
        return restExchange.getBody();
    }

    private String buildRouteString(String oldEndpoint, String newEndpoint, String serviceName){
        int index = oldEndpoint.indexOf(serviceName);

        String strippedRoute = oldEndpoint.substring(index + serviceName.length());
        System.out.println("Target route: " + String.format("%s/%s", newEndpoint, strippedRoute));
        return String.format("%s/%s", newEndpoint, strippedRoute);
    }

    private String getVerb(HttpServletRequest request) {
        String sMethod = request.getMethod();
        return sMethod.toUpperCase();
    }

    private HttpHost getHttpHost(URL host) {
        HttpHost httpHost = new HttpHost(host.getHost(), host.getPort(),
                host.getProtocol());
        return httpHost;
    }

    private Header[] convertHeaders(MultiValueMap<String, String> headers) {
        List<Header> list = new ArrayList<>();
        for (String name : headers.keySet()) {
            for (String value : headers.get(name)) {
                list.add(new BasicHeader(name, value));
            }
        }
        return list.toArray(new BasicHeader[0]);
    }

    private HttpResponse forwardRequest(HttpClient httpclient, HttpHost httpHost,
                                        HttpRequest httpRequest) throws IOException {
        return httpclient.execute(httpHost, httpRequest);
    }


    private MultiValueMap<String, String> revertHeaders(Header[] headers) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        for (Header header : headers) {
            String name = header.getName();
            if (!map.containsKey(name)) {
                map.put(name, new ArrayList<String>());
            }
            map.get(name).add(header.getValue());
        }
        return map;
    }

    private InputStream getRequestBody(HttpServletRequest request) {
        InputStream requestEntity = null;
        try {
            requestEntity = request.getInputStream();
        }
        catch (IOException ex) {
            // no requestBody is ok.
        }
        return requestEntity;
    }

    private void setResponse(HttpResponse response) throws IOException {
        this.helper.setResponse(response.getStatusLine().getStatusCode(),
                response.getEntity() == null ? null : response.getEntity().getContent(),
                revertHeaders(response.getAllHeaders()));
    }

    private HttpResponse forward(HttpClient httpclient, String verb, String uri,
                                 HttpServletRequest request, MultiValueMap<String, String> headers,
                                 MultiValueMap<String, String> params, InputStream requestEntity)
            throws Exception {
        Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
                requestEntity);
        URL host = new URL( uri );
        HttpHost httpHost = getHttpHost(host);

        HttpRequest httpRequest;
        int contentLength = request.getContentLength();
        InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
                request.getContentType() != null
                        ? ContentType.create(request.getContentType()) : null);
        switch (verb.toUpperCase()) {
            case "POST":
                HttpPost httpPost = new HttpPost(uri);
                httpRequest = httpPost;
                httpPost.setEntity(entity);
                break;
            case "PUT":
                HttpPut httpPut = new HttpPut(uri);
                httpRequest = httpPut;
                httpPut.setEntity(entity);
                break;
            case "PATCH":
                HttpPatch httpPatch = new HttpPatch(uri );
                httpRequest = httpPatch;
                httpPatch.setEntity(entity);
                break;
            default:
                httpRequest = new BasicHttpRequest(verb, uri);

        }
        try {
            httpRequest.setHeaders(convertHeaders(headers));
            HttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest);

            return zuulResponse;
        }
        finally {
        }
    }



    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;
    }

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

        AbTestingRoute abTestRoute = getAbRoutingInfo( filterUtils.getServiceId() );

        if (abTestRoute!=null && useSpecialRoute(abTestRoute)) {
            String route = buildRouteString(ctx.getRequest().getRequestURI(),
                    abTestRoute.getEndpoint(),
                    ctx.get("serviceId").toString());
            forwardToSpecialRoute(route);
        }

        return null;
    }

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

        MultiValueMap<String, String> headers = this.helper
                .buildZuulRequestHeaders(request);
        MultiValueMap<String, String> params = this.helper
                .buildZuulRequestQueryParams(request);
        String verb = getVerb(request);
        InputStream requestEntity = getRequestBody(request);
        if (request.getContentLength() < 0) {
            context.setChunkedRequestBody();
        }

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

        try {
            httpClient  = HttpClients.createDefault();
            response = forward(httpClient, verb, route, request, headers,
                    params, requestEntity);
            setResponse(response);
        }
        catch (Exception ex ) {
            ex.printStackTrace();

        }
        finally{
            try {
                httpClient.close();
            }
            catch(IOException ex){}
        }
    }
}

6.1.1 run()方法

当路由请求触发Spe cialRoutesFilter 中的run()方法时,它将对SpecialRoutes 服务执行REST调用。该服务将执行查找,并确定是否存在针对被调用的目标服务的Eureka服务ID的路由记录。

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

6.1.2 SpecialRouteservice()方法

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

6.1.3 forwardToSpecialRoute()方法

forwardToSpecialRoute()方法负责转发工作。

7 关联ID

7.1 线程上下文和Hystrix

当一个@HystrixCommand 被执行时,它可以使用两种不同的隔离策略THREAD(线程)和SEMAPHORE (信号量)来运行。在默认情况下, Hystrix 以THREAD 隔离策略运行。用于保护调用的每个Hystrix命令都在一个单独的线程池中运行,该线程池不与父线程共享它的上下文。

7.2 Threadlocal 与Hystrix

在默认情况下,Hystrix 不会将父线程的上下文传播到由Hystrix命令管理的线程中。例如,在默认情况下,对被父线程调用并由@HystrixComman保护的方法而言,在父线程中设置为ThreadLocal值的值都是不可用的(再强调一次,这是假设当前使用的是THREAD隔离级别) 。

通常在基于REST的环境中,开发人员希望将上下文信息传递给服务调用, 这将有助于在运维上管理该服务(收集跨很多服务的请求相关的日志)。例如, 可以在REST 调用的HTTP首部中传递关联ID ( correlation ID)或验证令牌, 然后将其传播到任何下游服务调用。关联ID是唯一标识符, 该标识符可用于在单个事务中跨多个服务调用进行跟踪。要使服务调用中的任何地方都可以使用此值,开发人员可以使用Spring过滤器类来拦截对REST服务的每个调用,并从传人的HTTP请求中检索此信息, 然后将此上下文信息存储在自定义的UserContext对象中。然后在任何需要在REST服务调用中访问该值的时候, 可以从ThreadLocal存储变量中检索UserContext并读取该值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值