Spring Cloud体系实现标签路由

若是你正在使用Spring Cloud体系,在实际使用过程当中正遇到如下问题,能够阅读本文章的内容做为后续你解决这些问题的参考,文章内容不保证无错,请务必仔细思考以后再进行实践。java

问题:

1,本地连上开发或测试环境的集群连调,正常测试请求可能会请求到本地,被本身的debug阻塞。
2,测试环境维护时,多项目并发提测,维护多个相同的集群进行测试是否必要,是否有更好的方案。服务器

通常,咱们在使用Spring Cloud全家桶的时候,会选择zuul做为网关,Ribbon做为负载均衡器,Feign做为远程服务调用模版。使用过Spring Cloud的同窗对这些组件的做用必然很是熟悉。这里就拿这些组件组合成的微服务集群来实现标签路由的功能。并发

实现的效果如图所示,在头上带上标签的请求会在通过网关和各个应用时进行标签判断流量应该打到哪个去,而每个应用本身自己的标签是经过eureka上的matedate实现的。
app

以下图能够构想动态修改标签控制应用所能承接的请求,这里暂时不描述mq部分的功能:
负载均衡

答案:

实现一个ZoneAvoidanceRule的继承类,重写getPredicate方法:ide

@Override
public AbstractServerPredicate getPredicate() {
    OfflineEnvMetadataAwarePredicate offlineEnvMetadataAwarePredicate = new OfflineEnvMetadataAwarePredicate();
    offlineEnvMetadataAwarePredicate.setEnv(env);
    return offlineEnvMetadataAwarePredicate;
}

Predicate的实现屏蔽了开发测试环境中非这个环境网段启动的应用,而且比对请求的标签和本地的标签,来控制路由给哪个服务器。微服务

/**
 * 线下环境路由策略具体逻辑
 */
public class OfflineEnvMetadataAwarePredicate extends AbstractServerPredicate {


    private String env;


    public void setEnv(String env) {
        this.env = env;
    }


    @Override
    public boolean apply(PredicateKey predicateKey) {
        if(predicateKey == null || !(predicateKey.getServer() instanceof DiscoveryEnabledServer)){
            return true;
        }
        DiscoveryEnabledServer server = (DiscoveryEnabledServer) predicateKey.getServer();
        String serverZone = server.getInstanceInfo().getMetadata().get("zone");
        String requestZone = RequestZoneLabelContext.getRequestZone();
        // dev || sit 环境 本地不容许直接连调
        if(env.equals("sit") || env.equals("dev")){
            if(StringUtils.isBlank(requestZone) && !server.getHost().startsWith("10.0")){
                return false;
            }
        }


        if(StringUtils.isNotBlank(serverZone)) {
            return serverZone.equals(requestZone);
        }else if(StringUtils.isNotBlank(requestZone)){
            return requestZone.equals(serverZone);
        }


        return true;


    }
}

那么咱们注意到请求头上的标签要在初始时就拿到,因此须要一个ServletRequestListener,将拿到的zone放入RequestZoneLabelContext。咱们知道在一个请求中若是是一个io线程执行到底,咱们只须要利用threadlocal来存储线程变量,但是若是一个请求中会产生不定的子线程完成,数据在线程间的传递就成为问题,这里使用了InheritableThreadLocal来决解,在RequestZoneLabelContext中能够看到。测试

public class RequestZoneLabelContextListener implements ServletRequestListener {


    private static final String ZONE_LABEL_NAME = "zone";


    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        RequestZoneLabelContext.remove();
    }


    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) {
        HttpServletRequest request = (HttpServletRequest)requestEvent.getServletRequest();
        String lbZone = request.getHeader(ZONE_LABEL_NAME);
        if(StringUtils.isNotBlank(lbZone)){
            RequestZoneLabelContext.setZone(lbZone);
        }
    }
}
/**
 * 从request header上传递label到feign请求
 */
public class RequestZoneLabelContext {


    private static InheritableThreadLocal<String> zoneLabelThreadLocal = new InheritableThreadLocal<>();


    public static void setZone(String zone){
        zoneLabelThreadLocal.set(zone);
    }


    public static String getRequestZone(){
        return zoneLabelThreadLocal.get();
    }


    public static void remove(){
        zoneLabelThreadLocal.remove();
    }
}

那么在应用之间调用的feign中咱们是须要继续把这个zone经过header传递下去的,因此又扩展了RequestInterceptor:this

public class FeignZoneHeaderInterceptor implements RequestInterceptor {


    @Override
    public void apply(RequestTemplate template) {
        String requestZone = RequestZoneLabelContext.getRequestZone();
        if(StringUtils.isNotBlank(requestZone)){
            template.header("zone", requestZone);
        }
    }
}

至此就基本实现了最初的想法。线程

这个实现方式仅供参考,若有跟好的方式,多多指教哈~

相关文章
相关标签/搜索