若是你正在使用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); } } }
至此就基本實現了最初的想法。線程
這個實現方式僅供參考,若有跟好的方式,多多指教哈~