Python使用分布式锁的代码演示示例


Posted in Python onJuly 30, 2018

在计算机并发领域编程中总是会与锁打交道,锁又有很多种,互斥锁、自旋锁等等。

锁总是伴随着线程、进程这样的词汇出现,阮一峰有 一篇文章 对这些名词进行了简单易懂的解释。

我的理解是,使用线程、进程是为了实现并发从而获得性能的提升(利用多核CPU,多台服务器),但这种并发由于调度的不确定性,很容易出乱子,为了(在一些共享资源、关键节点上)不出乱子,又需要对资源加锁,在操作这个资源时控制这种并发,将乱子消灭。

很多语言都提供了一些线程级别的锁实现以及一些相应的工具,但在进程方面就无能为力了。而一个服务部署到生产环境,往往会部署多个实例,这种情况下,就经常会用到给不同进程用的锁,分布式锁便是在分布式系统中对某共享资源进行加锁的构件。

现在来试着展示一下在Python项目中如何使用简单的分布式互斥锁。

不使用分布式锁会怎样

先用一个简单的实例来演示一下,不使用分布式锁会出怎样的乱子。

假设商城系统要做秒杀活动,在redis中记录着 count:1 的信息,到秒杀时间点的时候,会收到许多的请求,这时各应用程序去查redis中count的值,若count还大于0,则将count-1,这样其他请求就不再能秒杀到了。

# -*- coding: utf-8 -*-
import os
import arrow
import redis
from multiprocessing import Pool

HOT_KEY = 'count'
r = redis.Redis(host='localhost', port=6379)

def seckilling():
  name = os.getpid()
  v = r.get(HOT_KEY)
  if int(v) > 0:
    print name, ' decr redis.'
    r.decr(HOT_KEY)
  else:
    print name, ' can not set redis.', v

def run_without_lock(name):
  while True:
    if arrow.now().second % 5 == 0:
      seckilling()
      return

if __name__ == '__main__':
  p = Pool(16)
  r.set(HOT_KEY, 1)
  for i in range(16):
    p.apply_async(run_without_lock, args=(i, ))
  print 'now 16 processes are going to get lock!'
  p.close()
  p.join()
  print('All subprocesses done.')

以上代码使用多进程来模仿这种并发请求场景,程序开始的时候将count设为1,之后各进程开始进入等待,当秒数为5的时候,所有进程同时去访问秒杀函数,来看一下效果:

Python使用分布式锁的代码演示示例

运行结果

Python使用分布式锁的代码演示示例

redis查询展示

从程序打印与查redis的结果来说并未如愿,本来秒杀商品只有一件,但却被成功抢购到了4次。这是由于各进程在get count的值时,对redis值更新的指令已经发出而还未进行完毕,会让其他进程认为自己可以购得。

这种问题可归为 不可重复读 种类的数据并发问题。

在这种毫无保护的情况下,其他常见并发问题幻读、脏读、第一第二类丢失更新等都有可能发生,这里不再一一举例。

使用ZooKeeper作分布式锁

作为致力于解决分布式协同问题的知名工具,利用zookeeper提供的API和它对于节点唯一性与顺序一致性的保证可以实现分式式锁。

实现思路为,各进程去创建 /exclusive_lock/lock 的结点,zookeeper保证只有一个client可以创建成功,那么便认为创建成功的那个client获得了锁,当它处理完业务后,将该node删除,其他client会监听到这个事件,并再次尝试创建该节点,如此进行下去。

Kazoo 库实现了这种Lock,使用起来非常简单,编程人员可以不用再去自己实现acquire,release等锁的通用接口。

同时在Python中,对锁的使用往往可以通过优雅的上下文管理器with。

def run_with_zk_lock(name):
  zk = KazooClient()
  zk.start()
  lock = zk.Lock("/lockpath", "my-identifier")
  while True:
    if arrow.now().second % 5 == 0:
      with lock:
        seckilling()
        return

Python使用分布式锁的代码演示示例

使用zk结果

Python使用分布式锁的代码演示示例

redis查询展示

当秒杀发生时,只有获得锁的进程可以去进行秒杀操作。

在锁的帮助下,程序按照预想的方式运行了。

使用redis作分布式锁

在redis的网站有一篇文章 专门介绍如何使用redis作为分布式锁,文尾还附带了对此文章的反对文章以及再次回击的文章,有点精彩。

文章提到了一个redlock的分布式锁设计。

设置锁的redis命令为 SET resource_name my_random_value NX PX 30000 ,当加NX参数时,若resouce_name不存在才会创建,若不存在则会向client返回不同的结果,利用这个机制,便只有一个client可以set成功,就像上面的zk一样了。

但是,实现这样一个分布式锁远不止这么简单,redis并不像zk一样是一个分布式协同工具,会向client做出分布式中各种一致性及容错、可用性的保证。

redis本身也是集群部署的,它们之间有着异步复制时间差、容错等问题可能会出现,要真正做到这个锁的实现在线上大规模分布式系统中可用,真的是要考虑各种情况,很不容易。

关于如何在语言上实现一个锁的接口,redlock的原理与代码实现,以及上述kazoo包里实现lock的源码,我会在另一篇专门的文章中说一下。

redlock-py 包是python语言中对上述文章的实现,我们现在使用它来进行尝试。

rlock = RedLock([{"host": "localhost", "port": 6379, "db": 0}, ])

def run_with_redis_lock(name):
  while True:
    if arrow.now().second % 5 == 0:
      with rlock:
        seckilling()
        return

Python使用分布式锁的代码演示示例

