python基于mysql实现的简单队列以及跨进程锁实例详解


Posted in Python onJuly 07, 2014

通常在我们进行多进程应用开发的过程中,不可避免的会遇到多个进程访问同一个资源(临界资源)的状况,这时候必须通过加一个全局性的锁,来实现资源的同步访问(即:同一时间里只能有一个进程访问资源)。

举个例子如下:

假设我们用mysql来实现一个任务队列,实现的过程如下:

1. 在Mysql中创建Job表,用于储存队列任务,如下:

create table jobs(
  id auto_increment not null primary key,
  message text not null,
  job_status not null default 0
);

message 用来存储任务信息,job_status用来标识任务状态,假设只有两种状态,0:在队列中, 1:已出队列 
 
2. 有一个生产者进程,往job表中放新的数据,进行排队:

insert into jobs(message) values('msg1');

3.假设有多个消费者进程,从job表中取排队信息,要做的操作如下:

select * from jobs where job_status=0 order by id asc limit 1;
update jobs set job_status=1 where id = ?; -- id为刚刚取得的记录id

4. 如果没有跨进程的锁,两个消费者进程有可能同时取到重复的消息,导致一个消息被消费多次。这种情况是我们不希望看到的,于是,我们需要实现一个跨进程的锁。

=========================分割线=======================================

说到跨进程的锁实现,我们主要有几种实现方式:

(1)信号量
(2)文件锁fcntl
(3)socket(端口号绑定)
(4)signal
这几种方式各有利弊,总体来说前2种方式可能多一点,这里我就不详细说了,大家可以去查阅资料。
 
查资料的时候发现mysql中有锁的实现,适用于对于性能要求不是很高的应用场景,大并发的分布式访问可能会有瓶颈.
 
对此用python实现了一个demo,如下:
 
文件名:glock.py

#!/usr/bin/env python2.7 
# 
# -*- coding:utf-8 -*- 
# 
#  Desc  : 
# 
import logging, time 
import MySQLdb 
class Glock: 
  def __init__(self, db): 
    self.db = db 
  def _execute(self, sql): 
    cursor = self.db.cursor() 
    try: 
      ret = None 
      cursor.execute(sql) 
      if cursor.rowcount != 1: 
        logging.error("Multiple rows returned in mysql lock function.") 
        ret = None 
      else: 
        ret = cursor.fetchone() 
      cursor.close() 
      return ret 
    except Exception, ex: 
      logging.error("Execute sql \"%s\" failed! Exception: %s", sql, str(ex)) 
      cursor.close() 
      return None 
  def lock(self, lockstr, timeout): 
    sql = "SELECT GET_LOCK('%s', %s)" % (lockstr, timeout) 
    ret = self._execute(sql) 
 
    if ret[0] == 0: 
      logging.debug("Another client has previously locked '%s'.", lockstr) 
      return False 
    elif ret[0] == 1: 
      logging.debug("The lock '%s' was obtained successfully.", lockstr) 
      return True 
    else: 
      logging.error("Error occurred!") 
      return None 
  def unlock(self, lockstr): 
    sql = "SELECT RELEASE_LOCK('%s')" % (lockstr) 
    ret = self._execute(sql) 
    if ret[0] == 0: 
      logging.debug("The lock '%s' the lock is not released(the lock was not established by this thread).", lockstr) 
      return False 
    elif ret[0] == 1: 
      logging.debug("The lock '%s' the lock was released.", lockstr) 
      return True 
    else: 
      logging.error("The lock '%s' did not exist.", lockstr) 
      return None 
#Init logging 
def init_logging(): 
  sh = logging.StreamHandler() 
  logger = logging.getLogger() 
  logger.setLevel(logging.DEBUG) 
  formatter = logging.Formatter('%(asctime)s -%(module)s:%(filename)s-L%(lineno)d-%(levelname)s: %(message)s') 
  sh.setFormatter(formatter) 
  logger.addHandler(sh) 
  logging.info("Current log level is : %s",logging.getLevelName(logger.getEffectiveLevel())) 
def main(): 
  init_logging() 
  db = MySQLdb.connect(host='localhost', user='root', passwd='') 
  lock_name = 'queue' 
 
  l = Glock(db) 
 
  ret = l.lock(lock_name, 10) 
  if ret != True: 
    logging.error("Can't get lock! exit!") 
    quit() 
  time.sleep(10) 
  logging.info("You can do some synchronization work across processes!") 
  ##TODO 
  ## you can do something in here ## 
  l.unlock(lock_name) 
if __name__ == "__main__": 
  main()

在main函数里:

l.lock(lock_name, 10) 中,10是表示timeout的时间是10秒,如果10秒还获取不了锁,就返回,执行后面的操作。
 
在这个demo中,在标记TODO的地方,可以将消费者从job表中取消息的逻辑放在这里。即分割线以上的.

2.假设有多个消费者进程,从job表中取排队信息,要做的操作如下:

select * from jobs where job_status=0 order by id asc limit 1;
update jobs set job_status=1 where id = ?; -- id为刚刚取得的记录id

这样,就能保证多个进程访问临界资源时同步进行了,保证数据的一致性。
 
测试的时候,启动两个glock.py, 结果如下:

