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基于xml parse实现解析cdatasection数据
Sep 30 Python
Python中用函数作为返回值和实现闭包的教程
Apr 27 Python
六个窍门助你提高Python运行效率
Jun 09 Python
让python 3支持mysqldb的解决方法
Feb 14 Python
谈谈python中GUI的选择
Mar 01 Python
打包python 加icon 去掉cmd黑窗口方法
Jun 24 Python
使用NumPy读取MNIST数据的实现代码示例
Nov 20 Python
Scrapy框架基本命令与settings.py设置
Feb 06 Python
深入浅析Python 函数注解与匿名函数
Feb 24 Python
tensorflow指定CPU与GPU运算的方法实现
Apr 21 Python
Python制作一个仿QQ办公版的图形登录界面
Sep 22 Python
 python中的元类metaclass详情
May 30 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与XML、XSLT、Mysql的结合运用实现代码
2009/11/19 PHP
PHP和Mysqlweb应用开发核心技术 第1部分 Php基础-3 代码组织和重用2
2011/07/03 PHP
PHP代码审核的详细介绍
2013/06/13 PHP
PHP速成大法
2015/01/30 PHP
PHP框架Laravel学习心得体会
2015/10/28 PHP
PHP使用zlib扩展实现GZIP压缩输出的方法详解
2018/04/09 PHP
laravel + vue实现的数据统计绘图(今天、7天、30天数据)
2018/07/31 PHP
javascript与asp.net(c#)互相调用方法
2009/12/13 Javascript
javascript实现的在当前窗口中漂浮框的代码
2010/03/15 Javascript
JS JSON对象转为字符串的简单实现方法
2013/11/18 Javascript
javaScript中slice函数用法实例分析
2015/06/08 Javascript
javascript产生随机数方法汇总
2016/01/25 Javascript
Javascript闭包与函数柯里化浅析
2016/06/22 Javascript
AngularJS通过$location获取及改变当前页面的URL
2016/09/23 Javascript
jqueryUI tab标签页代码分享
2017/10/09 jQuery
JS实现指定区域的全屏显示功能示例
2019/04/25 Javascript
layer弹出层扩展主题的方法
2019/09/11 Javascript
解决element-ui的下拉框有值却无法选中的情况
2020/11/07 Javascript
深入理解Python中各种方法的运作原理
2015/06/15 Python
详解Python import方法引入模块的实例
2017/08/02 Python
在python中利用opencv简单做图片比对的方法
2019/01/24 Python
使用Python和OpenCV检测图像中的物体并将物体裁剪下来
2019/10/30 Python
Django实现分页显示效果
2019/10/31 Python
关于Flask项目无法使用公网IP访问的解决方式
2019/11/19 Python
wxpython+pymysql实现用户登陆功能
2019/11/19 Python
在tensorflow中实现去除不足一个batch的数据
2020/01/20 Python
Python同时迭代多个序列的方法
2020/07/28 Python
python实现人工蜂群算法
2020/09/18 Python
HTML5 localStorage使用总结
2017/02/22 HTML / CSS
html5中使用hotcss.js实现手机端自适配的方法
2020/04/23 HTML / CSS
TripAdvisor越南:全球领先的旅游网站
2017/09/21 全球购物
销售总监岗位职责
2014/01/04 职场文书
报到证办理个人委托书
2014/10/06 职场文书
2014年纳税评估工作总结
2014/12/23 职场文书
股东大会通知
2015/04/24 职场文书
react使用antd的上传组件实现文件表单一起提交功能(完整代码)
2021/06/29 Javascript