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实现linux服务器批量修改密码并生成execl
Apr 22 Python
Python使用matplotlib简单绘图示例
Feb 01 Python
Django使用详解:ORM 的反向查找(related_name)
May 30 Python
Python for循环生成列表的实例
Jun 15 Python
Python wxPython库消息对话框MessageDialog用法示例
Sep 03 Python
Python中创建二维数组
Oct 17 Python
Python3 使用cookiejar管理cookie的方法
Dec 28 Python
Python Pandas 如何shuffle(打乱)数据
Jul 30 Python
Python 网络编程之UDP发送接收数据功能示例【基于socket套接字】
Oct 11 Python
Python搭建代理IP池实现检测IP的方法
Oct 27 Python
python中逻辑与或(and、or)和按位与或异或(&、|、^)区别
Aug 05 Python
如何在Python中创建二叉树
Mar 30 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实现excel中rank函数功能的方法
2015/01/20 PHP
浅析PHP类的反射来实现依赖注入过程
2018/02/06 PHP
鼠标移到图片上变大显示而不是放大镜效果
2014/06/15 Javascript
滚动条响应鼠标滑轮事件实现上下滚动的js代码
2014/06/30 Javascript
jquery实现未经美化的简洁TAB菜单效果
2015/08/28 Javascript
JS实现双击屏幕滚动效果代码
2015/10/28 Javascript
微信小程序链接传参并跳转新页面
2016/11/29 Javascript
深入理解jquery中extend的实现
2016/12/22 Javascript
ES6新特性之变量和字符串用法示例
2017/04/01 Javascript
Vue 动态设置路由参数的案例分析
2018/04/24 Javascript
AngularJS ui-router刷新子页面路由的方法
2018/07/23 Javascript
js计算两个日期间的天数月的实例代码
2018/09/20 Javascript
angular 表单验证器验证的同时限制输入的实现
2019/04/11 Javascript
es6数组的flat(),flatMap()函数用法实例分析
2020/04/18 Javascript
js实现直播点击飘心效果
2020/08/19 Javascript
js实现点击选项置顶动画效果
2020/08/25 Javascript
[01:02:06]LGD vs Mineski Supermajor 胜者组 BO3 第二场 6.5
2018/06/06 DOTA
Python编程中运用闭包时所需要注意的一些地方
2015/05/02 Python
高效测试用例组织算法pairwise之Python实现方法
2017/07/19 Python
Python两个字典键同值相加的几种方法
2019/03/05 Python
Python3.6实现带有简单界面的有道翻译小程序
2019/04/16 Python
python仿evething的文件搜索器实例代码
2019/05/13 Python
Python3.x+迅雷x 自动下载高分电影的实现方法
2020/01/12 Python
html5的自定义data-*属性与jquery的data()方法的使用
2014/07/02 HTML / CSS
HTML5 Canvas的事件处理介绍
2015/04/24 HTML / CSS
饿了么订餐官网:外卖、网上订餐
2019/06/28 全球购物
DeinDesign德国:设计自己的手机壳
2019/12/14 全球购物
2013年员工自我评价范文
2013/12/27 职场文书
24岁生日感言
2014/01/13 职场文书
电气工程及其自动化专业毕业生自荐信
2014/06/21 职场文书
喝酒驾驶检讨书
2014/10/01 职场文书
党性分析材料格式
2014/12/19 职场文书
作文评语怎么写
2014/12/25 职场文书
导游词之丽江普济寺
2019/10/22 职场文书
《王者天下》第4季首话新剧照 4月9日正式开播
2022/04/07 日漫
webpack介绍使用配置教程详解webpack介绍和使用
2022/06/25 Javascript