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用list或dict字段模式读取文件的方法
Jan 10 Python
Python SQLite3数据库日期与时间常见函数用法分析
Aug 14 Python
pygame实现俄罗斯方块游戏
Jun 26 Python
使用Python处理BAM的方法
Sep 28 Python
Python3从零开始搭建一个语音对话机器人的实现
Aug 23 Python
Python pandas RFM模型应用实例详解
Nov 20 Python
Python求解正态分布置信区间教程
Nov 20 Python
Python函数的定义方式与函数参数问题实例分析
Dec 26 Python
Django 404、500页面全局配置知识点详解
Mar 10 Python
Django-xadmin后台导入json数据及后台显示信息图标和主题更改方式
Mar 11 Python
Python xml、字典、json、类四种数据类型如何实现互相转换
May 27 Python
如何解决cmd运行python提示不是内部命令
Jul 01 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文件上传后端处理小技巧
2016/05/22 PHP
Laravel5.5 实现后台管理登录的方法(自定义用户表登录)
2019/09/30 PHP
javascript各种复制代码收集
2008/09/20 Javascript
ASP中Sub和Function的区别说明
2020/08/30 Javascript
基于jquery的不规则矩形的排列实现代码
2012/04/16 Javascript
JS打开图片另存为对话框实现代码
2012/12/26 Javascript
jsp+javascript打造级连菜单的实例代码
2013/06/14 Javascript
javascript实现回车键提交表单方法总结
2015/01/10 Javascript
js判断登录与否并确定跳转页面的方法
2015/01/30 Javascript
jquery图片轮播特效代码分享
2020/04/20 Javascript
小白谈谈对JS原型链的理解
2016/05/03 Javascript
jQuery的框架介绍
2016/05/11 Javascript
JS实现动态表格的添加,修改,删除功能(推荐)
2016/06/15 Javascript
AngularJs页面筛选标签小功能
2016/08/01 Javascript
在localStorage中存储对象数组并读取的方法
2016/09/24 Javascript
Bootstrap免费字体和图标网站(值得收藏)
2017/03/16 Javascript
详解jQuery-each()方法
2019/03/13 jQuery
最全vue的vue-amap使用高德地图插件画多边形范围的示例代码
2020/07/17 Javascript
Vue-CLI 3 scp2自动部署项目至服务器的方法
2020/07/24 Javascript
Python操作Excel插入删除行的方法
2018/12/10 Python
更新pip3与pyttsx3文字语音转换的实现方法
2019/08/08 Python
详解python中各种文件打开模式
2020/01/19 Python
Python Selenium安装及环境配置的实现
2020/03/17 Python
pygame用blit()实现动画效果的示例代码
2020/05/28 Python
查看keras的默认backend实现方式
2020/06/19 Python
python报错: 'list' object has no attribute 'shape'的解决
2020/07/15 Python
python 使用三引号时容易犯的小错误
2020/10/21 Python
python上下文管理的使用场景实例讲解
2021/03/03 Python
CSS3 实现弹跳的小球动画
2020/10/26 HTML / CSS
爱与责任师德演讲稿
2014/08/26 职场文书
幼儿园教师自我评价
2015/03/04 职场文书
幼儿园2015年度工作总结
2015/04/01 职场文书
2015年酒店客房部工作总结
2015/04/25 职场文书
先进个人主要事迹怎么写
2015/11/04 职场文书
完美解决golang go get私有仓库的问题
2021/05/05 Golang
win10蓝屏0xc0000001安全模式进不了怎么办?win10出现0xc0000001的解决方法
2022/08/05 数码科技