一、概述
锁定
Namespace
,可以设置ConfigDB
的ServerConfig
的"namespace.lock.switch"
为true
,开启后
配置的修改和发布是互斥的,就是你修改了配置,但是发布需要另外一个人来发布。
二、页面流程
这里我们在页面上创建一个配置项
会请求后台的item接口
另外一个人发布刚刚添加的配置项
会请求后台的release接口
2.代码流程
- 这里直接到
adminservice
模块的ItemController
,patrol
模块的流程后续会讲。
- 这里会发现方法上增加了一个注解
@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;
}
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);
}
adminservice
模块下的AppNamespaceController#create
这里就是做了唯一性校验,然后就调用
service
的createAppNamespace
方法
@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);
}
- 当页面上点击发布按钮,会调用到
Patrol
模块的ReleaseController
,然后他会调用adminservice
的ReleaseController
,我们看下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;
}
checkLock
方法
这里逻辑很简单,就是根据
namespace
去db
查询有没有加锁,并且判断加锁的人是不是自己,如果是自己会报错,因为自己不能同时新增配置并发布
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.");
}
}
}
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;
}
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;
}