Apollo源码解析——锁定Namespace


一、概述

锁定Namespace,可以设置 ConfigDBServerConfig"namespace.lock.switch" true,开启后
配置的修改和发布是互斥的,就是你修改了配置,但是发布需要另外一个人来发布。

二、页面流程

这里我们在页面上创建一个配置项
在这里插入图片描述
会请求后台的item接口
在这里插入图片描述

另外一个人发布刚刚添加的配置项
在这里插入图片描述
会请求后台的release接口
在这里插入图片描述

2.代码流程

  1. 这里直接到adminservice模块的ItemController ,patrol模块的流程后续会讲。
  1. 这里会发现方法上增加了一个注解 @PreAcquireNamespaceLock,与之对应存在一个切面来切这个注解。
  @PreAcquireNamespaceLock
  @PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
  public ItemDTO create(@PathVariable("appId") String appId,
                        @PathVariable("clusterName") String clusterName,
                        @PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) {
    // 将 ItemDTO 转换成 Item 对象
    Item entity = BeanUtils.transform(Item.class, dto);

    //创建 ConfigChangeContentBuilder 对象
    ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
    //校验对应的 Item 是否已经存在。若是,抛出异常
    Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());
    if (managedEntity != null) {
      throw new BadRequestException("item already exists");
    }
    //保存Item对象
    entity = itemService.save(entity);
    //添加到 ConfigChangeContentBuilder
    builder.createItem(entity);
    //将 item 转换成 ItemDTO对象
    dto = BeanUtils.transform(ItemDTO.class, entity);

    //创建commit对象
    Commit commit = new Commit();
    commit.setAppId(appId);
    commit.setClusterName(clusterName);
    commit.setNamespaceName(namespaceName);
    commit.setChangeSets(builder.build());
    commit.setDataChangeCreatedBy(dto.getDataChangeLastModifiedBy());
    commit.setDataChangeLastModifiedBy(dto.getDataChangeLastModifiedBy());
    commitService.save(commit);

    return dto;
  }
  1. NamespaceAcquireLockAspect

这里直接调用acquireLock尝试锁定

@Aspect
@Component
public class NamespaceAcquireLockAspect {
  private static final Logger logger = LoggerFactory.getLogger(NamespaceAcquireLockAspect.class);

  private final NamespaceLockService namespaceLockService;
  private final NamespaceService namespaceService;
  private final ItemService itemService;
  private final BizConfig bizConfig;

  public NamespaceAcquireLockAspect(
      final NamespaceLockService namespaceLockService,
      final NamespaceService namespaceService,
      final ItemService itemService,
      final BizConfig bizConfig) {
    this.namespaceLockService = namespaceLockService;
    this.namespaceService = namespaceService;
    this.itemService = itemService;
    this.bizConfig = bizConfig;
  }


  //create item
  @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)")
  public void requireLockAdvice(String appId, String clusterName, String namespaceName,
                                ItemDTO item) {
    //尝试锁定
    acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
  }
...............................省略其他方法

3. NamespaceAcquireLockAspect#acquireLock

这里首先获取Namespace对象,然后调用重载方法,传入namespace和当前uid来进行加锁

  void acquireLock(String appId, String clusterName, String namespaceName,
                           String currentUser) {
    // 当关闭锁定 Namespace 开关时,直接返回
    if (bizConfig.isNamespaceLockSwitchOff()) {
      return;
    }

    //获得 Namespace 对象
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);

    //尝试锁定
    acquireLock(namespace, currentUser);
  }

4. NamespaceAcquireLockAspect#acquireLock

下面首先校验下是不是已经被加锁了,如果没有加锁,则调用tryLock进行加锁

  private void acquireLock(Namespace namespace, String currentUser) {
    //当 Namespace 为空时,抛出 BadRequestException 异常
    if (namespace == null) {
      throw new BadRequestException("namespace not exist.");
    }

    long namespaceId = namespace.getId();

    // 获得 NamespaceLock对象
    NamespaceLock namespaceLock = namespaceLockService.findLock(namespaceId);
    // 当 NamespaceLock 不存在时,尝试锁定
    if (namespaceLock == null) {
      try {
        //锁定
        tryLock(namespaceId, currentUser);
        //lock success
      } catch (DataIntegrityViolationException e) {
        //lock fail
        //锁定失败,获得 NamespaceLock 对象
        namespaceLock = namespaceLockService.findLock(namespaceId);
        //校验锁定人是否是当前管理员
        checkLock(namespace, namespaceLock, currentUser);
      } catch (Exception e) {
        logger.error("try lock error", e);
        throw e;
      }
    } else {
      //check lock owner is current use
      // 校验锁定人是否是当前管理员
      checkLock(namespace, namespaceLock, currentUser);
    }
  }

5. trylock

这里发现加锁就是往数据库写入一条数据,并且使用数据库的唯一索引来保证互斥。

   private void tryLock(long namespaceId, String user) {
    NamespaceLock lock = new NamespaceLock();
    lock.setNamespaceId(namespaceId);
    lock.setDataChangeCreatedBy(user);
    lock.setDataChangeLastModifiedBy(user);
    namespaceLockService.tryLock(lock);
  }
  1. adminservice模块下的AppNamespaceController#create

