個人博客 轉載請註明原創出處。前端
小程序新版本上線須要審覈,若是有接口新版本返回內容發生了變化,後端直接上線會致使舊版本報錯,不上線審覈又通不過。java
以前是經過寫新接口來兼容,可是這樣會有不少兼容代碼或者冗餘代碼,開發也不容易能想到這一點,常常直接修改了舊接口,因而版本控制就成了迫切的需求。spring
全部請求都是走的網關,很天然的就能想到在網關層實現版本控制。首先想到的是在ZuulFilter
過濾器中實現,前端全部請求都在請求頭中增長一個version
的header
,而後進行匹配。可是這樣只能獲取到前端的版本,不能匹配選擇後端實例。小程序
查詢資料後發現應該在負載均衡的時候實現版本控制。一樣是前端全部請求都在請求頭中增長一個version
的header
,後端實例都配置一個版本的tag
。後端
首先須要說明的是我選擇的控制中心是consul
,網關是zuul
。bash
負載均衡策略被抽象爲IRule
接口,項目默認狀況下使用的IRule
的子類ZoneAvoidanceRule extends PredicateBasedRule
,咱們須要實現一個PredicateBasedRule
的子類來替換ZoneAvoidanceRule
。服務器
PredicateBasedRule
須要實現一個過濾的方法咱們就在這個方法裏實現版本控制,過濾後就是默認的負載均衡策略了,默認是輪詢。markdown
/** * Method that provides an instance of {@link AbstractServerPredicate} to be used by this class. * */ public abstract AbstractServerPredicate getPredicate(); 複製代碼
咱們能夠看到PredicateBasedRule
的getPredicate()
方法須要返回一個AbstractServerPredicate
實例,這個實例具體定義了版本控制的業務邏輯。代碼以下:app
private static class VersionPredicate extends AbstractServerPredicate { private static final String VERSION_KEY = "version"; @Override public boolean apply(@NullableDecl PredicateKey predicateKey) { if (predicateKey == null) { return true; } RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String version = request.getHeader(VERSION_KEY); if (version == null) { return true; } ConsulServer consulServer = (ConsulServer) predicateKey.getServer(); if (!consulServer.getMetadata().containsKey(VERSION_KEY)) { return true; } return consulServer.getMetadata().get(VERSION_KEY).equals(version); } } 複製代碼
首先來了解下負載均衡的過程。一個請求到達網關後會解析出對應的服務名,而後會獲取到該服務的全部可用實例,以後就會調用咱們的過濾方法過濾出該請求可用的全部服務實例,最後進行輪詢負載均衡。負載均衡
PredicateKey
類就是上層方法將可用實例Server
和loadBalancerKey
封裝後的類。版本控制的業務邏輯以下:
predicateKey
是否爲null
,是的話直接返回true
,true
表明該實例可用RequestContext
獲取當前請求實例HttpServletRequest
,再經過請求實例獲取請求頭裏的版本號true
ConsulServer
類,這裏是由於我用的註冊中心是consul
,選擇其餘的可自行轉換成對應的實現類spring.cloud.consul.discovery.tags="version=1.0.0"
),能夠看到咱們是用consul
的tags
實現的版本控制,能夠設置不一樣的tag
實現不少功能true
最終實現以下:
/** * @author Yuicon */ @Slf4j public class VersionRule extends PredicateBasedRule { private final CompositePredicate predicate; public VersionRule() { super(); this.predicate = createCompositePredicate(new VersionPredicate(), new AvailabilityPredicate(this, null)); } @Override public AbstractServerPredicate getPredicate() { return this.predicate; } private CompositePredicate createCompositePredicate(VersionPredicate versionPredicate, AvailabilityPredicate availabilityPredicate) { return CompositePredicate.withPredicates(versionPredicate, availabilityPredicate) .build(); } private static class VersionPredicate extends AbstractServerPredicate { private static final String VERSION_KEY = "version"; @Override public boolean apply(@NullableDecl PredicateKey predicateKey) { if (predicateKey == null) { return true; } RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String version = request.getHeader(VERSION_KEY); if (version == null) { return true; } ConsulServer consulServer = (ConsulServer) predicateKey.getServer(); if (!consulServer.getMetadata().containsKey(VERSION_KEY)) { return true; } log.info("id is {}, header is {}, metadata is {}, result is {}", consulServer.getMetaInfo().getInstanceId(), version, consulServer.getMetadata().get(VERSION_KEY), consulServer.getMetadata().get(VERSION_KEY).equals(version)); return consulServer.getMetadata().get(VERSION_KEY).equals(version); } } } 複製代碼
本來我是加上@Component
註解後在本地直接測試經過了。但是在更新到生產服務器後卻出現大部分請求都找不到的服務實例的錯誤,搞的我一頭霧水,趕忙回滾到原來的版本。
查詢了不少資料後才找到一篇文章,發現須要一個Config
類來聲明替換原有的負載均衡策略類。代碼以下:
@RibbonClients(defaultConfiguration = RibbonGatewayConfig.class) @Configuration public class RibbonGatewayConfig { @Bean public IRule versionRule() { return new VersionRule(); } } 複製代碼
到此爲止版本控制算是實現成功了。
在實際使用過程當中發現仍是有不少問題。好比前端版本號是全局惟一的,當其中一個服務升級了版本號,就須要將全部服務都升級到該版本號,即便代碼沒有任何更改。比較好的解決方案是前端根據不一樣服務傳遞不一樣的版本號,不過前端反饋實現困難。
還有個妥協的方案,就是利用配置中心來對具體服務是否開啓版本控制進行配置,由於如今的需求只是一小段時間裏須要版本控制,小程序審覈事後就能夠把舊服務實例關了。你們若是有更好的方案歡迎討論。