Ovirt程序员

【Ovirt 笔记】锁机制分析

2017-04-24  本文已影响58人  58bc06151329

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

分析整理的版本为 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 的资源,其它需排队等待。

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;
}
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;
        }
    }
}
private final Condition releasedLock = globalLock.newCondition();
上一篇 下一篇

猜你喜欢

热点阅读