这里就是做了唯一性校验,然后就调用servicecreateAppNamespace方法

  @PostMapping("/apps/{appId}/appnamespaces")
  public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace,
                                @RequestParam(defaultValue = "false") boolean silentCreation) {

    //将 AppNamespaceDTO 转换成 AppNamespace 对象
    AppNamespace entity = BeanUtils.transform(AppNamespace.class, appNamespace);
    // 判断 `name` 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 BadRequestException 异常。
    AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());

    if (managedEntity == null) {
      if (StringUtils.isEmpty(entity.getFormat())){
        entity.setFormat(ConfigFileFormat.Properties.getValue());
      }
      //保存 AppNamespace 对象到数据库
      entity = appNamespaceService.createAppNamespace(entity);
    } else if (silentCreation) {
      appNamespaceService.createNamespaceForAppNamespaceInAllCluster(appNamespace.getAppId(), appNamespace.getName(),
          appNamespace.getDataChangeCreatedBy());

      entity = managedEntity;
    } else {
      throw new BadRequestException("app namespaces already exist.");
    }

    return BeanUtils.transform(AppNamespaceDTO.class, entity);
  }
  1. 当页面上点击发布按钮,会调用到Patrol模块的ReleaseController,然后他会调用adminserviceReleaseController,我们看下adminservice下的ReleaseController#publish

这里因为我们只关注发布的锁定,所以我们之间看怎么校验lock,我们看下releaseService.publish方法

  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 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));
    }
    // 发布 Namespace 的配置
    Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);

    //获得 Cluster 名
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
    String messageCluster;
    if (parentNamespace != null) {// 灰度发布
      messageCluster = parentNamespace.getClusterName();
    } else {
      messageCluster = clusterName; // 使用请求的 ClusterName
    }
    //发送 Release 消息
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
            Topics.APOLLO_RELEASE_TOPIC);
    //将 Release 转换成 ReleaseDTO对象
    return BeanUtils.transform(ReleaseDTO.class, release);
  }

6. ReleaseService#publish方法

这里我们直接看发布配置时如何校验锁定人,其他的逻辑后续会说,看下checkLock方法

  public Release publish(Namespace namespace, String releaseName, String releaseComment,
                         String operator, boolean isEmergencyPublish) {

    //发布配置时,会校验锁定人是否是当前的管理员
    checkLock(namespace, isEmergencyPublish, operator);

    //获得 Namespace 的普通配置 Map
    Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);

    //获得 父 Namespace
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);

    //branch release
    //若有 父 Namespace,则是子 Namespace,进行灰度发布
    if (parentNamespace != null) {
      return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems,
                                    releaseName, releaseComment, operator, isEmergencyPublish);
    }

    //获得子 Namespace 对象
    Namespace childNamespace = namespaceService.findChildNamespace(namespace);

    //获得上一次,并且有效的 Release 对象
    Release previousRelease = null;
    if (childNamespace != null) {
      previousRelease = findLatestActiveRelease(namespace);
    }

    //master release
    //创建操作 Context
    Map<String, Object> operationContext = Maps.newLinkedHashMap();
    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
    //若有子 Namespace 时, 自动将主干合并到子 Namespace,并进行一次子 Namespace 的发布
    if (childNamespace != null) {
      mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems,
                                      releaseName, releaseComment, operator, previousRelease,
                                      release, isEmergencyPublish);
    }

    return release;
  }
  1. checkLock方法

这里逻辑很简单,就是根据namespacedb查询有没有加锁,并且判断加锁的人是不是自己,如果是自己会报错,因为自己不能同时新增配置并发布

  private void checkLock(Namespace namespace, boolean isEmergencyPublish, String operator) {
    if (!isEmergencyPublish) { //非紧急发布
      //获得 NamespaceLock对象
      NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
      //校验锁定人是否是当前管理员,如果是,抛出 BadRequest异常
      if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) {
        throw new BadRequestException("Config can not be published by yourself.");
      }
    }
  }
  1. masterRelease方法

这里我们可以想一下等发布完成肯定会释放锁,所以我们直接跟进去看释放锁的逻辑,这块代码后续讲解,直接看createRelease方法

private Release masterRelease(Namespace namespace, String releaseName, String releaseComment,
                                Map<String, String> configurations, String operator,
                                int releaseOperation, Map<String, Object> operationContext) {
    // 获得最后有效的 Release 对象
    Release lastActiveRelease = findLatestActiveRelease(namespace);
    long previousReleaseId = lastActiveRelease == null ? 0 : lastActiveRelease.getId();
    //创建 Release 对象,并保存
    Release release = createRelease(namespace, releaseName, releaseComment,
            configurations, operator);

    // 创建 ReleaseHistory 对象并保存
    releaseHistoryService.createReleaseHistory(namespace.getAppId(), namespace.getClusterName(),
            namespace.getNamespaceName(), namespace.getClusterName(),
            release.getId(), previousReleaseId, releaseOperation,
            operationContext, operator);

    return release;
  }
  1. createRelease方法

这里发布完成后最终会释放锁,最终会把之前db中插入的那条记录删掉

  private Release createRelease(Namespace namespace, String name, String comment,
                                Map<String, String> configurations, String operator) {
    Release release = new Release();
    release.setReleaseKey(ReleaseKeyGenerator.generateReleaseKey(namespace));
    release.setDataChangeCreatedTime(new Date());
    release.setDataChangeCreatedBy(operator);
    release.setDataChangeLastModifiedBy(operator);
    release.setName(name);
    release.setComment(comment);
    release.setAppId(namespace.getAppId());
    release.setClusterName(namespace.getClusterName());
    release.setNamespaceName(namespace.getNamespaceName());
    release.setConfigurations(GSON.toJson(configurations));
    release = releaseRepository.save(release);
    //释放锁
    namespaceLockService.unlock(namespace.getId());
    auditService.audit(Release.class.getSimpleName(), release.getId(), Audit.OP.INSERT,
                       release.getDataChangeCreatedBy());

    return release;
  }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值