openstack 锁的使用及其原理

来源:互联网 发布:会计事务所做帐软件 编辑:程序博客网 时间:2024/06/03 23:05

1 锁使用场景

compute 服务使用协程锁

@utils.synchronized(instance['uuid'])def _sync_refresh():    xxxxxxx

下载镜像,使用线程锁 external=True

@utils.synchronized(filename, external=True, lock_path=self.lock_path)def call_if_not_exists(target, *args, **kwargs):    if not os.path.exists(target):        fetch_func(target=target, *args, **kwargs)

2 锁的类型

•   协程锁: semaphore.Semaphore() 实现•   进程锁: Nova 在 semaphore.Semaphore() 基础之上自身实现的跨进程的文件锁

external关键字参数标示加锁类型。

3 锁原理

3.1 进程锁

python 中给文件加锁——fcntl模块

3.1.1 简单示例:

import fcntl

锁的时机:

打开一个文件f = open('./test') ##当前目录下test文件要先存在,如果不存在会报错。

加锁:

fcntl.flock(f,fcntl.LOCK_EX)

这样就对文件test加锁了,如果有其他进程对test文件加锁,则不能成功,会被阻塞,但不会退出程序。
解锁:

fcntl.flock(f,fcntl.LOCK_UN)

3.1.2 fcntl模块提供方法

参数 名称 说明 fcntl.LOCK_UN 解锁 解除文件锁 fcntl.LOCK_EX 排他锁 除加锁进程外其他进程没有对已加锁文件读写访问权限。 fcntl.LOCK_SH 共享锁 所有进程没有写访问权限,即使是加锁进程也没有。所有进程有读访问权限。 fcntl.LOCK_NB 非阻塞锁 函数不能获得文件锁就立即返回,否则,函数会等待获得文件锁。

LOCK_NB可以同LOCK_SH或LOCK_NB进行按位或(|)运算操作。 fcnt.flock(f,fcntl.LOCK_EX|fcntl.LOCK_NB),我们在 nova 中的就是这种加锁方式。如果一个方法已经被加锁,另一个线程获得这个锁时,抛出异常返回,下面是一个循环等待加锁的例子:

while True:    try:        fcntl.flock(x, fcntl.LOCK_EX | fcntl.LOCK_NB)        break    except IOError as e:        # raise on unrelated IOErrors        if e.errno != errno.EAGAIN:            raise        else:            time.sleep(0.1)

try 尝试加锁,如果检测到有锁,抛出异常,except 捕获后,进入下一个循环尝试获得锁。
文件所高级使用方式:with语句对 线程锁进行的包装,加锁方式如下:

with lock:    retval = f(*args, **kwargs)

lock中含有两个方法:

enter 加锁,
exit进行解锁

def __enter__(self):    self.lockfile = open(self.fname, 'w')    while True:        try:            self.trylock()            return self        except IOError, e:            if e.errno in (errno.EACCES, errno.EAGAIN):                time.sleep(0.01)            else:                raise    def __exit__(self, exc_type, exc_val, exc_tb):        try:            self.unlock()            self.lockfile.close()        except IOError:            LOG.exception(_("Could not release the acquired lock `%s`")                         % self.fname)

3.2 协程锁

使用 协程提供的 Semaphore 类,与其它Semaphore 锁类似,Semaphore锁可以进行流控制,但是nova这里只是用了信号量为1 的锁。信号量为1 表示同时只有一个进程可以获得锁,
假设有两个协程A B,其抢占锁过程如下:

  • A 获得锁,Semaphore 对象中变量 counter(初始值为1)减一为0;
  • B 尝试获得锁,counter 为零,把B加入到 waiters队列中;
  • A 释放锁,counter +1 , counter为1,B 的acquire 方法立即获得锁;
  • A 释放所,同时把B 从waiters 队列中取出,立即唤醒;
  • B 执行自己方法;

加锁的整个过程,B 获得锁时,使用 hubs.get_hub().switch() 方法,把执行机会让给其他线程。
当B 从waiters 队列中取出时,B 可能处于睡眠状态,使用 switch()方法,把B唤醒并执行。

hubs.get_hub().switch() 执行的是协程的切换,b 的switch() 方法唤醒本身。
参考:http://eventlet.net/doc/modules/semaphore.html

以上,是协程锁的原理。
其加锁方式,和线程锁类似,使用with 进行封装。

4 nova使用

openstack nova 使用with语句,对上锁,解锁进行封装,两种锁,嵌套使用,简化如下:

sem = _semaphores.get(name, semaphore.Semaphore())with sem:    if external and not FLAGS.disable_process_locking:        lock = InterProcessLock(lock_file_path)        with lock:            funtion()

以上:
1 尝试使用eventlet 提供的 semaphore 对像,实现协程之间的加锁。
2 在协程锁的基础上,如果传入参数中含有external 这个关键字,此时标识:线程之间不能同时访问锁

nova 在使用with封装的基础上,又使用装饰器,对使用方式进行进一步的转化,最终我们使用@utils.synchronized()的方式即可对方法进行加锁。

5 锁使用

@utils.synchronized() 方式使用锁,锁住的是一个方法。当我们向在一个方法中对一个逻辑进行加锁,我们可以可以把这个逻辑提取成一个方法,然后把这个方法使用@utils.synchronized() 进行装饰。

def run_instance(self):    @utils.synchronized(instance['uuid'])    def do_run_instance():        xxxx    do_run_instance()
0 0