Distributed 分布式lock

分布式锁

为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用并发处理相关的功能进行互斥控制。

但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的应用并不能提供分布式锁的能力。

解决这个问题就需要一种跨机器的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

如下图所示:

image-20210410174152461

设计思想

在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:

  1. 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
  2. 高可用的获取锁与释放锁;
  3. 高性能的获取锁与释放锁;
  4. 具备可重入特性;
  5. 具备锁失效机制,防止死锁;
  6. 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

Redis实现

其实在MySQL中,本身的乐观锁、悲观锁等已经具有分布式锁的功能,但是效率堪忧。

Redis实现分布式锁是最简单通用的一种方式,它有以下2个优点:

  1. 由于Redis具有很高的性能,所以获得锁与释放锁的操作会更加迅速
  2. 减轻MySQL的压力,MySQL不再关心锁的问题,更加全力的应对数据的变更

在介绍Redis实现分布式锁之前,先回忆一下它的几个方法:

  • setnx:仅当key不存在的时候创建该key,意味只负责创建,不负责更新

  • expire:为key设置一个超时时间,超时后自动删除该key,单位为秒

  • delete:删除key

实现思路如下:

1)当某一个request需要操纵MySQL时,会先经过Redis通过setnx获得一把锁,锁的key值固定,value值随机,如uuid等均可。

2)使用expire为该锁加上一个超时时间,超过该时间时自动释放锁,避免死锁情况的发生

3)request在获取锁的时候,也需要加入一个超时时间,若在限定时间内该request没获得锁,则放弃获取

4)释放锁的时候,通过uuid判断是不是自己的锁,若是自己锁则使用delete进行锁的释放

实现代码

实现代码以Python举例:

#! /usr/local/bin/python3
# -*- coding:utf-8 -*-

import redis
import time
import uuid
from threading import Thread, current_thread

# 连接redis
redis_client = redis.Redis(host="localhost",
                           port=6379,
                           password="password",
                           db=10)


# 获取一个锁
# lock_name:锁定名称
# acquire_time: 客户端等待获取锁的时间
# time_out: 锁的超时时间
def acquire_lock(lock_name, acquire_time=10, time_out=10):
    """获取一个分布式锁"""

    # identifier:redis中key的value
    identifier = str(uuid.uuid4())

    # end:获得锁的超时时间
    end = time.time() + acquire_time

    # lock: redis中的key
    lock = "string:lock:" + lock_name

    # 死循环检测,是否超过时间
    while time.time() < end:
        # 如果设置成功,则代表获得锁,反之则代表没有获得锁,继续向下走、或者循环获取锁
        # 直到获得锁的时间限制过期
        if redis_client.setnx(lock, identifier):
            # 给锁设置超时时间, 防止进程崩溃导致其他进程无法获取锁
            redis_client.expire(lock, time_out)
            # 返回锁的value
            return identifier

        # --- 已被其他人锁定 ---
        # not ttl真 -> False
        # not ttl假 -> True

        # 这里的意思是说,判断别人的那把锁有没有设置过期时间
        # 如果没有则给他设置一个
        elif not redis_client.ttl(lock):
            redis_client.expire(lock, time_out)
        time.sleep(0.001)
    return False


# 释放一个锁
def release_lock(lock_name, identifier):
    """通用的锁释放函数"""

    lock = "string:lock:" + lock_name

    # 开启管道,用于提交事务
    pip = redis_client.pipeline(True)
    while True:
        try:
            # 监听锁的key,不允许key再次发生变更
            pip.watch(lock)
            # 获得锁
            lock_value = redis_client.get(lock)
            # 如果获取不到,则证明超时了,自动释放了
            if not lock_value:
                return True

            # 查看是不是自己的锁,Redis数据拿出来或转码对比自己锁的value
            if lock_value.decode() == identifier:
                # 开启事务块,直到释放锁
                pip.multi()
                pip.delete(lock)
                pip.execute()
                return True
            # 取消监听
            pip.unwatch()
            break
        except redis.excetions.WacthcError:
            pass
    return False


# 测试案例
def seckill():
    identifier = acquire_lock('resource')
    print(current_thread().getName(), "获得了锁")
    release_lock('resource', identifier)


if __name__ == '__main__':
    for i in range(50):
        t = Thread(target=seckill)
        t.start()

官方锁

Redis官方也提供了分布式锁,使用较为简单。

但是这个分布式锁并未放在pypi社区中,其他语言暂时不知,可以访问下面的链接进行获取:

如果你使用Python,则下载下来通过以下方式安装即可:

# cd到下载的目录中

python setup.py build
python setup.py install
posted @ 2021-04-10 18:49  云崖君  阅读(581)  评论(0编辑  收藏  举报