【Ovirt 笔记】锁机制分析
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 Ovirt 3.4.5 版本。
Ovirt Engine 虚拟化平台中提供了一种对资源的线程安全的实现机制,在应用的逻辑上也加了一些对共享资源的限制,在单线程的情况下,是可以检测出数据库中是否存在同名的情况下,但并不能足以保证在大并发情况下,资源的线程安全性。平台是通过 LockManager 接口,提供对 command 执行时对实际进行资源的加解锁,确保并发执行时的绝对安全性。根据应用的特点,提供了两种类型的锁( 独占锁 和 共享锁 ),封装在了 EngineLock 对象中。
private Map<String, Pair<String, String>> exclusiveLocks;
private Map<String, Pair<String, String>> sharedLocks;
public EngineLock() {
}
public EngineLock(Map<String, Pair<String, String>> exclusiveLocks, Map<String, Pair<String, String>> sharedLocks) {
this.exclusiveLocks = exclusiveLocks;
this.sharedLocks = sharedLocks;
}
独占锁 和 共享锁 都可以同时锁定多个资源,采用了 Map<String, Pair<String, String>> 的数据结构,其中外层 Map 的 Key,存放了 加锁资源的 ID,例如虚拟机 ID。Pair Map Key 存放了锁定资源组的类型,如VM,Pair Map Value 中存放了遭遇加锁时,给出的错误提示信息。目前包含10多种可锁定的资源组类型。
资源组类型:
序号 | 可锁定资源组类型名称 | 描述 |
---|---|---|
1 | POOL | 池 |
2 | VDS | 主机 |
3 | VDS_INIT | 主机初始化 |
4 | VDS_FENCE | 电源管理 |
5 | VM | 虚拟机 |
6 | TEMPLATE | 模版 |
7 | DISK | 磁盘 |
8 | VM_DISK_BOOT | 引导盘 |
9 | VM_NAME | 虚拟机名称 |
10 | NETWORK | 网络 |
11 | STORAGE | 存储 |
12 | STORAGE_CONNECTION | 存储连接 |
13 | REGISTER_VDS | 注册主机 |
14 | VM_SNAPSHOTS | 虚拟机快照 |
15 | GLUSTER | 卷 |
16 | USER_VM_POOL | 池分配虚拟机给用户 |
17 | REMOTE_TEMPLATE | 导出模版 |
18 | REMOTE_VM | 导出虚拟机 |
commandBase 中抽象了独立锁 getExclusiveLocks 和 共享锁 getSharedLocks,在业务逻辑 Command 中进行了不同的实现。
例如: AddVmCommand:
共享锁
@Override
protected Map<String, Pair<String, String>> getSharedLocks() {
Map<String, Pair<String, String>> locks = new HashMap<String, Pair<String, String>>();
locks.put(getVmTemplateId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.TEMPLATE, getTemplateSharedLockMessage()));
for (DiskImage image: getImagesToCheckDestinationStorageDomains()) {
locks.put(image.getId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.DISK, getDiskSharedLockMessage()));
}
return locks;
}
提供的共享资源是以模板和磁盘为资源做为锁。
独占锁
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
if (!StringUtils.isBlank(getParameters().getVm().getName())) {
return Collections.singletonMap(getParameters().getVm().getName(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM_NAME, VdcBllMessages.ACTION_TYPE_FAILED_OBJECT_LOCKED));
}
return null;
}
虚拟机名称在平台中必须唯一,因此为虚拟机名称上锁,整个平台运行过程中,只有一个线程能够获得对虚拟机名字为 xx 的资源,其它需排队等待。
- 具体的业务提供了锁的资源,封装在了业务逻辑 Command 继承的基类 commandBase 里面的变量 commandLock 里面。执行业务逻辑 Command 的 canDoAction(Command 执行前验证) 后,调用 internalCanDoAction 方法中的 acquireLock,获取锁。
protected boolean acquireLock() {
LockIdNameAttribute annotation = getClass().getAnnotation(LockIdNameAttribute.class);
boolean returnValue = true;
if (annotation != null) {
releaseLocksAtEndOfExecute = annotation.isReleaseAtEndOfExecute();
if (!annotation.isWait()) {
returnValue = acquireLockInternal();
} else {
acquireLockAndWait();
}
}
return returnValue;
}
具体的业务 Command 在实现上,定义了 LockIdNameAttribute 注解,如果定义了注解,则意味着需要对资源进行加锁。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LockIdNameAttribute {
boolean isWait() default false;
boolean isReleaseAtEndOfExecute() default true;
}
注解 isReleaseAtEndOfExecute 属性,执行完成后,是否立刻释放锁,为 true 立刻释放锁,默认为 true。
private void freeLockExecute() {
if (releaseLocksAtEndOfExecute || !getSucceeded() ||
(!hasTasks() && !(this instanceof IVdsAsyncCommand))) {
freeLock();
}
}
为 false 时,不释放锁,可以实现异步场景。
public final boolean acquireLockAsyncTask() {
LockIdNameAttribute annotation = getClass().getAnnotation(LockIdNameAttribute.class);
boolean returnValue = true;
if (annotation != null) {
releaseLocksAtEndOfExecute = annotation.isReleaseAtEndOfExecute();
if (!releaseLocksAtEndOfExecute) {
returnValue = acquireLockInternal();
}
}
return returnValue;
}
注解 isWait 属性,获取锁失败后,是否等待,为 true,进行等待,默认为 false。不等待的情况,执行 acquireLockInternal 方法。
protected boolean acquireLockInternal() {
// if commandLock is null then we acquire new lock, otherwise probably we got lock from caller command.
if (commandLock == null) {
EngineLock lock = buildLock();
if (lock != null) {
Pair<Boolean, Set<String>> lockAcquireResult = getLockManager().acquireLock(lock);
if (lockAcquireResult.getFirst()) {
log.infoFormat("Lock Acquired to object {0}", lock);
commandLock = lock;
} else {
log.infoFormat("Failed to Acquire Lock to object {0}", lock);
getReturnValue().getCanDoActionMessages()
.addAll(extractVariableDeclarations(lockAcquireResult.getSecond()));
return false;
}
}
}
return true;
}
- commandLock 不为 null,通过 buildLock 方法,构建锁。
- LockManager 接口的实现是 InMemoryLockManager 单例对象,是 jboss 管理的对象,在初始化时通过注册到容器中, 通过该对象的方法 acquireLock 实现上锁。
- InMemoryLockManager 中定义的锁,是通过一个全局的可重入锁 加锁,接着就是核心的具体资源锁的实现。
private final Lock globalLock = new ReentrantLock();
@Override
public Pair<Boolean, Set<String>> acquireLock(EngineLock lock) {
log.debugFormat("Before acquiring lock {0}", lock);
globalLock.lock();
try {
return acquireLockInternal(lock);
} finally {
globalLock.unlock();
}
}
@Override
public void acquireLockWait(EngineLock lock) {
log.debugFormat("Before acquiring and wait lock {0}", lock);
validateLockForAcquireAndWait(lock);
globalLock.lock();
try {
while (!acquireLockInternal(lock).getFirst()) {
log.infoFormat("Failed to acquire lock and wait lock {0}", lock);
releasedLock.await();
}
} catch (InterruptedException e) {
} finally {
globalLock.unlock();
}
}
private Pair<Boolean, Set<String>> acquireLockInternal(EngineLock lock) {
boolean checkOnly = true;
for (int i = 0; i < 2; i++) {
if (lock.getSharedLocks() != null) {
for (Entry<String, Pair<String, String>> entry : lock.getSharedLocks().entrySet()) {
Pair<Boolean, Set<String>> result =
insertSharedLock(buildHashMapKey(entry), entry.getValue().getSecond(), checkOnly);
if (!result.getFirst()) {
log.debugFormat("Failed to acquire lock. Shared lock is taken for key :{0} , value: {1}",
entry.getKey(),
entry.getValue().getFirst());
return result;
}
}
}
if (lock.getExclusiveLocks() != null) {
for (Entry<String, Pair<String, String>> entry : lock.getExclusiveLocks().entrySet()) {
Pair<Boolean, Set<String>> result =
insertExclusiveLock(buildHashMapKey(entry), entry.getValue().getSecond(), checkOnly);
if (!result.getFirst()) {
log.debugFormat("Failed to acquire lock. Exclusive lock is taken for key: {0} , value: {1}",
entry.getKey(),
entry.getValue().getFirst());
return result;
}
}
}
checkOnly = false;
}
log.debugFormat("Successed acquiring lock {0} succeeded ", lock);
return LOCK_INSERT_SUCCESS_RESULT;
}
注意:acquireLockInternal 方法,包含了两个步骤:依次检测该锁的所有资源是否能够获取,如果不能,则直接返回失败;再次执行一次,组装资源,返回结果。之所以进行两步操作,而不是一次性获取并且组装,是因为存在需要锁定多个资源的场景,例如增加虚拟机,可以添加多个磁盘,其中一个磁盘资源无法加锁,则整个操作都需要失败,如果边获取边组装,失败后还需要回滚,业务逻辑会变得更为复杂。
等待的情况,执行 acquireLockAndWait 方法。
private void acquireLockAndWait() {
// if commandLock is null then we acquire new lock, otherwise probably we got lock from caller command.
if (commandLock == null) {
Map<String, Pair<String, String>> exclusiveLocks = getExclusiveLocks();
if (exclusiveLocks != null) {
EngineLock lock = new EngineLock(exclusiveLocks, null);
getLockManager().acquireLockWait(lock);
commandLock = lock;
}
}
}
- commandLock 不为 null,通过 buildLock 方法,构建锁。
- LockManager 接口的实现是 InMemoryLockManager 单例对象, 通过该对象的方法 acquireLockWait 实现上锁。
- 检测需要上锁的资源。如果失败则 wait,等待被唤醒,再次获取资源,直到成功获得资源为止。获取的过程与 acquireLockInternal。需要注意的是,wait 的机制使用了可重入锁的条件队列 。
private final Condition releasedLock = globalLock.newCondition();