当前位置: 首页 > 编程笔记 >

Springcloud实现服务多版本控制的示例代码

万涵亮
2023-03-14
本文向大家介绍Springcloud实现服务多版本控制的示例代码,包括了Springcloud实现服务多版本控制的示例代码的使用技巧和注意事项,需要的朋友参考一下

需求

小程序新版本上线需要审核,如果有接口新版本返回内容发生了变化,后端直接上线会导致旧版本报错,不上线审核又通不过。

之前是通过写新接口来兼容,但是这样会有很多兼容代码或者冗余代码,开发也不容易能想到这一点,经常直接修改了旧接口,于是版本控制就成了迫切的需求。

思路

所有请求都是走的网关,很自然的就能想到在网关层实现版本控制。首先想到的是在ZuulFilter过滤器中实现,前端所有请求都在请求头中增加一个version的header,然后进行匹配。但是这样只能获取到前端的版本,不能匹配选择后端实例。

查询资料后发现应该在负载均衡的时候实现版本控制。同样是前端所有请求都在请求头中增加一个version的header,后端实例都配置一个版本的tag。

实现

首先需要说明的是我选择的控制中心是consul,网关是zuul。

负载均衡策略被抽象为IRule接口,项目默认情况下使用的IRule的子类ZoneAvoidanceRule extends PredicateBasedRule,我们需要实现一个PredicateBasedRule的子类来替换ZoneAvoidanceRule。

PredicateBasedRule需要实现一个过滤的方法我们就在这个方法里实现版本控制,过滤后就是默认的负载均衡策略了,默认是轮询。

  /**
   * Method that provides an instance of {@link AbstractServerPredicate} to be used by this class.
   * 
   */
  public abstract AbstractServerPredicate getPredicate();

VersionPredicate

我们可以看到PredicateBasedRule的getPredicate()方法需要返回一个AbstractServerPredicate实例,这个实例具体定义了版本控制的业务逻辑。代码如下:

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();
  }
}

到此为止版本控制算是实现成功了。

结尾

在实际使用过程中发现还是有很多问题。比如前端版本号是全局唯一的,当其中一个服务升级了版本号,就需要将所有服务都升级到该版本号,即使代码没有任何更改。比较好的解决方案是前端根据不同服务传递不同的版本号,不过前端反馈实现困难。

还有个妥协的方案,就是利用配置中心来对具体服务是否开启版本控制进行配置,因为现在的需求只是一小段时间里需要版本控制,小程序审核过后就可以把旧服务实例关了。大家如果有更好的方案欢迎讨论。

到此这篇关于Springcloud实现服务多版本控制的示例代码的文章就介绍到这了,更多相关Springcloud多版本控制内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!

 类似资料:
  • 需要修改Spring Boot微服务的现有契约(请求/响应有效负载),这实际上是破坏性的更改(不向后兼容)。而且在一段时间内支持合同的两个版本--直到所有客户都升级到更新的版本--是至关重要的。 为了实现这一点,已经决定使用URL版本控制策略(如/v1/{resource}和/v2/{resource})。 现在,问题是在代码中实现这一点的最佳方式是什么?以下是两个建议的解决方案 > 扩展版本一(

  • 版本2: 还是这样做错了?或者更常见的是创建不同的包来保存不同版本的控制器?还是有其他办法?

  • 我对Kubernetes还很陌生,只是从一个示例项目开始学习。我目前正在运行一个.NET微服务,它需要一个MongoDB作为数据库。微服务被打包到Docker映像中,我创建了一个单独的Helm图表来正确部署我的微服务和所需的MongoDB。 是这样做的吗?还是我错过了什么?所有给我指明正确方向的线索都非常欢迎!

  • 本文向大家介绍SpringBoot实现API接口多版本支持的示例代码,包括了SpringBoot实现API接口多版本支持的示例代码的使用技巧和注意事项,需要的朋友参考一下 一、简介 产品迭代过程中,同一个接口可能同时存在多个版本,不同版本的接口URL、参数相同,可能就是内部逻辑不同。尤其是在同一接口需要同时支持旧版本和新版本的情况下,比如APP发布新版本了,有的用户可能不选择升级,这是后接口的版本

  • 本文向大家介绍SpringCloud之服务注册与发现Spring Cloud Eureka实例代码,包括了SpringCloud之服务注册与发现Spring Cloud Eureka实例代码的使用技巧和注意事项,需要的朋友参考一下 一、Spring Cloud简介 Spring Cloud是一个基千SpringBoot实现的微服务架构开发 工具。它为微服务架构中涉及的 配置管理、服务治理、 断路器

  • 本文向大家介绍java实现memcache服务器的示例代码,包括了java实现memcache服务器的示例代码的使用技巧和注意事项,需要的朋友参考一下 什么是Memcache? Memcache集群环境下缓存解决方案 Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说