redis锁运行结果

运行结果和上面使用zk一样,符合程序设计预期。

以上只是基于python语言的一些代码展示,通过使用两个第三方包,来使用分布式锁来避免并发程序中混乱的产生。

但其实这中间是有一个断层的,即,这两个工具都是提供了一个机制,而并不是直接对外提供了操作锁的API,那么如何利用这个机制来实现这样的锁正是这两个第三方做的事情。

简单看过它们实现的源码,以及threading中一些lock的代码,发现在锁的实现上是有着共通之处的,都有通用的acquire与release方法,然后将 enter 与 exit 指向前面两个方法来实现上下文管理器with的用法。

此外,还可以利用关系型数据库如MySQL固有的锁机制来作为分布式锁,但由于数据库往往是系统的瓶颈所在,没有必要为它引入不必要的压力。同时,MySQL中的锁、隔离级别也有一大堆可说的,在github上找了一下也并未找到一个成熟的像上面的基于MySQL实现的对外暴露锁通用API的第三方包,故未能在上面加以展示。

想要说清楚这个事情并没有那么容易,之后我会尝试搞清楚如何写一个比较地道的锁,并对上面两个第三方包的具体实现加以研究,争取把这个断层补上。之后,或许可以尝试实现一下基于MySQL的类似第三方包,这需要对MySQL的一些机制搞得更加清楚才行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python模块学习 datetime介绍
Aug 27 Python
基于Python实现一个简单的银行转账操作
Mar 06 Python
python cx_Oracle模块的安装和使用详细介绍
Feb 13 Python
Python3之文件读写操作的实例讲解
Jan 23 Python
Python使用Selenium模块模拟浏览器抓取斗鱼直播间信息示例
Jul 18 Python
PyTorch读取Cifar数据集并显示图片的实例讲解
Jul 27 Python
Python爬虫库requests获取响应内容、响应状态码、响应头
Jan 25 Python
python_array[0][0]与array[0,0]的区别详解
Feb 18 Python
Python 可视化神器Plotly详解
Dec 26 Python
Python编写万花尺图案实例
Jan 03 Python
如何在pycharm中快捷安装pip命令(如pygame)
May 31 Python
用Python爬取某乎手机APP数据
Jun 15 Python
利用Python如何批量修改数据库执行Sql文件
Jul 29 #Python
利用Python如何批量更新服务器文件
Jul 29 #Python
python高阶爬虫实战分析
Jul 29 #Python
python3.5基于TCP实现文件传输
Mar 20 #Python
python3基于TCP实现CS架构文件传输
Jul 28 #Python
python cs架构实现简单文件传输
Mar 20 #Python
Tornado Web Server框架编写简易Python服务器
Jul 28 #Python
You might like
PHP中=赋值操作符对不同数据类型的不同行为
2011/01/02 PHP
把1316这个数表示成两个数的和,其中一个为13的倍数,另一个是11的倍数,求这两个数。
2011/06/24 PHP
JS 继承实例分析
2008/11/04 Javascript
FireFox JavaScript全局Event对象
2009/06/14 Javascript
图片在浏览器中底部对齐 解决方法之一
2011/11/30 Javascript
Jquery中的层次选择器与find()的区别示例介绍
2014/02/20 Javascript
JavaScript里实用的原生API汇总
2015/05/14 Javascript
jQuery实现向下滑出的平滑下拉菜单效果
2015/08/21 Javascript
CSS3 3D 技术手把手教你玩转
2016/09/02 Javascript
JS判断form内所有表单是否为空的简单实例
2016/09/09 Javascript
AngularJs中 ng-repeat指令中实现含有自定义指令的动态html的方法
2017/01/19 Javascript
webpack配置的最佳实践分享
2017/04/21 Javascript
支付宝小程序自定义弹窗dialog插件的实现代码
2018/11/30 Javascript
移动端(微信等使用vConsole调试console的方法
2019/03/05 Javascript
微信小程序弹窗禁止页面滚动的实现代码
2020/12/30 Javascript
[00:30]塑造者的传承礼包-戴泽“暗影之焰”套装展示视频
2014/04/04 DOTA
[48:37]EG vs OG 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
Python中处理字符串的相关的len()方法的使用简介
2015/05/19 Python
[原创]Python入门教程2. 字符串基本操作【运算、格式化输出、常用函数】
2018/10/29 Python
选择Python写网络爬虫的优势和理由
2019/07/07 Python
Python 进程之间共享数据(全局变量)的方法
2019/07/16 Python
15行Python代码实现免费发送手机短信推送消息功能
2020/02/27 Python
Tensorflow与Keras自适应使用显存方式
2020/06/22 Python
利用CSS3实现单选框动画特效示例代码
2016/09/26 HTML / CSS
利用HTML5+CSS3实现3D转换效果实例详解
2017/05/02 HTML / CSS
用HTML5制作视频拼图的教程
2015/05/13 HTML / CSS
HTML5超文本标记语言的实现方法
2020/09/24 HTML / CSS
办公室年终个人自我评价
2013/10/28 职场文书
集团公司总经理岗位职责
2013/12/20 职场文书
成语的广告词
2014/03/19 职场文书
新颖的化妆品活动方案
2014/08/21 职场文书
正风肃纪查摆剖析材料
2014/10/10 职场文书
2014年工程师工作总结
2014/11/25 职场文书
2015国庆66周年宣传语
2015/07/14 职场文书
创业计划书之暑假培训班
2019/11/09 职场文书
windows server2012 R2下安装PaddleOCR服务的的详细步骤
2022/09/23 Servers