【Apollo】原理详解


在这里插入图片描述


前言

公司使用apollo作为架构的配置管理中心。为什么公司选择携程的apollo作为配置中心?我的考虑有三点:
第一点,很好的能够管理不同的开发,测试,生产环境。
第二点,可以做代码降级(方便diff和切换开关做回滚)。
第三点,灰度发布。
本文章主要总结apollo原理,如果有人想了解这三点用法也可以私信于我。


一、apollo结构原理

在这里插入图片描述

apollo由哪几部分组成?

apollo一共由四个部分client,config,admin,protal组成。其中config server是提供读取和推送。客户端Client来读配置连接config当我们从portal(管理界面)来发布一个配置的时候,会通过admin service配置写到数据库configDB(mysql的表)中,当客户端(client)读配置通过config service来读。

Eureka扮演这什么角色?

Eureka(和nacos一样服务注册中心)里面有一个meta server(mata server也算是eureka的一个注册中心中的client,只是这个client获取metaService,Config Service和Admin Service的服务信息)。
Meta Server从Eureka获取metaService,Config Service和Admin Service的服务信息,相当于是一个Eureka Client。
增设一个Meta Server的角色主要是为了封装服务发现的细节,对Portal和Client而言,永远通过一个Http接口获取Admin Service和Config Service的服务信息,而不需要关心背后实际的服务注册和发现组件。
补充:Meta Server只是一个逻辑角色,在部署时和Config Service是在一个JVM进程中的,所以IP、端口和Config Service一致。由于和Config Service部署在一个JVM中,所以相应的metaService也是都是多实例、无状态部署,保证了服务的高可用性。

工作流程

config 和admin都会连接数据库并注册到eureka当中,当client通过负载均衡然后通过meta server拿到eureka的服务注册列表从而找到config具体地址。


二、apollo应用管理

在这里插入图片描述

apollo应用管理层级是怎样的?

Apollo应用管理是以应用为单位的,应用就是工程。应用由于环境不同,配置也不同,有不同的环境开发环境,测试环境,生产环境。环境就有集群的区别。项目上线了,北京和上海都有自己的集群,配置文件一定不同,北京机房的地址和上海机房的地址首先就不一致。所以配置文件配置信息隶属于应用的(工程)。

命名空间的具体作用是什么?

我们有这么多配置项(key :value)这些怎么去管理呢?通过namespace去管理,可以简单理解为namespace就是一个配置文件。
当好几个项目都需要使用的多个配置项好多个key value大家都是一样的,这个时候就可以建立一个公共命名空间。
当在具体集群里面的配置项的命名空间就可以继承我们公共的命名空间,就可以直接用公共命名空间。还可以个性化定义公共命名空间。就类似于公共命名空间有数据库地址,北京和上海的数据库地址不同,但是可以拉到自己的明明空间中在进行修改。
一般一个项目会有好多配置信息 properties yml文件 类比apollo的每一个应用每一个环境都可能有许多个namespace 。


三、apollo配置发布原理(源码解读)

在这里插入图片描述
用户要在界面protal发布配置,此时protal会去请求admin service去向数据库去保存刚才能改的信息,在写完数据库之后,admin回异步通知config service,当config得到配置信息的时候,他就会通知客户端Client.
而发送异步release message完全可以采用消息队列的消息机制,但是apollo不想和第三方软件耦合。他自己实现了消息队列。

分析admin代码

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
在这里插入图片描述

我们可以在Admin Service中看到

@Transactional
  @PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases")
  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 NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
    }
    Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);

    //send release message
    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.transform(ReleaseDTO.class, release);
  }

messageSender.sendMessage发送的消息就对应数据库,而这个api是引用apollo-biz。

@Override
  @Transactional
  public void sendMessage(String message, String channel) {
    logger.info("Sending message {} to channel {}", message, channel);
    if (!Objects.equals(channel, Topics.APOLLO_RELEASE_TOPIC)) {
      logger.warn("Channel {} not supported by DatabaseMessageSender!", channel);
      return;
    }

    Tracer.logEvent("Apollo.AdminService.ReleaseMessage", message);
    Transaction transaction = Tracer.newTransaction("Apollo.AdminService", "sendMessage");
    try {
      ReleaseMessage newMessage = releaseMessageRepository.save(new ReleaseMessage(message));
      if(!toClean.offer(newMessage.getId())){
        logger.warn("Queue is full, Failed to add message {} to clean queue", newMessage.getId());
      }
      transaction.setStatus(Transaction.SUCCESS);
    } catch (Throwable ex) {
      logger.error("Sending message to database failed", ex);
      transaction.setStatus(ex);
      throw ex;
    } finally {
      transaction.complete();
    }
  }

2.Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录。首先在Config Service新起一个定时任务,这个定时任务会调用Apollo-biz中的中的 scanAndSendMessages() 方法。scanAndSendMessages() 方法首先获取一个数据库连接(JPA),并使用该连接创建一个事务。在事务中,它会从 ReleaseMessage 表中查询出最新的未发送的消息记录。

/**
   * scan messages and send
   *
   * @return whether there are more messages
   */
  private boolean scanAndSendMessages() {
    //current batch is 500
    List<ReleaseMessage> releaseMessages =
        releaseMessageRepository.findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned);
    if (CollectionUtils.isEmpty(releaseMessages)) {
      return false;
    }
    fireMessageScanned(releaseMessages);
    int messageScanned = releaseMessages.size();
    long newMaxIdScanned = releaseMessages.get(messageScanned - 1).getId();
    // check id gaps, possible reasons are release message not committed yet or already rolled back
    if (newMaxIdScanned - maxIdScanned > messageScanned) {
      recordMissingReleaseMessageIds(releaseMessages, maxIdScanned);
    }
    maxIdScanned = newMaxIdScanned;
    return messageScanned == 500;
  }
  /**
   * Notify listeners with messages loaded
   * @param messages
   */
  private void fireMessageScanned(Iterable<ReleaseMessage> messages) {
    for (ReleaseMessage message : messages) {
      for (ReleaseMessageListener listener : listeners) {
        try {
          listener.handleMessage(message, Topics.APOLLO_RELEASE_TOPIC);
        } catch (Throwable ex) {
          Tracer.logError(ex);
          logger.error("Failed to invoke message listener {}", listener.getClass(), ex);
        }
      }
    }
  }

3.Config Service如果发现有新的消息记录,那么就会通知到所有的消息监听器

@Override
  public void handleMessage(ReleaseMessage message, String channel) {
    logger.info("message received - channel: {}, message: {}", channel, message);

    String content = message.getMessage();
    Tracer.logEvent("Apollo.LongPoll.Messages", content);
    if (!Topics.APOLLO_RELEASE_TOPIC.equals(channel) || Strings.isNullOrEmpty(content)) {
      return;
    }

    String changedNamespace = retrieveNamespaceFromReleaseMessage.apply(content);

    if (Strings.isNullOrEmpty(changedNamespace)) {
      logger.error("message format invalid - {}", content);
      return;
    }
    ·
    ·
    ·
    ·

4.NotificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端


   

总结

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值