背景
在进行存储管控系统中的块存储管理模块开发时,块存储所需的数据需要从底层拉取并同步到管控系统的数据库中,最终在页面中展示。系统设计了2种同步数据的方式,一种是自动同步:后台创建一个定时任务,每隔一段时间自动执行;另一种是页面设置了同步按钮,用户可随时点击进行同步。由于同步数据时间较长,所以存在当一个同步任务正在进行的时候,系统同时接收另一个同步请求。但同一时刻系统只能执行一个数据同步任务,否则会导致数据错乱。这就需要用到文件锁技术。
为什么要使用文件锁?
存储管控系统使用django-crontab库实现定时任务,创建一个定时任务的简单实现方式为:
CRONJOBS = [
('10 22 * * *', 'ceph.views.rbd.volume_view.main_rbd_job', '>>/var/log/ecloudmon-web/cstor_rbd_sync_job.log')
]
创建完成后,python会在底层操作系统使用crontab命令自动创建对应的定时任务,同时系统为该定时任务创建一个新的进程,且与python主程序不同。
定时任务执行时本质上是对代码文件进行读写,当python主程序同时文件同一个代码文件时(并发),如果不进行加锁处理,就会引发问题;而我们常用的threading lock主要是针对多线程并发进行处理,此种情况下并不适用。
文件锁可以针对单个文件的读写进行加锁处理,不管是多进程并发还是多线程并发,适用于此种情况。
文件锁在管控系统中的实现方式介绍
-
引入模块fcntl
fcntl模块是python标准库中自带的,主要针对Linux系统下的fcntl()接口。
import fcntl
-
使用fcntl
fcntl.flock(fd, operation)
其中,fd为文件描述符,operation为指定要进行的锁操作,主要有以下取值:
- LOCK_SH: 表示要创建一个共享锁,在任意时间内,一个文件的共享锁可以被多个进程拥有。
- LOCK_EX:表示创建一个排它锁,在任意时间内,一个文件的排它锁只能被一个进程拥有
- LOCK_UN:表示删除该进程创建的锁
- LOCK_NB:如果指定此参数,函数不能获得文件锁就立即返回,否则,函数会等待获得文件锁。LOCK_NB可以同LOCK_SH或LOCK_NB进行按位或(|)运算操作。flock(f,fcntl.LOCK_EX|fcntl.LOCK_NB)
- LOCK_MAND:它主要是用于共享模式强制锁,它可以与LOCK_READ或者LOCK_WRITE联合起来使用,从而表示是否允许并发的读操作或者并发的写操作。
-
存储管控系统中的应用
在存储管控系统中,数据同步任务在同一时间只能执行一次,即文件只能被一个进程访问,所以使用了LOCK_EX,即排他锁。同时组合使用了LOCK_NB,使同步任务不具备执行条件时立即退出。实现的关键代码如下:
try:
with open(BASE_DIR + "/rbd_sync_job.py", 'r') as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
get_rbd_info()
except Exception as e:
log.info('rbd sync job is running for frontend!')
finally:
f.close()