> 文章列表 > 分布式锁特点、以及用python3实现redis分布式锁

分布式锁特点、以及用python3实现redis分布式锁

分布式锁特点、以及用python3实现redis分布式锁

1、概述

官方文档:https://redis.io/docs/manual/patterns/distributed-locks/

对于分布式场景,我们可以使用分布式锁,它是控制分布式系统之间互斥访问共享资源的一种方式。
比如说在一个分布式系统中,多台机器上部署了多个服务,当客户端一个用户发起一个数据插入请求时,如果没有分布式锁机制保证,那么那多台机器上的多个服务可能进行并发插入操作,导致数据重复插入,对于某些不允许有多余数据的业务来说,这就会造成问题。而分布式锁机制就是为了解决类似这类问题,保证多个服务之间互斥的访问共享资源,如果一个服务抢占了分布式锁,其他服务没获取到锁,就不进行后续操作。

2、分布式锁特点

分布式锁一般有如下的特点:

  • 互斥性: 同一时刻只能有一个线程持有锁
  • 可重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁
  • 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
  • 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
  • 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒

3、分布式锁的实现方式

我们一般实现分布式锁有以下几种方式:

  • 基于数据库
  • 基于Redis
  • 基于zookeeper

4、用python3实现redis分布式锁

实现原理:WATCH, MULTI, EXEC, DISCARD事务机制实现分布式锁,事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

EXEC 命令负责触发并执行事务中的所有命令:如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。

python3脚本如下:

# -*- encoding: utf-8 -*-
import time
import uuid
import redisfrom multiprocessing import Processredis_client = redis.Redis(host='127.0.0.1', port=6379, db=3)def acquire_lock(lock_name, args, acquire_timeout=30, time_out=120):"""加锁:param lock_name: 加锁名称:param args::param acquire_timeout::param time_out::return:"""# 36位长度的uuid, 也可以自定义加密identifier = str(uuid.uuid4())# 客户端获取锁的结束时间end = time.time() + acquire_timeoutlock_names = "lock_name:" + lock_nameprint(f"进程 {str(args)} end_time:{end}")while time.time() < end:# setnx(key,value) 只有key不存在情况下,将key的值设置为value 返回True,若key存在则不做任何动作,返回Falseif redis_client.setnx(lock_names, identifier):# 设置键的过期时间,过期自动剔除,释放锁print(f'获得锁:进程{str(args)}  分布式锁value: {identifier}')redis_client.expire(lock_name, time_out)return identifier# 当锁未被设置过期时间时,重新设置其过期时间elif redis_client.ttl(lock_name) == -1:redis_client.expire(lock_name, time_out)time.sleep(0.001)return Falsedef release_lock(lock_name, identifire):"""锁的释放"""lock_names = "lock_name:" + lock_namepipe = redis_client.pipeline(True)while True:try:# 通过watch命令监视某个键,当该键未被其他客户端修改值时,事务成功执行。当事务运行过程中,发现该值被其他客户端更新了值,任务失败pipe.watch(lock_names)if not pipe.get(lock_names):print(f'锁未释放成功: {pipe.get(lock_names)}')returnif pipe.get(lock_names).decode() == identifire:  # 检查客户端是否仍然持有该锁# multi命令用于开启一个事务,它总是返回ok# multi执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC 命令被调用时, 所有队列中的命令才会被执行pipe.multi()# 删除键,释放锁pipe.delete(lock_names)# execute命令负责触发并执行事务中的所有命令pipe.execute()return Truepipe.unwatch()breakexcept redis.exceptions.WatchError:# # 释放锁期间,有其他客户端改变了键值对,锁释放失败,进行循环passreturn Falsedef exec_test(lockname, args):"""模拟加锁解锁的过程"""identifire = acquire_lock(lockname, args)print(f'identifire :{identifire}')# 如果获取到锁,则进行业务逻辑处理if identifire:# sleep 3s 模拟业务逻辑处理,处理完之后进行锁释放,让其他进程获取锁time.sleep(3)res = release_lock(lockname, identifire)print(f'释放状态: {res}')else:print('获取redis分布式锁失败,其他进程正在使用')if __name__ == '__main__':# 启动9个进程模拟加锁和释放锁的过程# for i in range(0, 9):#     Process(target=exec_test, args=('test11', i)).start()# 加锁lock_result = acquire_lock('link11', 1, acquire_timeout=30, time_out=20)print(f'lock_result: {lock_result}')# 释放锁release_result = release_lock('link11', lock_result)