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使用7z解压软件备份文件脚本分享
Feb 21 Python
用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化
Apr 01 Python
Python下Fabric的简单部署方法
Jul 14 Python
浅谈Python使用Bottle来提供一个简单的web服务
Dec 27 Python
python3对接mysql数据库实例详解
Apr 30 Python
python 杀死自身进程的实现方法
Jul 01 Python
如何使用python进行pdf文件分割
Nov 11 Python
Python实现自定义读写分离代码实例
Nov 16 Python
Python获取对象属性的几种方式小结
Mar 12 Python
解决import tensorflow as tf 出错的原因
Apr 16 Python
keras实现基于孪生网络的图片相似度计算方式
Jun 11 Python
Python爬取某拍短视频
Jun 11 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+xslt在windows平台上
2006/10/09 PHP
PHP 压缩文件夹的类代码
2009/11/05 PHP
php 判断服务器操作系统的类型
2014/02/17 PHP
PHP中使用localhost连接Mysql不成功的解决方法
2014/08/20 PHP
php密码生成类实例
2014/09/24 PHP
php数组操作之键名比较与差集、交集赋值的方法
2014/11/10 PHP
ThinkPHP表单数据智能写入create方法实例分析
2015/09/27 PHP
jquery控制listbox中项的移动并排序
2009/11/12 Javascript
Javascript 键盘keyCode键码值表
2009/12/24 Javascript
javascript使用activex控件的代码
2011/01/27 Javascript
javascript调试过程中找不到哪里出错的可能原因
2013/12/16 Javascript
JS判断客户端是手机还是PC的2个代码
2014/04/12 Javascript
什么是MEAN?JavaScript编程中的MEAN是什么意思?
2014/12/18 Javascript
javascript制作幻灯片(360度全景图片)
2015/07/28 Javascript
JavaScript标准对象_动力节点Java学院整理
2017/06/27 Javascript
JS处理数据四舍五入(tofixed与round的区别详解)
2017/10/26 Javascript
Vue中使用JsonView来展示Json树的实例代码
2020/11/16 Javascript
利用soaplib搭建webservice详细步骤和实例代码
2013/11/20 Python
使用wxpython实现的一个简单图片浏览器实例
2014/07/10 Python
python获取当前计算机cpu数量的方法
2015/04/18 Python
Java多线程编程中ThreadLocal类的用法及深入
2016/06/21 Python
使用pycharm生成代码模板的实例
2018/05/23 Python
Python运维自动化之nginx配置文件对比操作示例
2018/08/29 Python
python3中rank函数的用法
2019/11/27 Python
python scrapy重复执行实现代码详解
2019/12/28 Python
15行Python代码实现免费发送手机短信推送消息功能
2020/02/27 Python
python爬虫用scrapy获取影片的实例分析
2020/11/23 Python
一款纯css3实现的颜色渐变按钮的代码教程
2014/11/12 HTML / CSS
英国休闲奢华的缩影:Crew Clothing
2019/05/05 全球购物
说出数据连接池的工作机制是什么?
2013/04/19 面试题
药品促销活动方案
2014/02/14 职场文书
高考备战决心书
2014/03/11 职场文书
信息管理与信息系统专业求职信
2014/06/21 职场文书
学风建设演讲稿
2014/09/12 职场文书
MySQL创建索引需要了解的
2021/04/08 MySQL
CSS中理解层叠性及权重如何分配
2022/12/24 HTML / CSS