Apollo 9 — adminService 主/灰度版本发布

目录

  1. Controller 层
  2. Service 层 publish 方法
  3. 发送 ReleaseMessage 消息
  4. 总结

1. Controller 层

主版本发布即点击主版本发布按钮:

具体接口位置:com.ctrip.framework.apollo.adminservice.controller 包下 ReleaseController#publish
实际上灰度版本发布也是调用这个接口的。
代码:

  /**
   * 主版本发布
   */
  @Transactional
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
  public ReleaseDTO publish(@PathVariable("appId") String appId,
                            @PathVariable("clusterName") String clusterName,
                            @PathVariable("namespaceName") String namespaceName,
                            @RequestParam("name") String releaseName,
                            @RequestParam(name = "comment", required = false) String releaseComment,
                            @RequestParam("operator") String operator,
                            @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
    // 校验存在与否
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
    if (namespace == null) {
      throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
                                                clusterName, namespaceName));
    }
    // 发布
    Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);

    //send release message 发送消息到 ReleaseMessage
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
    String messageCluster;
    if (parentNamespace != null) {
      messageCluster = parentNamespace.getClusterName();
    } else {
      messageCluster = clusterName;
    }
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
                              Topics.APOLLO_RELEASE_TOPIC);
    return BeanUtils.transfrom(ReleaseDTO.class, release);
  }

该层主要做了 2 件事情,1是调用 Service 层的 public 方法做真正的发布操作,2是发送“发布消息”到数据库——等待 ConfigService 消费。

所以,我们主要关注 Service 层的 publish 方法。

2. Service 层 publish 方法

该方法有些繁琐,主要流程图如下:

publish 流程图

可以通过比对流程图和代码来看。

代码如下:

  @Transactional
  public Release publish(Namespace namespace, String releaseName, String releaseComment,
                         String operator, boolean isEmergencyPublish) {
    // 检查锁
    checkLock(namespace, isEmergencyPublish, operator);
    // 获取 item
    Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);

    // 根据当前 namespace 找到父 namespace, 也就是灰度的主版本.
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);

    //branch release // 父 namespace 不是 null, 说明当前就是灰度版本.
    if (parentNamespace != null) {
      // 发布灰度版本.
      return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems,
                                    releaseName, releaseComment, operator, isEmergencyPublish);
    }

    // 非灰度版本, 找到子版本
    Namespace childNamespace = namespaceService.findChildNamespace(namespace);

    Release previousRelease = null;
    if (childNamespace != null) {
      // 找到上一个版本
      previousRelease = findLatestActiveRelease(namespace);
    }

    //master release
    Map<String, Object> operationContext = Maps.newHashMap();
    // 记录是否紧急发布
    operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
    // 主版本发布
    Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
                                    operator, ReleaseOperation.NORMAL_RELEASE, operationContext);


    //merge to branch and auto release
    // 将主版本合并到灰度版本. 并自动发布
    if (childNamespace != null) {
      mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems,
                                      releaseName, releaseComment, operator, previousRelease,
                                      release, isEmergencyPublish);
    }
    return release;
  }
  1. 检查锁:如果不是紧急发布,就需要检查锁,如果这个 namespace 的最后修改者就是当前用户,那么就抛出异常。禁止其修改。

  2. 根据 namespace 获取所有的 item,也就是配置。

  3. 判断当前的 namespace 是否有父 namespace,如果有,说明当前 namespace 是灰度 namespace,则进行灰度发布(主版本发布和灰度发布逻辑不同)。

这里说下父子 namespace 在 apollo 的设计:

主体E-R Diagram 图片来自 apollo wiki

从图中可以看出,namespace 和 cluster 是多对一的关系,而 cluster 有个字段:ParentClusterId,也就是说,cluster 是有层级的。每当创建一个灰度配置,实际上,就是创建了一个新的 cluster,这个新的 cluster 的名字就是 时间戳-字符串,大概是这样的:20180705150428-1dc5208dc9e8146b. 然后再在这个新 cluster 下面创建新的 namespace,那么,namespace 无形中也有了层级(父子)关系。

  1. 如果没有父 namespace,说明是主版本发布,那么就需要处理他的子 (灰度)版本,同时,为了后面比对灰度版本和上一个版本的区别(如果灰度修改了上一个版本的数据,就需要记录,否则,灰度数据和主版本将无法对应),还要记录上一个版本的 release 信息。

  2. 发布主版本。并保存发布历史。

  3. 如果存在灰度版本,就更新灰度版本的配置,并发布灰度版本。

