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 两个列表的差集、并集和交集实现代码
Sep 21 Python
python编程之requests在网络请求中添加cookies参数方法详解
Oct 25 Python
Python基于高斯消元法计算线性方程组示例
Jan 17 Python
Python解决八皇后问题示例
Apr 22 Python
Python装饰器语法糖
Jan 02 Python
python实现关闭第三方窗口的方法
Jun 28 Python
用python建立两个Y轴的XY曲线图方法
Jul 08 Python
Python人工智能之路 jieba gensim 最好别分家之最简单的相似度实现
Aug 13 Python
numpy创建单位矩阵和对角矩阵的实例
Nov 29 Python
python装饰器原理与用法深入详解
Dec 19 Python
django-csrf使用和禁用方式
Mar 13 Python
Pytorch 中net.train 和 net.eval的使用说明
May 22 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
基于simple_html_dom的使用小结
2013/07/01 PHP
解析php类的注册与自动加载
2013/07/05 PHP
php环境套包 dedeampz 伪静态设置示例
2014/03/26 PHP
PHP中file_get_contents高?用法实例
2014/09/24 PHP
Yii学习总结之安装配置
2015/02/22 PHP
PHP中的数组处理函数实例总结
2016/01/09 PHP
关于php unset对json_encode的影响详解
2018/11/14 PHP
Js实现双击鼠标自动滚动屏幕的示例代码
2013/12/14 Javascript
jquery实现侧边弹出的垂直导航
2014/12/09 Javascript
JavaScript获取并更改input标签name属性的方法
2015/07/02 Javascript
js实现二级菜单渐隐显示
2015/11/03 Javascript
基于jquery实现简单的手风琴特效
2015/11/24 Javascript
在AngularJS中如何使用谷歌地图把当前位置显示出来
2016/01/25 Javascript
Bootstrap实现弹性搜索框
2016/07/11 Javascript
AngularJS中的JSONP实例解析
2016/12/01 Javascript
Vue.js第一天学习笔记(数据的双向绑定、常用指令)
2016/12/01 Javascript
jQuery获取所有父级元素及同级元素及子元素的方法(推荐)
2018/01/21 jQuery
Vue实现动态创建和删除数据的方法
2018/03/17 Javascript
如何在Vue中抽离接口配置文件
2019/10/31 Javascript
js实现弹窗猜数字游戏
2020/11/26 Javascript
JS+JQuery实现无缝连接轮播图
2020/12/30 jQuery
Python实现把json格式转换成文本或sql文件
2015/07/10 Python
Python计算已经过去多少个周末的方法
2015/07/25 Python
小米5s微信跳一跳小程序python源码
2018/01/08 Python
python ---lambda匿名函数介绍
2019/03/13 Python
Python Scrapy框架:通用爬虫之CrawlSpider用法简单示例
2020/04/11 Python
canvas小画板之平滑曲线的实现
2020/08/12 HTML / CSS
加拿大时尚床上用品零售商:QE Home | Quilts Etc
2018/01/22 全球购物
英国户外服装、鞋类和设备的领先零售商:Millets
2020/10/12 全球购物
.net软件工程师应聘上机试题
2015/03/10 面试题
银行会计职员个人的自我评价
2013/09/29 职场文书
八年级音乐教学反思
2014/01/09 职场文书
营销团队口号
2014/06/06 职场文书
医药公司采购员岗位职责
2015/04/03 职场文书
2015年精神文明建设工作总结
2015/04/21 职场文书
MySQL 百万级数据的4种查询优化方式
2021/06/07 MySQL