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 相关文章推荐
python3.3实现乘法表示例
Feb 07 Python
python网络编程学习笔记(九):数据库客户端 DB-API
Jun 09 Python
对于Python中线程问题的简单讲解
Apr 03 Python
约瑟夫问题的Python和C++求解方法
Aug 20 Python
举例讲解如何在Python编程中进行迭代和遍历
Jan 19 Python
图文详解WinPE下安装Python
May 17 Python
Python中的异常处理相关语句基础学习笔记
Jul 11 Python
Tesserocr库的正确安装方式
Oct 19 Python
Python 实现一个手机号码获取妹子名字的功能
Sep 25 Python
利用python生成照片墙的示例代码
Apr 09 Python
利用Python中的Xpath实现一个在线汇率转换器
Sep 09 Python
Python趣味挑战之教你用pygame画进度条
May 31 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
多重?l件?合查?(二)
2006/10/09 PHP
PHP性能优化工具篇Benchmark类调试执行时间
2011/12/06 PHP
php使用curl并发减少后端访问时间的方法分析
2016/05/12 PHP
Yii2使用dropdownlist实现地区三级联动功能的方法
2016/07/18 PHP
如何用PHP做到页面注册审核
2017/03/02 PHP
jQuery autocomplete插件修改
2009/04/17 Javascript
jQuery代码优化之基本事件
2011/11/01 Javascript
javascript的offset、client、scroll使用方法详解
2012/12/25 Javascript
jquery Validation表单验证使用详解
2020/09/12 Javascript
Nodejs初级阶段之express
2015/11/23 NodeJs
静态页面实现 include 引入公用代码的示例
2017/09/25 Javascript
微信小程序出现wx.navigateTo页面不跳转问题的解决方法
2017/12/26 Javascript
react build 后打包发布总结
2018/08/24 Javascript
微信{"errcode":48001,"errmsg":"api unauthorized, hints: [ req_id: 1QoCla0699ns81 ]"}
2018/10/12 Javascript
微信小程序学习笔记之获取位置信息操作图文详解
2019/03/29 Javascript
利用JavaScript将Excel转换为JSON示例代码
2019/06/14 Javascript
jquery实现商品sku多属性选择功能(商品详情页)
2019/12/20 jQuery
Vue路由权限控制解析
2020/11/09 Javascript
使用Vant完成Dialog弹框案例
2020/11/11 Javascript
JavaScript基于SVG的图片切换效果实例代码
2020/12/15 Javascript
python脚本实现统计日志文件中的ip访问次数代码分享
2014/08/06 Python
Python实现批量下载文件
2015/05/17 Python
11月编程语言排行榜 Python逆袭C#上升到第4
2017/11/15 Python
pytorch绘制并显示loss曲线和acc曲线,LeNet5识别图像准确率
2020/01/02 Python
python手写均值滤波
2020/02/19 Python
numpy的Fancy Indexing和array比较详解
2020/06/11 Python
Marlies Dekkers内衣美国官方网上商店:高端内衣品牌
2018/11/12 全球购物
经济类毕业生求职信
2014/06/26 职场文书
学习“七一”讲话精神体会
2014/07/08 职场文书
党员批评与自我批评思想汇报
2014/10/08 职场文书
申报优秀教师材料
2014/12/16 职场文书
论文答谢词
2015/01/20 职场文书
听证会主持词
2015/07/03 职场文书
会计实训总结范文
2015/08/03 职场文书
Python爬虫之爬取某文库文档数据
2021/04/21 Python
MySQL中in和exists区别详解
2021/06/03 MySQL