关于灰度版本,这里多提一句,每次发布都是一个 release,release 对象有个 configuration,包含了此次发布的全量配置,因此,灰度发布的 configuration 中,包含了每次对应的主版本的配置,如果主版本发生了变化,那么灰度版本肯定也是要变更的。所以需要重新发布灰度版本。

其中关键的方法就是 mergeConfiguration,该方法表明了灰度发布的主要逻辑:

  private Map<String, String> mergeConfiguration(Map<String, String> baseConfigurations,
                                                 Map<String, String> coverConfigurations) {
    Map<String, String> result = new HashMap<>();
    //copy base configuration
    for (Map.Entry<String, String> entry : baseConfigurations.entrySet()) {
      result.put(entry.getKey(), entry.getValue());
    }

    //update and publish
    for (Map.Entry<String, String> entry : coverConfigurations.entrySet()) {
      result.put(entry.getKey(), entry.getValue());
    }

    return result;
  }

方法很简单:两个参数,主版本配置,灰度版本配置。首先将主版本配置保存到 Map 中,然后将灰度版本配置也 put 到 Map 中,利用 Map 唯一 Key 的特性,保证灰度版本覆盖主版本。

所以这个方法的 put 顺序决定了灰度版本覆盖主版本。

publish 方法更多的细节不再赘述,有疑惑的地方可以交流。

3. 发送 ReleaseMessage 消息

这个发送消息的操作本来应该是 MQ,apollo 为了减少依赖,直接使用的 mysql,但已经留好了MQ 的设计。关于 ReleaseMessage 的设计,我这里引用一下 apollo 的文档:

Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。
从概念上来看,这是一个典型的消息使用场景,Admin Service作为producer发出消息,各个Config Service作为consumer消费消息。通过一个消息组件(Message Queue)就能很好的实现Admin Service和Config Service的解耦。
在实现上,考虑到Apollo的实际使用场景,以及为了尽可能减少外部依赖,我们没有采用外部的消息中间件,而是通过数据库实现了一个简单的消息队列。
实现方式如下:

  1. Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的AppId+Cluster+Namespace,参见DatabaseMessageSender
  2. Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录,参见ReleaseMessageScanner
  3. Config Service如果发现有新的消息记录,那么就会通知到所有的消息监听器(ReleaseMessageListener),如NotificationControllerV2,消息监听器的注册过程参见ConfigServiceAutoConfiguration
  4. NotificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端

示意图如下:

apollo 定义了 MessageSender 接口,定义了一个 sendMessage 方法,这个方法目前只有基于 Mysql 的实现,即 DatabaseMessageSender 实现类。

该类会将数据直接保存到数据库。然后清理掉比刚刚存的消息旧的消息—— 防止消息表不断增大。

4. 总结

发布分为主版本发布,灰度版本发布,全量发布,这次说了前两个,全量发布下次再说。

而主/灰发布的一个比较繁琐的地方就是两个版本的合并,灰度版本发布要合并主版本。主版本发布要更新灰度版本

同时,灰度的设计也有点绕,中间隔了一层 cluster。

在发布成功之后,需要发送消息到数据库,让 ConfigService 能够感知到此次发布,并通知客户端。关于如何通知客户端,下次再说。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 目录 UI 界面 Portal 服务 admin 服务 总结 1. UI 界面 2. Portal 服务 当我们点...
    莫那一鲁道阅读 672评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 大纲 看本文之前,建议看看 apollo 的官方文档,特别是数据库设计文档。 主流程分析 2.1 聊聊细节 2.2...
    莫那一鲁道阅读 1,304评论 8 2
  • 前言:在我们日常开发工作当中,我们经常会模仿一些经典的APP来开发,而我们今天所需要的就是延续《APP的基本结构与...
    Heybeauty阅读 3,290评论 0 5
  • 拜日式的好处: 放松身心 提升柔韧性 燃烧脂肪 练习专注力 改善血液循环 增强内脏功能 锻炼肌肉 改善体态 缓解失...
    映卿阅读 376评论 0 0