[@tj-10-47 test]# ./glock.py  
2014-03-14 17:08:40,277 -glock:glock.py-L70-INFO: Current log level is : DEBUG 
2014-03-14 17:08:40,299 -glock:glock.py-L43-DEBUG: The lock 'queue' was obtained successfully. 
2014-03-14 17:08:50,299 -glock:glock.py-L81-INFO: You can do some synchronization work across processes! 
2014-03-14 17:08:50,299 -glock:glock.py-L56-DEBUG: The lock 'queue' the lock was released.

可以看到第一个glock.py是 17:08:50解锁的,下面的glock.py是在17:08:50获取锁的,可以证实这样是完全可行的。

[@tj-10-47 test]# ./glock.py 
2014-03-14 17:08:46,873 -glock:glock.py-L70-INFO: Current log level is : DEBUG
2014-03-14 17:08:50,299 -glock:glock.py-L43-DEBUG: The lock 'queue' was obtained successfully.
2014-03-14 17:09:00,299 -glock:glock.py-L81-INFO: You can do some synchronization work across processes!
2014-03-14 17:09:00,300 -glock:glock.py-L56-DEBUG: The lock 'queue' the lock was released.
[@tj-10-47 test]#
Python 相关文章推荐
简单介绍Python中用于求最小值的min()方法
May 15 Python
Python 循环语句之 while,for语句详解
Apr 23 Python
Python日志模块logging基本用法分析
Aug 23 Python
Python图像处理之图像的读取、显示与保存操作【测试可用】
Jan 04 Python
Python3实现统计单词表中每个字母出现频率的方法示例
Jan 28 Python
python递归法实现简易连连看小游戏
Mar 25 Python
python实现通过flask和前端进行数据收发
Aug 22 Python
详解python itertools功能
Feb 07 Python
Python爬虫程序架构和运行流程原理解析
Mar 09 Python
python+adb+monkey实现Rom稳定性测试详解
Apr 23 Python
python实现图像随机裁剪的示例代码
Dec 10 Python
解决Pytorch修改预训练模型时遇到key不匹配的情况
Jun 05 Python
python中使用urllib2获取http请求状态码的代码例子
Jul 07 #Python
Python中使用urllib2防止302跳转的代码例子
Jul 07 #Python
python中使用urllib2伪造HTTP报头的2个方法
Jul 07 #Python
python实现多线程采集的2个代码例子
Jul 07 #Python
Python程序员开发中常犯的10个错误
Jul 07 #Python
python采用requests库模拟登录和抓取数据的简单示例
Jul 05 #Python
浅析python 中__name__ = '__main__' 的作用
Jul 05 #Python
You might like
PHP文章采集URL补全函数(FormatUrl)
2012/08/02 PHP
PHP解码unicode编码的中文字符代码分享
2014/08/13 PHP
PHP 反射(Reflection)使用实例
2015/05/12 PHP
php中array_slice和array_splice函数解析
2016/10/18 PHP
新页面打开实际尺寸的图片
2006/08/25 Javascript
jQuery 入门讲解1
2009/04/15 Javascript
使用 JScript 创建 .exe 或 .dll 文件的方法
2011/07/13 Javascript
JavaScript跨平台的开源框架NativeScript
2015/03/24 Javascript
JavaScript中isPrototypeOf函数作用和使用实例
2015/06/01 Javascript
java中String类型变量的赋值问题介绍
2016/03/23 Javascript
微信小程序链接传参并跳转新页面
2016/11/29 Javascript
React Native仿美团下拉菜单的实例代码
2017/08/08 Javascript
微信小程序movable view移动图片和双指缩放实例代码
2017/08/08 Javascript
vue2.0.js的多级联动选择器实现方法
2018/02/09 Javascript
[05:53]完美世界携手游戏风云打造 卡尔工作室观战系统篇
2013/04/22 DOTA
Python实现获取nginx服务器ip及流量统计信息功能示例
2018/05/18 Python
Python退火算法在高次方程的应用
2018/07/26 Python
Python静态类型检查新工具之pyright 使用指南
2019/04/26 Python
在python plt图表中文字大小调节的方法
2019/07/08 Python
python redis 批量设置过期key过程解析
2019/11/26 Python
python中设置超时跳过,超时退出的方式
2019/12/13 Python
详解python datetime模块
2020/08/17 Python
关于django python manage.py startapp 应用名出错异常原因解析
2020/12/15 Python
javascript实现用户必须勾选协议实例讲解
2021/03/24 Javascript
小小的船教学反思
2014/02/21 职场文书
事业单位竞聘上岗实施方案
2014/03/28 职场文书
学习雷锋月活动总结
2014/07/03 职场文书
创先争优公开承诺书
2014/08/30 职场文书
入党积极分子学习党的纲领思想汇报
2014/09/13 职场文书
毕业生自荐信范文
2015/03/05 职场文书
拯救大兵瑞恩观后感
2015/06/09 职场文书
校园安全教育心得体会
2016/01/15 职场文书
工人先锋号事迹材料(2016精选版)
2016/03/01 职场文书
浅谈Java实现分布式事务的三种方案
2021/06/11 Java/Android
Nginx中使用Lua脚本与图片的缩略图处理的实现
2022/03/18 Servers
SpringCloud Function SpEL注入漏洞分析及环境搭建
2022/04/08 Java/Android