Spring Cloud体系实现标签路由
如果你正在使用Spring Cloud体系,在实际使用过程中正遇到以下问题,可以阅读本文章的内容作为后续你解决这些问题的参考,文章内容不保证无错,请务必仔细思考之后再进行实践。
问题:
1,本地连上开发或测试环境的集群连调,正常测试请求可能会请求到本地,被自己的debug阻塞。
2,测试环境维护时,多项目并发提测,维护多个相同的集群进行测试是否必要,是否有更好的方案。
一般,我们在使用Spring Cloud全家桶的时候,会选择zuul作为网关,Ribbon作为负载均衡器,Feign作为远程服务调用模版。使用过Spring Cloud的同学对这些组件的作用必然非常熟悉。这里就拿这些组件组合成的微服务集群来实现标签路由的功能。
实现的效果如图所示,在头上带上标签的请求会在经过网关和各个应用时进行标签判断流量应该打到哪一个去,而每一个应用自己本身的标签是通过eureka上的matedate实现的。
如下图可以构想动态修改标签控制应用所能承接的请求,这里暂时不描述mq部分的功能:
答案:
实现一个ZoneAvoidanceRule的继承类,重写getPredicate方法:
@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:
public class FeignZoneHeaderInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { String requestZone = RequestZoneLabelContext.getRequestZone(); if(StringUtils.isNotBlank(requestZone)){ template.header("zone", requestZone); } } }
至此就基本实现了最初的想法。
这个实现方式仅供参考,如有跟好的方式,多多指教哈~
- 使用Spring Cloud Zuul实现动态路由
- 利用Spring Cloud Zuul实现动态路由示例代码
- Spring Cloud 系列之 Spring cloud gateway 实现网关路由转发和过滤功能
- spring cloud 整合 zuul 实现简单路由时 zuul No route found for uri 问题
- rabbitmq 路由spring-amqp rabbit标签实现
- SpringCloud教程十:Zuul+Mysql实现动态路由
- Spring Cloud Zuul实现动态路由(zuul设计)
- SpringCloud Zuul实现动态路由
- 扩展spring 的 xml schema,实现自己的标签
- spring cloud : 网关Zuul(过滤:安全、监控、限流、路由)
- spring cloud ribbon 两则实例(自定义zone与脱离eureka注册中心路由)
- 利用springcloud+Redis在分布式系统中实现分布式锁
- spring cloud gateway 限流的实现与原理
- spring cloud gateway 限流的实现与原理
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十四):权限控制(Shiro 注解)
- Spring Cloud Hystrix实现服务容错
- 【Spring Cloud】Zuul 路由访问
- 【Spring Cloud】Zuul 路由访问
- 【spring cloud】自定义jwt实现spring cloud nosession
- Spring Cloud zuul自定义统一异常处理实现方法