细说分布式锁

细说分布式锁实现 Redis分布式锁 获取锁 当一个进程前来请求分布式锁,首先通过 setnx 来尝试获取锁,如果获取锁成功,OK,设置好过期时间,可以返回去干自己事情了。如果 setnx 没获取成功,事情麻烦了,说明肯定有人拿到锁了。这个时候就要看看拿锁的进程是不是该让让位了——比较当前时间和锁里设置的过期时间(通过 get 查看),如果没到过期时间,好吧,自己就只好放弃或者继续等待了。如果发现当前时间已经超过过... 阅读详情

一、使用场景

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

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在分布式场景中就无能为力了。也就是说单纯的Java Api并不能提供分布式锁的能力。

分布式锁有以下几个特点:

  1. 互斥性:和我们本地锁一样互斥性是最基本的,但是分布式锁需要保证在不用节点的不同线程的互斥。
  2. 可重入性:同一个节点上的同一个线程如果获取锁之后那么也可以再次获取这个锁。
  3. 锁超时:和本地锁一样支持锁超时,防止死锁。
  4. 高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
  5. 支持阻塞和非阻塞:和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)。
  6. 支持公平锁和非公平锁(可选):公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。

常见的分布式锁:
我们一般实现分布式锁有以下几个方式:
1.MySQL
2.ZK
3.Redis
4.自研分布式锁,如谷歌的Chubby

二、mysql数据库的实现

2.1利用mysql的隔离性:唯一索引

创建一张锁表

CREATE TABLE `methodLock` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
  `desc` varchar(1024) NOT NULL DEFAULT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

当我们想要获取锁的时候,执行以下sql:

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)

因为我们对method_name字段做了唯一性约束,所以如果有多个插入操作,数据库只会保证一个成功其他的抛出异常,我们可以认为操作成功的那个线程获得锁,可以执行方法体的内容。
当我们想释放锁的时候,需要执行以下sql:

delete from methodLock where method_name ='method_name'

2.2基于数据库排他锁

还用刚刚创建的那张表,可以通过数据库的排他锁来实现分布式锁。

public boolean lock(){
    connection.setAutoCommit(false)
    while(true){
        try{
            result = select * from methodLock where method_name=xxx for update;
            if(result==null){
                return true;
            }
        }catch(Exception e){

        }
        sleep(1000);
    }
    return false;
}

