如果你正在使用 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);
- }
- }
- }
至此就基本实现了最初的想法.
这个实现方式仅供参考, 如有跟好的方式, 多多指教哈~
来源: https://www.cnblogs.com/killbug/p/10878482.html