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 相关文章推荐
Django1.3添加app提示模块不存在的解决方法
Aug 26 Python
Python利用多进程将大量数据放入有限内存的教程
Apr 01 Python
简单掌握Python的Collections模块中counter结构的用法
Jul 07 Python
基于Python中numpy数组的合并实例讲解
Apr 04 Python
pytorch构建网络模型的4种方法
Apr 13 Python
Python实现获取前100组勾股数的方法示例
May 04 Python
使用Python实现图像标记点的坐标输出功能
Aug 14 Python
keras实现VGG16 CIFAR10数据集方式
Jul 07 Python
Python实现一个简单的递归下降分析器
Aug 01 Python
Python3如何在服务器打印资产信息
Aug 27 Python
Python+OpenCV图像处理——打印图片属性、设置存储路径、调用摄像头
Oct 22 Python
pandas 按日期范围筛选数据的实现
Feb 20 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编程语言开发动态WAP页面
2006/10/09 PHP
PHP 出现乱码和Sessions验证问题的解决方法!
2008/12/06 PHP
php类自动加载器实现方法
2015/07/28 PHP
Yii2 GridView实现列表页直接修改数据的方法
2016/05/16 PHP
php文件包含目录配置open_basedir的使用与性能详解
2017/04/03 PHP
js 遍历对象的属性的代码
2011/12/29 Javascript
基于JQuery实现滚动到页面底端时自动加载更多信息
2014/01/31 Javascript
利用jQuery实现可以编辑的表格
2014/05/26 Javascript
js 模式窗口(模式对话框和非模式对话框)的使用介绍
2014/07/17 Javascript
jQuery插件Tmpl的简单使用方法
2015/04/27 Javascript
Bootstrap每天必学之标签与徽章
2015/11/27 Javascript
jQuery Validate表单验证深入学习
2015/12/18 Javascript
JavaScript的设计模式经典之代理模式
2016/02/24 Javascript
js仿百度登录页实现拖动窗口效果
2016/03/11 Javascript
jquery简单插件制作(fn.extend)完整实例
2016/05/24 Javascript
jQuery解决$符号命名冲突
2016/06/18 Javascript
JS生成和下载二维码的代码
2016/12/07 Javascript
js实现数字递增特效【仿支付宝我的财富】
2017/05/05 Javascript
详解nodejs微信jssdk后端接口
2017/05/25 NodeJs
jQuery+Ajax请求本地数据加载商品列表页并跳转详情页的实现方法
2017/07/12 jQuery
在vue中使用css modules替代scroped的方法
2018/03/10 Javascript
微信小程序实现长按删除图片的示例
2018/05/18 Javascript
微信小程序实现折叠与展开文章功能
2018/06/12 Javascript
微信小程序防止多次点击跳转(函数节流)
2019/09/19 Javascript
Python实现感知器模型、两层神经网络
2017/12/19 Python
python简单实现插入排序实例代码
2020/12/16 Python
详解移动端HTML5页面端去掉input输入框的白色背景和边框(兼容Android和ios)
2016/12/15 HTML / CSS
amazeui页面分析之登录页面的示例代码
2020/08/25 HTML / CSS
Feelunique美国:欧洲大型的在线美妆零售电商
2018/11/04 全球购物
波兰化妆品和护肤品购物网站:eKobieca
2019/08/30 全球购物
抽奖活动主持词
2014/03/31 职场文书
招股说明书范本
2014/05/06 职场文书
中层干部培训方案
2014/06/16 职场文书
机电一体化毕业生自荐信
2014/06/19 职场文书
小学家长通知书评语
2014/12/31 职场文书
详解Python常用的魔法方法
2021/06/03 Python