分布式锁实现

news2024/11/28 18:35:56

分布式锁实现

  • 一 为什么要使用分布式锁
  • 二 分布式锁应该具备哪些条件
  • 三 分布式锁的三种实现方式
  • 四 基于数据库的实现方式
  • 五 基于Redis的实现方式

一 为什么要使用分布式锁

我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的锁进行处理,并且可以完美的运行,毫无Bug!

注意这是单机应用,后来业务发展,需要做集群,一个应用需要部署到几台机器上然后做负载均衡,大致如下图:
在这里插入图片描述
上图可以看到,变量A存在三个服务器内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象),如果不加任何控制的话,变量A同时都会在分配一块内存,三个请求发过来同时对这个变量操作,显然结果是不对的!即使不是同时发过来,三个请求分别操作三个不同内存区域的数据,变量A之间不存在共享,也不具有可见性,处理的结果也是不对的!

如果我们业务中确实存在这个场景的话,我们就需要一种方法解决这个问题!

为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用并发处理相关的功能进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的应用并不能提供分布式锁的能力。为了解决这个问题就需要一种跨机器的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

二 分布式锁应该具备哪些条件

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

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

三 分布式锁的三种实现方式

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

# 三种实现方式

基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;

四 基于数据库的实现方式

基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

(1)创建一个表:

DROP TABLE IF EXISTS `method_lock`;
CREATE TABLE `method_lock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',
  `desc` varchar(255) NOT NULL COMMENT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

(2)想要执行某个方法,就使用这个方法名向表中插入数据:

INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '测试的methodName');

因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。
(3)成功插入则获取锁,执行完成后删除对应的行数据释放锁:

delete from method_lock where method_name ='methodName';

注意:这只是使用基于数据库的一种方法,使用数据库实现分布式锁还有很多其他的玩法!

使用基于数据库的这种实现方式很简单,但是对于分布式锁应该具备的条件来说,它有一些问题需要解决及优化:

  1. 因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;
  2. 不具备可重入的特性,因为同一个线程在释放锁之前,行数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前机器和线程相同,若相同则直接获取锁;
  3. 没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;
  4. 不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。
  5. 在实施的过程中会遇到各种不同的问题,为了解决这些问题,实现方式将会越来越复杂;依赖数据库需要一定的资源开销,性能问题需要考虑。

五 基于Redis的实现方式

选用Redis实现分布式锁原因:

  • Redis有很高的性能;
  • Redis命令对此支持较好,实现起来比较方便

使用命令介绍:

  • SETNX:SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
  • expire:expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
  • delete:delete key:删除key

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

实现思想:

  1. 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
  2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
  3. 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

python实现redis分布式锁

pip3 install redlock-py

github地址:https://github.com/SPSCommerce/redlock-py

源码安装:

python3 setup.py install 
import redis
import uuid
import time

from threading import Thread, get_ident

# 连接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 = str(uuid.uuid4())
    end = time.time() + acquire_time
    lock = "string:lock:" + lock_name
    while time.time() < end:
        if redis_client.setnx(lock, identifier):
            # 给锁设置超时时间, 防止进程崩溃导致其他进程无法获取锁
            redis_client.expire(lock, time_out)
            return identifier
        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:
            pip.watch(lock)
            lock_value = redis_client.get(lock)
            if not lock_value:
                return True

            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(get_ident(), "获得了锁")
    release_lock('resource', identifier)
    print(get_ident(), "释放了锁")


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142683.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HCIE-Security:顺利通过,备考心得

备考半年多&#xff0c;终于通过华为HCIE安全&#xff0c;今天把心得贴出来&#xff0c;供大家参考。 我是4月1日开始在机构开始学习安全IE的&#xff0c;报名之后从IA开始学习&#xff0c;学习期间也算勤勤恳恳&#xff0c;每次上课都进行预习和复习&#xff0c;形成自己的笔记…

论文投稿指南——中文核心期刊推荐(大气科学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

2023-1-5 javaScript

JavaScript基础 javaScript 概念 概念&#xff1a;一门客户端脚本语言 脚本语言&#xff1a;不需要编译&#xff0c;直接就可以被浏览器解析执行了 功能&#xff1a;可以增强用户和heml页面交互的过程可以控制html元素&#xff0c;让页面有一些动态的效果&#xff0c;增强用…

[AHK]腾讯实时股票数据接口

腾讯财经接口获取最新行情以五粮液为例&#xff0c;要获取最新行情&#xff0c;访问数据接口&#xff1a;qt.gtimg.cn/qsz000858返回数据&#xff1a;v_sz000858"51~五 粮 液~000858~27.78~27.60~27.70~417909~190109~227800~27.78~492~27.77~332~27.76~202~27.75~334~27.…

[ERROR] Malformed \uxxxx encoding.报错解决

今天用idea运行完项目。想直接打包的时候&#xff0c;结果打包失败&#xff0c;一直报错 [ERROR] Malformed \uxxxx encoding. 网上查了之后&#xff0c;一直说是&#xff0c;有路径在使用斜杠的时候&#xff0c;使用错误。将"\“换成”/“就好了&#xff0c;但是我配置文…

pb将字符串中的中文和英文(含符号)拆分

//用于将字符串中的中文和英文(含符号)拆分 //uf_split_str_enorcn(as_inputstr) //as_inputstr:导入字符串 long i, li_len, li_lenA as_return_cn = as_return_en = if as_inputstr > then li_len = len(as_inputstr) //带中文长度 li_lenA = lenA…

ICMP隧道-调研笔记

ICMP隧道通信原理与通信特征 https://baijiahao.baidu.com/s?id1652047934643855432&wfrspider&forpc 1.一个正常的 ping每秒最多只会发送两个数据包&#xff0c;而使用ICMP隧道的浏览器在同一时间会产生大量ICMP 数据包 2.ICMP隧道数据包中DATA 往往大于64 比特 3.正…

TC275-11CCU6_PWM_Generation

基础知识 CCU6&#xff0c;Capture/Compare Unit 6捕获/比较单元&#xff0c;是一个专门用于电机控制而设计的16位捕获和比较单元。 CCU6包含多个定时器&#xff0c;将它们的计数值和参考值进行比较&#xff0c;来生成PWM信号。 定时器12&#xff08;T12&#xff09;配有三个…

Java并发编程(三)

临界区 临界资源&#xff1a;一次仅允许一个进程使用的资源成为临界资源 临界区&#xff1a;访问临界资源的代码块 竞态条件&#xff1a;多个线程在临界区内执行&#xff0c;由于代码的执行序列不同而导致结果无法预测&#xff0c;称之为发生了竞态条件 一个程序运行多个线…

日志框架之TLog讲解分析

文章目录1 TLog1.1 引言1.2 简介1.3 TLog操作1.3.1 pom.xml1.3.2 替换logback配置项1.3.3 测试1.4 TLog接入方式1.5 TLog的基本原理1.5.1 日志标签1.5.2 TLogContext1.5.3 TLogRPCHandler1.6 第三方框架的适配1.6.1 异步线程1.6.1.1 一般异步线程1.6.1.2 线程池1.6.2 对RPC框架…

应用程序性能瓶颈中的CPU缓存优化

1.前言 在应用程序中会有大量的对变量的操作&#xff0c;在一般情况下不会导致问题&#xff0c;但在多线程操作共享变量时&#xff0c;不当的操作会产生大量的冗余操作&#xff0c;造成性能的浪费。这篇文章主要从编码方式与逻辑策略对变量从CPU寄存器&#xff0c;CPU缓存&…

Redis面试题整理

认识Redis 什么是Redis? 一种基于内存的数据库&#xff1b;在内存中完成对数据的读写操作&#xff1b;读写速度非常快&#xff1b;常用于缓存&#xff0c;消息队列&#xff0c;分布式锁等场景 Redis和Memcached有什么区别&#xff1f; 共同点 都是基于内存的数据库&#x…

PaddleNLP系列课程二:RocketQA、SKEP(属性级情感分析)、通用信息抽取技术UIE

文章目录一、使用RocketQA搭建端到端的问答系统1.1 问答系统介绍1.2 RocketQA1.2.1 检索式QA VS预训练时代QA1.2.2 RocketQA简介1.3 使用RocketQA搭建问答系统1.3.1 安装1.3.2 使用预置模型完成预测1.3.3 搭建问答系统1.3.3.1 使用Faiss搭建自己的问答系统1.3.3.2 使用Jina搭建…

Leecode---141、142环形链表

141 难度 &#xff1a; easy 个人主要思路是&#xff0c; 循环遍历每个节点&#xff0c; 判断该节点此前是否被访问过。 方法一&#xff1a; 时间8ms &#xff0c; 内存 6.8M , func hasCycle(head *ListNode) bool {var val map[*ListNode]*ListNode{}if head nil {return …

l2逐笔接口数据传输延时高吗?

l2逐笔接口数据传输延时高吗&#xff1f;信息服务商的机器部署在交易所机房内&#xff0c;并通过接口直接向用户转发。按照交易所的规定&#xff0c;每个接收用户均需支付成本十几万&#xff0c;使用l2逐笔接口数据做量化是需要一定门槛。但用户端SDK直连的方式&#xff0c;能最…

C++学习 Day.9(宏和模板简介)

好久没更了&#xff0c;摆还是爽 遗留问题&#xff1a; (16条消息) int&作为函数返回类型-编程语言-CSDN问答&#xff08;已解决&#xff09; 宏&#xff1a; 预处理器编译指令都以#打头 #define&#xff08;宏常量&#xff09;使得预处理器进行文本替换&#xff0c;而不…

Acwing---795.前缀和

前缀和1.题目2.基本思想3.代码实现4.总结1.题目 输入一个长度为n的整数序列。 接下来再输入m个询问&#xff0c;每个询问输入一对l&#xff0c;r。 对于每个询问&#xff0c;输出原序列中从第l个数到第 r 个数的和。 输入格式 第一行包含两个整数n和m。 第二行包含n个整数&am…

一种简洁又不失优雅的工作流:极狐 flow

本文来自&#xff1a; 万金 极狐(GitLab)解决方案专家 杨周 极狐(GitLab) 高级解决方案架构师 极狐(GitLab) 市场部内容团队 我们提到的 Workflow 是指什么&#xff1f; 我们在日常开发工作中提到的 Workflow 通常是指通过 Git&#xff08;版本控制工具&#xff09;实现的分布式…

JavaSE学习day1_03, Java的发展

5. Java语言的扩展知识,重点 5.1 Java语言的发展 java语言前身是oka语言. JDK5&#xff1a;第一个大版本号更新 JDK8&#xff1a;企业中最常用的版本 JDK17&#xff1a;课程中学习的版本 特点&#xff1a;兼容性。 用jdk8编写的代码&#xff0c;用17可以运行 用jdk17编写…