Skip to main content
 Web开发网 » 编程语言 » Python语言

如何用 redis 造一把分布式锁

2021年11月27日5500百度已收录

  基本概念

  wiki:In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy.

  在计算机科学中,锁或互斥量是一种同步机制,用于在多线程执行环境中,强行限制对资源访问。锁常被用于同步并发控制。

  简单来讲,锁是用来控制多线程执行对资源的并发访问的。比如当一个资源只允许在任意时刻只有一个执行线程对其进行写操作,那当其他线程要访问资源时,就必须要检查该该资源上是否存在写操作锁,如果存在,必须要等待锁的释放并获得锁之后才能对资源进行访问。

  悲观锁

  悲观锁假设在一个完整事务发生的过程中,总是会有其他线程会更改所操作的资源,因此线程总是对资源加锁之后才会对其做更改。

  乐观锁

  乐观锁假设在一个完整事务发生的过程中,不一定会有其他线程会更改资源,一旦发现资源被更改,则停止当前事务回滚所有操作。

  分布式锁

  常见的锁有多线程锁、数据库事务中的行级锁、表级锁等,这些锁的特点都是发生在单一系统环境中,如果需要在不同的进程、不同机器和系统等分布式环境中控制对资源的访问,这时候我们需要一把分布式锁。

  简单来说,分布式锁就是解决分布式环境中对资源的访问限制。

  如何设计一把分布式锁

  我们用 redis 来实现这把分布式的锁,redis 速度快、支持事务、可持久化的特点非常适合创建分布式锁。

  分布式环境中如何消除网络延迟对锁获取的影响

  锁,简单来说就是存于 redis 中一个唯一的 key。一般而言,redis 用 set 命令来完成一个 key 的设置(加锁),使用 get 命令获取 key 的信息(检查锁)。由于网络延迟的存在,简单的使用 set 和 get 命令可能会带来如下问题:

  线程 A 检查锁是否存在(get)–>否–>加锁(set),在 A 发起加锁命令但是还没有加锁成功的时候,可能线程 B 已经完成了 set 操作,锁被 B 获得,但是 A 也发起了加锁请求,由于 set 命令并不检查 key 的存在,B 的锁很可能会被 A 的 set 操作破坏。

  幸运的是,redis 提供了另一个命令 setx : 当指定的 key 不存在时,设置 key 的值为指定 value,如果存在,不做任何操作,成功则返回 1,失败则返回 0。也就是只要命令返回成功,线程就能正确获得锁,不需要再做类似 get 检查操作。

  使用 setx 可以消除网络延迟对锁设置的影响。

  加锁的客户端发生 crash 导致锁不能被正确释放应该怎么处理?

  加锁成功并操作完成时候,就需要加锁线程对锁进行释放,以让出资源的控制权。释放锁,简单来说就是删除 redis 中这个唯一的 key,但是一定要保证删除的这个 key 是该线程创建的,因而锁创建时必须携带执行线程的唯一特征以标示创建者的身份。

  如果加锁的线程出现异常 crash 了而不能及时删除锁,则会导致锁一直无法被正确释放,资源处于一直被占有,别的线程处于一直等待的状态。为了避免这样的情况发生,锁一定要在异常发生之后可以自己释放,以让出资源的控制权,可以使用 redis 的超时机制来达到这个目的。超时时间视不同的业务场景而定,一般是最大允许等待时间。需要注意的是,只有在加锁成功之后才可以对 key 设置 TTL,否则很容易导致 key 被多个线程不断设置 TTL 而无法过期。

  if CONN.setnx(lockname, identifier):

  CONN.expire(lockname, timeout)

  加锁之后如何有效监测锁是否被篡改?

  redis 提供了 pipeline 和事务操作来保证多个命令可以在一个事务内全部完成从而减少多次网络请求带来的开销,watch 命令又可以在事务开始执行之前对所要操作的 key 执行监测,从而保证了事务的完整性和一致性。因此,为了防止锁篡改,可以在加锁完成之后对锁进行 watch 操作,一旦锁发生变化,则终止事务,回滚操作。

  pipe = CONN.pipeline(True)

  pipe.watch(lock)

  提供锁的宿主机( redis 服务器) crash 导致锁不能被正确建立和释放该如何处理?

  不论是通信故障或是服务器故障而导致的锁服务器无法响应,此时都会导致客户端加锁和释放锁的请求无法完成,因此一定要有相应的应急处理,以确保程序流程的完整体验,加强客户端的健壮性。比如相应的超时提示,异常告警等。

  哪些边界需要注意

  1.只有锁正确释放才算是整个事务的完整结束,如果锁释放失败,比如被篡改、锁服务器异常等,不同的业务可以根据自己的需求进行变动和调整。

  2.设置 TTL 一定要是加锁成功之后,否则所有获取锁的客户端都会尝试 TTL 导致锁无法过期。

  3.锁的过期时间也就是获取锁的客户端的最大等待时间,这个时间根据执行的事务能够容忍的最长时间为限

  一个简单的 python 实现

  import time

  import redis

  import logging

  logger = logging.getLogger('service.redis_lock')

  CONN = redis.Redis(host='localhost')

  def acquire_lock(lockname, identifier, wait_time=20, timeout=15):

  end = time.time() + wait_time

  while end > time.time():

  if CONN.setnx(lockname, identifier):

  CONN.expire(lockname, timeout) # set expire time

  return identifier

  time.sleep(0.001) #wait until the lock expired or release by some thread

  return False

  def release_lock(lockname, identifier):

  pipe = CONN.pipeline(True)

  try:

  #watch lock once lock has been changed, break this transaction

  pipe.watch(lockname)

  #check if lock has been changed

  if pipe.get(lockname) == identifier:

  pipe.multi()

  pipe.delete(lockname)

  pipe.execute()

  return True

  pipe.unwatch() #execu when identifier not equal

  except redis.exceptions.WatchError as e:

  logger.error(e)

  return False

  except Exception as e:

  logger.error(e)

  return False

  return False

  if __name__ == '__main__':

  print release_lock('h', 'a')

  现在互联网公司面试的时候都会问到Redis,但是仅仅掌握以上所述是不够的,我们需要掌握更多的基础知识,这是我整理的一些需要掌握的知识技术点,分享给大家:

  需要思维导图格式的可以加群:810589193

  1.    高性能架构专题

  1.1.     分布式架构思维

    大型互联网架构演进过程

    架构师应具备的分布式知识

    主流分布式架构设计详解

  1.2.     Zookeeper分布式环境指挥官

    zookeeper基础

    zookeeper进阶

    zk的使用举例

  1.3.     Nginx高并发分流进阶实战

    Nginx模块简介

    Nginx工作原理及安装配置

    Nginx常用命令管理及升级

    Nginx配置文件精讲

    实战线上Nginx多站点配置

    Nginx配置优化及深入剖析

    Nginx Rewrite规则剖析

    Nginx日志分析及脚本编写

    Nginx日志切割案例讲解

  Nginx防盗链案例配置

  Nginx日常运维及故障解决

  Nginx构建安全架构实战

  企业实战Nginx+Tomcat动静分离架构实战

  Nginx+Keepalived集群架构实战

  Nginx+Keepalived双主架构案例实战

  1.4.     ActiveMq消息中间件

    消息中间件(ActiveMQ、RabbitMQ、Kafka)简介及对比

    软件下载、安装及部署

    P2P、PUB\SUB模型详解

    消息确认及重发机制

    企业级高可用集群部署方案

    ActiveMQ基于Spring完成分布式消息队列实战

  1.5.     RabbitMq消息中间件

    RabbitMQ及高可用集群部署

    深入学习RabbitMQ消息分发机制及主题消息分发

    RabbitMQ消息路由机制分析

    RabbitMQ消息确认机制分析

    RabbitMQ基于Spring完成分布式消息队列实战

    安装配置

    集群化与镜像队列

  1.6.     Kafka百万级吞实战

    基于ZooKeeper搭建高可用集群实战

    Kafka消息处理过程解析

    基于Java语言实现Kafka生产者与消费者实例

    Kafka副本机制及选举原理窥探

    使用Kafka实现日志实时上报统计分析实战

  1.7.     Memcached进阶实战

    概述

    开发基础

  1.8.     Redis高性能缓存数据库

    Redis初入门及介绍

    Redis主从模式

    Redis常用命令及应用场景

    Redis客户端

    Redis持久化

    哨兵核心机制

    高可用集群

    原子性

    应用场景代码开发与设计分析实战

  1.9.     MongoDB进阶实战

    mongodb入门

    mongodb进阶

    mongodb高级知识

    最佳实践与注意事项

  1.10. 高性能缓存开发实战

  缓存雪崩解决方案实战

  缓存粒度控制实战

  缓存击穿实战

  缓存热点KEY重建优化实战

  缓存同步实战

  Spring-Cache开发实战

  1.11. Mysql高性能存储实战

  Mysql

  Mycat

  1.12. FastDFS分布式文件存储实战

  文件存储实战

  文件同步实战

  文件查询实战

  分布式部署实战

  1.13. 高并发场景分布式解决方案实战

  分布式主键生成方案

  Session跨域共享实战

  分布式事务解决方案实战

  分布式锁解决方案实战

  分布式单点登录 SSO实战

  分布式调度任务实战

  分布式配置中心

  针对以上的技术点,有十余年Java经验系统架构师、项目经理录制了一些视频,用来回答这些技术。

  最后送福利了,现在加群:810589193免费可以获取Java工程化、高性能及分布式、高性能、高架构、性能调优、Spring、MyBatis、Netty源码分析等多个知识点高级进阶干货的相关视频资料,还有spring和虚拟机等书籍扫描版,还有更多面试题等你来拿

  分享给喜欢Java,喜欢编程,有梦想成为架构师的程序员们,希望能够帮助到你们

如何用 redis 造一把分布式锁  Python分布式计算 第1张

评论列表暂无评论
发表评论
微信