个人博客 转载请注明原创出处。前端
小程序新版本上线须要审核,若是有接口新版本返回内容发生了变化,后端直接上线会致使旧版本报错,不上线审核又通不过。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(); } } 复制代码
到此为止版本控制算是实现成功了。
在实际使用过程当中发现仍是有不少问题。好比前端版本号是全局惟一的,当其中一个服务升级了版本号,就须要将全部服务都升级到该版本号,即便代码没有任何更改。比较好的解决方案是前端根据不一样服务传递不一样的版本号,不过前端反馈实现困难。
还有个妥协的方案,就是利用配置中心来对具体服务是否开启版本控制进行配置,由于如今的需求只是一小段时间里须要版本控制,小程序审核事后就能够把旧服务实例关了。你们若是有更好的方案欢迎讨论。