在查询语句后面加for update,数据库会在查询过程中给数据库加排他锁(InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。
获得排他锁的线程即可获得分布式锁,再通过以下方法解锁。

public void unlock(){
    connection.commit();
}

2.3version乐观锁

乐观锁与前面最大的区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。一般抢购、秒杀就是用了这种实现以防止超卖。
通过增加递增的版本号字段实现乐观锁
select …,version
update table set vesion+1 where version=XX

优点:
直接借助数据库,实现简单。
缺点:
数据库是单点,不够可靠。
锁没有失效时间
影响数据库性能
使用数据库的行级锁不一定靠谱,尤其当锁表并不大的时候。

三、Redis的实现

Redis可以利用命令Setnx()来实现分布式锁,性能是最好的,但是可靠性没有zookeeper好,而且通过超时时间来控制锁的失效时间并不可靠。
还可以通过Lua脚本来释放锁,这种分布式锁在redis sentinel集群情况下并不靠谱。
具体可以参考文章https://www.cnblogs.com/demingblog/p/9542124.html#%E9%94%81%E8%B6%85%E6%97%B6

package cn.sp.lock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Created by 2YSP on 2019/1/26.
 */
@Component
public class RedisLock {

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 获取锁
     * @param lockName
     * @param requireTimeOut
     * @return
     */
    public String requireLock(String lockName,long requireTimeOut){
        String key = "lock:"+lockName;
        String identifier = UUID.randomUUID().toString();
        long end = System.currentTimeMillis() + requireTimeOut;
        while (System.currentTimeMillis() < end){
            Boolean success = redisTemplate.opsForValue().setIfAbsent(key, identifier);
            if (success){
                return identifier;
            }

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 释放锁
     * @param lockName
     * @param identifier
     * @return
     */
    public boolean releaseLock(String lockName,String identifier){
        String key = "lock:"+lockName;
        while (true){
            redisTemplate.watch(key);
            if (identifier.equals(redisTemplate.opsForValue().get(key))){
                //检查是否还未释放
                SessionCallback<Object> sessionCallback = new SessionCallback<Object>() {
                    @Nullable
                    @Override
                    public  Object execute(RedisOperations operations) throws DataAccessException {
                        operations.multi();
                        operations.delete(key);
                        List obj = operations.exec();
                        return obj;
                    }
                };
                Object object = redisTemplate.execute(sessionCallback);
                if (object != null) {
                    return true;
                }
                continue;
            }
            redisTemplate.unwatch();
            break;
        }
        return false;
    }

    /**
     *
     * @param lockName
     * @param requireTimeOut
     * @return
     */
    public String requireLockWithTimeOut(String lockName,long requireTimeOut,long lockTimeOut){
        String key = "lock:"+lockName;
        String identifier = UUID.randomUUID().toString();
        long end = System.currentTimeMillis() + requireTimeOut;
        int lockExpire = (int) (lockTimeOut/1000);
        while (System.currentTimeMillis() < end){
            Boolean success = redisTemplate.opsForValue().setIfAbsent(key, identifier);
            if (success){
                //设置过期时间
                redisTemplate.expire(key,lockExpire, TimeUnit.SECONDS);
                return identifier;
            }

            if (redisTemplate.getExpire(key,TimeUnit.SECONDS) == -1){
                redisTemplate.expire(key,lockExpire, TimeUnit.SECONDS);
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

四、zookeeper的实现

zookeeper实现分布式锁的原理主要是利用顺序临时节点的特性。
获取锁
所有客户端都试图创建同一个临时节点A,zookeeper会保证所有客户端中只有一个能创建成功,那么就可以认为该客户端获得了锁,其他客户端就要到临时节点A上注册一个子节点变更的Watcher监听。
释放锁
以下两种情况,都可能释放锁:

  1. 当前获取锁的客户端机器发生宕机,那么zookeeper上的这个临时节点就会被删除。
  2. 正常业务逻辑执行完后,客户端会主动将自己创建的临时节点删除。

无论什么情况移除了节点A,ZooKeeper都会通知所有在该节点上注册了子节点变更Watcher的客户端,这些客户端在接收到通知后,再次重新发起分布式锁获取。

我是使用的开源客户端Curator实现的
pom.xml

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>2.11.0</version>
		</dependency>
	</dependencies>

HelloController.java

package cn.sp.controller;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by 2YSP on 2019/1/27.
 */
@RestController
@RequestMapping("/zoo")
public class HelloController {

    public final Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    CuratorFramework client;

    @RequestMapping("/hello")
    public String hello(){
        final InterProcessMutex lock = new InterProcessMutex(client, "/lock");
        try {
            lock.acquire();
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
        log.info("{} 获取锁成功",Thread.currentThread().getName());
        System.out.println("执行业务逻辑。。。。");
        try {
            lock.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("{} 释放锁成功",Thread.currentThread().getName());
        return "OK";
    }
}

ZookeeperLockApplication.java

@SpringBootApplication
public class ZookeeperLockApplication {

	public static void main(String[] args) {
		SpringApplication.run(ZookeeperLockApplication.class, args);
	}

	@Bean
	public CuratorFramework client(){
		CuratorFramework client = CuratorFrameworkFactory.builder()
				.connectString("198.13.40.234:2181,198.13.40.234:2182,198.13.40.234:2183")
				.retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
		client.start();
		return client;
	}

}

然后使用ab测试访问即可。

注意:
Curator的版本要跟ZooKeeper的版本对应好,不然会报错。

Curator 2.x.x - compatible with both ZooKeeper 3.4.x and ZooKeeper 3.5.x

Curator 3.x.x - compatible only with ZooKeeper 3.5.x and includes support for new features such as dynamic reconfiguration, etc.

zookeeper有较好的性能和可靠性,但是性能不如Redis,主要原因是写操作(获取锁释放锁)都需要在Leader上进行,然后同步至follower。

五、总结

相关代码已上传至github,点击这里访问。

从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)Zookeeper >= 缓存 > 数据库
从性能角度(从高到低)缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库

【Java面试题汇总】JVM篇(2026版) JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优 阅读详情

相关推荐

CTransformers: 高效的Python语言模型绑定库

CTransformers是一个为C/C++实现的Transformer模型提供Python绑定的开源库。它基于GGML库构建,旨在为开发者提供一种高效且易用的方式来在Python中使用各种流行的语言模型。支持多种流行的语言模型,如GPT-2、LLaMA、Falcon等提供统一的简单API接口支持GPU加速(CUDA和Metal)与Hugging Face Transformers和LangChain集成支持流式输出和嵌入计算。

helloaiworld的博客 1267

通俗讲解分布式锁:场景和使用方法

点击关注公众号,实用技术文章及时了解前言对于锁大家肯定不会陌生,比如 synchronized 关键字 和 ReentrantLock 可重入锁,一般我们用其在多线程环境中控制对资源的并发...

Java知音 2114

ego-planner启动参数深度解析:从配置到实战避障应用

本文深入解析EGO-Planner启动参数的配置与实战应用,重点探讨无人机自主导航中的避障性能优化。通过环境感知、运动约束和算法控制三类参数的详细说明,结合ROS和RViz工具,提供从基础配置到高级调优的完整指南,帮助开发者提升无人机在复杂场景下的规划效率和安全性。

rum55的博客 246

分布式锁

分布式锁的常见实现方式:数据库 redis zookeeper etcd

wgwaaaa的博客 1128

PX4自定义uORB消息与MAVLink消息映射实战:从飞控到QGC地面站完整流程解析

本文详细解析PX4飞控中自定义uORB消息与MAVLink消息映射的完整流程,涵盖从飞控内部消息定义、MAVLink消息创建到QGC地面站接收显示的全链路实践。重点介绍如何使用mavlink inspector工具进行调试,解决版本兼容性和多实例消息处理等常见问题,为无人机开发者提供可靠的实时数据监控方案。

ll5678的博客 1067

从 SAP_FLP_USER 到项目落地:把 SAP S/4HANA 中的 SAP Fiori 授权体系真正讲透

SAP Fiori授权体系涉及前端访问、应用入口和后台业务对象三层权限,仅配置基础角色如SAP_FLP_USER无法满足实际业务需求。项目实践中常见用户能看到应用磁贴但无法访问数据的问题,多因授权模型设计不完整所致。SAP建议采用分层授权策略:基础层确保launchpad访问能力,业务层配置具体应用权限。管理员角色SAP_FLP_ADMIN需结合业务权限使用,测试时可临时配置S_SERVICE=*权限,但生产环境应遵循最小权限原则。完整的授权设计需参考SAP Fiori Apps Reference Lib

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 132

分布式锁特性

分布式锁

mywaya2333的博客 471

软考高级架构师笔记-10数学计算题

专栏《软考高级架构设计师》更新了,本章列举了10个场景计算题的解法,温故而知新,大家快来卷一卷

java后端开发的博客,不仅仅是后端开发 1598

分布式锁学习

一、分布式锁的特点 互斥性:分布式锁需要保证在不同节点的不同线程的互斥 可重入性:同一个节点上的同一个线程获取锁之后,也可以再次获取这个锁 锁超时:支持锁超时,防止死锁 高效、高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级 支持阻塞和非阻塞(可选):支持 lock 和 trylock 以及 tryLock(long timeOut) 支持公平锁和非公平锁(可选):公平锁指按照请求加锁的顺序获得锁,非公平锁则相反是无序获取锁 二、实现方式 1、基于 mysql 实现 基于

yin__ren的博客 1083

OpenStack ussuri 私有云平台搭建企业级实战

openstack是一个云操作系统,这个操作系统控制着数据中心中的计算,存储和网络资源。所有这些资源的管理都是通过API来来实现的,并且管理资源都有相应的认证机制。在openstack中有一个叫做dashboard的仪表盘,这个仪表盘通过web界面可以管理这些在DC中的资源。除了能提供IaaS功能外,你还可以使用orchestration,错误管理和服务管理等服务来确保应用的高可用性。

悦分享 2338

微信小程序:使用mqtt手机屏幕锁屏后导致中断问题,让mqtt在手机黑屏后依然能保持连接及持续后台运行接收或处理任务,详解微信小程序MQTT在应用退到后台或手机熄屏的情况下自动检测并重连【强制保活】

微信小程序切后台后MQTT连接无法维持,微信小程序MQTT一旦用户锁屏、切换到后台或手机熄屏,连接就会断开,导致无法继续监听和处理业务消息,微信小程序mqtt保活方案,微信小程序屏幕锁屏或息屏导致mqtt连接中断怎么办,如何解决MQTT不能黑屏持续连接的问题,微信小程序应用退到后台 MQTT连接断开问题解决,苹果ios手机,安卓手机,uniapp微信小程序mqtt断开怎么解决,后台应用怎么保活,uni-app中MQTT连接频繁断开如何解决?应用退到后台后MQTT连接断开以及常连接断连的问题,微信小程序、MQ

✅ 精选推荐文章 3026

Redis进阶-细说分布式锁

文章目录Pre引 Pre Redis Version : 5.0.3 Redis进阶-核心数据结构进阶实战 中我们讲 strings 数据结构的时候,举了一个例子 事实上,要实现一把相对完善的分布式锁,需要注意的细节还是蛮多的,这里我们好好的梳理一把。 引 我们先来看段代码 int stock = Integer.parseInt(stringRedisTemplate.opsForVa...

小工匠 2万+

YOLOV5 自动刷图脚本实战(十五)之如何找到正确的门

DXF是一个2D游戏,我们所看到的人物移动,其实是后面的背景在移动,我们所看到的画面,不过是一个完整背景中的一个镜头视角而已想要正确找到门的坐标,我们需要对不同的地图中的每个房间做一些配置信息。

qq_38892949的博客 2808

细说Redis分布式锁

谈起redis锁,下面三个,算是出现最多的高频词汇:setnxredLockredissonsetnx其实目前通常所说的setnx命令,并非单指redis的setnx key value...

dam454450872的博客 751

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Java锁、悲观乐观锁、分布式锁?细说那年我们用过的锁

Java锁、悲观乐观锁、分布式锁?细说那年我们用过的锁 一、概述 Java锁,指的是应用中使用的锁;应用中在处理线程安全的问题时,常常使用synchronized 或者ReentrantLock等锁来保证线程安全。 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。一...

feiyangtianyao的专栏 665

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

细说Redis 分布式锁

开发者(KaiFaX)面向全栈工程师的开发者专注于前端、Java/Python/Go/PHP的技术社区作者 | Vt来源 | juejin.im/post/5e61a454e51d452...

AI研习社 170
上一篇: Spring事件机制详解
下一篇: 【Netty】利用Netty实现心跳检测和重连机制
2YSP
博客等级 码龄10年 12粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值