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程序语言快速上手教程
Jul 18 Python
在Django中管理Users和Permissions以及Groups的方法
Jul 23 Python
一键搞定python连接mysql驱动有关问题(windows版本)
Apr 23 Python
Django 跨域请求处理的示例代码
May 02 Python
浅谈Series和DataFrame中的sort_index方法
Jun 07 Python
python pandas读取csv后,获取列标签的方法
Nov 12 Python
python实现五子棋小游戏
Mar 25 Python
python中的print()输出
Apr 12 Python
详解Python的循环结构知识点
May 20 Python
python 两种方法修改文件的创建时间、修改时间、访问时间
Sep 26 Python
Scrapy中如何向Spider传入参数的方法实现
Sep 28 Python
python 可视化库PyG2Plot的使用
Jan 21 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 验证码不显示只有一个小红叉的解决方法
2013/09/30 PHP
php根据用户名和手机号查询是否存在手机号码
2017/02/16 PHP
laravel-admin表单提交隐藏一些数据,回调时获取数据的方法
2019/10/08 PHP
PHP 实现 JSON 数据的编码和解码操作详解
2020/04/22 PHP
TNC vs IO BO3 第一场2.13
2021/03/10 DOTA
JS画5角星方法介绍
2013/09/17 Javascript
手机端网页点击链接触发自动拨打或保存电话的示例代码
2014/08/15 Javascript
jQuery知识点整理
2015/01/30 Javascript
jquery带翻页动画的电子杂志代码分享
2015/08/21 Javascript
JavaScript中的数据类型转换方法小结
2015/10/26 Javascript
温习Javascript基础语法之词法结构
2016/05/31 Javascript
Spring MVC中Ajax实现二级联动的简单实例
2016/07/06 Javascript
jQuery实现的选择商品飞入文本框动画效果完整实例
2016/08/10 Javascript
Javascript中this绑定的3种方法与比较
2016/10/13 Javascript
JavaScript三种绑定事件方式及相互之间的区别分析
2017/01/10 Javascript
Node.js利用js-xlsx处理Excel文件的方法详解
2017/07/05 Javascript
微信小程序之圆形进度条实现思路
2018/02/22 Javascript
深入理解redux之compose的具体应用
2020/01/12 Javascript
[45:18]2018DOTA2亚洲邀请赛 4.3 突围赛 Optic vs iG 第一场
2018/04/04 DOTA
[57:55]完美世界DOTA2联赛PWL S3 Magma vs Phoenix 第二场 12.12
2020/12/16 DOTA
Python使用requests发送POST请求实例代码
2018/01/25 Python
python 使用turtule绘制递归图形(螺旋、二叉树、谢尔宾斯基三角形)
2019/05/30 Python
python实现ip代理池功能示例
2019/07/05 Python
Python根据服务获取端口号的方法
2019/09/25 Python
python抓取多种类型的页面方法实例
2019/11/20 Python
详解Flask前后端分离项目案例
2020/07/24 Python
vscode配置anaconda3的方法步骤
2020/08/08 Python
python3.5的包存放的具体路径
2020/08/16 Python
pycharm中选中一个单词替换所有重复单词的实现方法
2020/11/17 Python
详解如何使用Pytest进行自动化测试
2021/01/14 Python
AmazeUI 按钮交互的实现示例
2020/08/24 HTML / CSS
奥地利度假券的专家:we-are.travel
2019/04/10 全球购物
优秀士兵先进事迹
2014/02/06 职场文书
市场营销专业求职信
2014/06/17 职场文书
交警作风整顿剖析材料
2014/10/11 职场文书
Java 死锁解决方案
2022/05/11 Java/Android