Redis分布式锁实现OneByOne组件

VSCode 1.85便携版安装与配置:解决远程服务器glibc版本过低报错的保姆级指南 本文详细介绍了如何通过VSCode 1.85便携版解决远程服务器glibc版本过低导致的兼容性问题。通过环境隔离和版本固化的方法,开发者可以在老旧服务器上稳定运行Remote-SSH插件,同时保留主力VSCode的配置。文章提供了从下载安装到深度配置的完整指南,帮助用户快速搭建稳定的开发环境。 阅读详情

背景

随着互联网项目的访问量增大,对系统的要求越来越高。应运而生出分布式系统,高可用集群等技术。而且已经非常成熟,在公司里面访问量再小的应用标配都是2台集群,单机应用已经一去不复返了。
java的多线程加锁的方式已经没有办法支撑这种分布式应用,因为java的线程锁,只起作用于当前运行的JVM中,多个JVM之间是相互分隔,无法控制的。

实现方案

核心就是将锁置于一个集中式的地方管理,让锁只有一把,所有集群应用争夺的就是这把锁。
因为是集中式访问,所以要求的性能较高-轻快,且要保证高可用-稳定:业内选择有比较多,zookeeper,redis等等,早期还有用DB(淘汰了)。
这边介绍下当前项目中设计的利用redis实现的。

实现要点

  1. 利用redis的setNX命令的原子性操作来进行加锁。如果单纯取锁,判断,再加锁存在不原子的情况,极端情况下会产生并发。
  2. 利用redis的lua脚本来释放锁,同样保证原子性
  3. 保证释放锁的动作,必须是当前锁的拥有者
  4. OneByOne阻塞等待:在现实应用中,尽可能的在防止业务并发的情况,保证业务的成功,所以排他锁(获取不到锁就失败)这种场景反而不是最多的。最多的是OneByOne,将并发转串行,尽量等待前一个业务完成之后,后一个业务能继续执行,而不是直接失败。 那势必需要设计一个阻塞的机制,这个阻塞等待并不像需要流量消峰那样用队列来做,只要释放可以有多个竞争者抢锁,抢到就继续执行。

核心代码

OneByOneTemplate模板实现

加锁,释放锁排队等等都是一个标准的流程,所以可以建立一个模板。调用者只要关注加锁代码块的业务逻辑实现。

模板

提供两个方法,一个是默认超时时间及排队时间。另一个可以指定锁的超时时间以及排队等待锁的时间。

定义模板接口
public interface OneByOneTemplate {
    <T> T execute(OneByOne oneByOne, CallBack<T> callBack);

    <T> T execute(OneByOne oneByOne, boolean waitInQueue, int timeoutMsecs, int expireMsecs, CallBack<T> callBack);
}
定义模板回调接口

业务实现逻辑,实现回调接口,并且实现回调方法即可

public interface CallBack<T> {
    T invoke();
}
具体模板实现
public class OneByOneTemplateImpl implements OneByOneTemplate {

    private static final int DEFAULT_TIME_OUT_MSECS = 10000;

    private static final int DEFAULT_EXPIRE_MSECS = 30000;

    @Override
    public <T> T execute(OneByOne oneByOne, CallBack<T> callBack) {
        return execute(oneByOne, Boolean.TRUE, DEFAULT_TIME_OUT_MSECS, DEFAULT_EXPIRE_MSECS, callBack);
    }

    @Override
    public <T> T execute(OneByOne oneByOne, boolean waitInQueue, int timeoutMsecs, int expireMsecs, CallBack<T> callBack) {
        // 需要排队
        if (waitInQueue) {
            // 当参数timeoutMsecs取值小于等于零时,则使用默认的排队10秒
            if (timeoutMsecs <= 0) {
                timeoutMsecs = DEFAULT_TIME_OUT_MSECS;
            } else {
                timeoutMsecs = 0;
            }

            // 不需要排队
        } else {
            timeoutMsecs = 0;
        }

        // 当参数expireMsecs取值小于等于零时,则使用默认的有效期30秒
        if (expireMsecs <= 0) {
            expireMsecs = DEFAULT_EXPIRE_MSECS;
        } 

        return invoke(oneByOne, timeoutMsecs, expireMsecs, callBack);
    }

    private <T> T invoke(OneByOne oneByOne, int timeoutMsecs, int expireMsecs, CallBack<T> callBack) {
        final String key = RedisCacheKeyConstants.REDIS_ONE_BY_ONE + oneByOne.getBizType() + "_" + oneByOne.getBizId();
        SedisLock sedisLock = new SedisLock(RedisUtil.redisClient, key, timeoutMsecs, expireMsecs);
        try {
            if (sedisLock.acquire()) { // 启用锁
                return callBack.invoke();
            } else {
                throw new AppException("bizType:" + oneByOne.getBizType() + ",bizId:" + oneByOne.getBizId() + ",并发执行!");
            }
        } catch (InterruptedException e) {
            throw new AppException("");
        } finally {
            sedisLock.release();
        }
    }

}

锁实现

声明redis锁的类,这段代码是在上面模板里面。这边列出来主要是为了控制释放锁时的owner

SedisLock sedisLock = new SedisLock(RedisUtil.redisClient, key, timeoutMsecs, expireMsecs);
加锁

加锁过程中会进行锁等待排队,利用的是轮询+wait()。
redis中存储的这个锁的key,并且要存放这个对象的随机属性,owner属性可以是一个UUID。

public synchronized boolean acquire() throws InterruptedException {
    int timeout = timeoutMsecs;
    while (timeout >= 0) {
        if ("OK".equals(redisClient.execute(new ShardedJedisAction<String>() {
            @Override
            public String doAction(ShardedJedis jedis) {
                Jedis j = jedis.getShard(lockKey);
                // redis中不存在就设置lockKey对应的值,同时设置毫秒级过期时间
                return j.set(lockKey, owner, "NX", "PX", expireMsecs);
            }
        }))) {
            // lock acquired
            locked = true;
            return true;
        }

        int spinWatingTime = random.nextInt(200) + 1;
        timeout -= spinWatingTime;
        wait(spinWatingTime);
    }
    return false;
}

轮询200内的随机毫秒进行一次尝试获取锁,并且排队时间减去相应的时间,一直等待时间小于0则不再尝试。
这边random.nextInt(200)获取的值是0-199,容易忽略会导致wait(0),造成线程长期占用。所以需要+1

锁释放

需要校验这个锁是否加锁状态,并且判断是否是锁拥有者
利用redis支持的LUA脚本来将get和del两个动作做成原子性

public void release() {
    if (locked) {
        // 判断锁拥有者和释放锁
        final String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        Object result = redisClient.execute(new ShardedJedisAction<Object>() {
            //
            @Override
            public Object doAction(ShardedJedis jedis) {
                List<String> keysList = Collections.singletonList(lockKey);
                List<String> argsList = Collections.singletonList(owner);
                Jedis jedisKey = jedis.getShard(lockKey);
                return jedisKey.eval(script, keysList, argsList);
            }
        });
        if (1 == (Long) result) {
            locked = false;
        }
    }
}

这样整体上一个OneByOne的分布式防并发锁就完成了。性能方面基于redis的高性能读写来说还是比较好的。

优点

  • 基于redis的锁机制,读写性能较高,提高系统的性能。
  • redis的原子性的特性的保证,为高并发下的锁处理变的稳定且简洁
  • 锁等待的设计能很好的提升业务的成功率,超时时间和等待时间设置合适的时间,可以达到很好的吞吐量和成功率。
线程池详解(通俗易懂超级好) 目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础 线程池使用 线程池综合案例 4.学员练习 5.线程池总结 概念介绍 1:什么是线程池 2:为什么使用线程池 3:线程池有哪些优势 什么是池 什么是线程池 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runna. 阅读详情

相关推荐

[ESP32S3N16R8][LVGL8.3.0][IDF5.2.3]ST7701S RGB屏幕驱动[vscode最详细配置]

ST7701S ESP32S3 基于ESP-IDF5.2.3编写,编程环境为vscode。本教程详细讲述了esp32s3配置st7701s屏幕驱动以及gt911触摸驱动并关联到lvgl8.3.0,使用IDF5.23示例rgb_panel进行测试删除了其里面的屏幕驱动,改为单独编写屏幕和触摸驱动,最后完成了实列。

qq_44022379的博客 2604

java多线程--简易使用同步锁实现一对一交替打印

一、本例需要分析的地方不多,只需要使用一个同步锁+一个计数器就能搞定,直接奉送源码吧: package com.example.liuxiaobing.statemodel.mutil_thread.onebyoneprint; /** * Created by liuxiaobing * Date on 2018/9/23 * Copyright 2013 - 2018 Qian...

刘胡来 2万+

AI 编程助手新玩法:用 Claude Code 一键调用国产大模型(DeepSeek/Kimi/GLM 对比)

本文深入评测了Claude Code与三大国产大模型(DeepSeek、Kimi、智谱GLM)的集成效果,详细对比了它们在代码生成质量、响应速度和特殊场景支持度等方面的差异。通过实测数据和实战技巧,帮助开发者选择最适合的AI编程助手组合,提升开发效率。

weixin_30955617的博客 485

java学习笔记(7)

java中输入输出流主要由四个抽象类表示:字节流:InputStream、OutputStream  字符流:Reader、Writer 根据功能层次的不同:分为节点流和处理流 Example: package com.test; import java.io.*; public class streamtest { public static void main(String[...

yjli98的博客 212

电流模架构Bandgap设计与仿真

带隙基准作为集成电路中一个重要模块,被广泛应用在低压差线性稳压(LDO)、充电电池保护芯片和通信电路、射频收发器、flash存储器等多种模拟及数模混合集成电路中,并且是片上集成系统(SOC)芯片中不可或缺的部分,为整个芯片提供精确的电压参考点............

qq_50968231的博客 5万+

Android 埋点信息分析——statsd框架

StatsPullAtomService 用以采集数据,并将数据填充到参数中,在StatsManager.PullAtomCallbackInternal 中会转换成 Parcel 格式传入到 native。 StatsCallbackPuller::PullInternal 中会回调StatsManager.PullAtomCallbackInternal 的onPullAtom,该函数中会回调StatsCallbackPuller::PullInternal 中定义的PullResultRecei

私房菜 2626

使用redis 实现分布式锁,处理并发问题

处理逻辑,为了防止同一个订单,产生并发的问题,这里设置了一个锁,向redis 中加入以订单号为key的键值对, 每次执行订单处理时,会先判断redis 缓存中是否有这个key, 已存在的话,就挂起一段之间,重试5次, 如果在业务逻辑处理完,会删除redis 中的关于该订单的数据 import java.util.ArrayList; import java.util.List; impo

Saltwater_leo的专栏 4207

数据库课程案例——电力公司收费管理系统分析与实践,完整带SQL语句

某电力公司收费管理信息系统实现客户信息、用电类型(类别号、类别名、电价)及业务员管理;实现客户用电信息管理(客户号、月份、用电类别号、用电度数);实现客户费用管理(客户号、月份、费用、收费标志),收费标志的默认值为“未收”;实现收费登记(客户、月份、应收费用、实收费用、业务员),并自动修改收费标志(用触发器实现);创建触发器,实现收费时自动更加应收费用和实收费用,计算本次结余,然后修改客户信息表中的结余金额;创建存储过程统计指定月份应收费用和实收费用;创建存储过程查询指定月份未交费的用户信息,以便崔费;创建

qq_44242804的博客 2761

Redis分布式锁-这一篇全了解(Redission实现分布式锁完美方案)

在某些场景中,多个进程必须以互斥的方式独占共享资源,这时用分布式锁是最直接有效的。随着技术快速发展,数据规模增大,分布式系统越来越普及,一个应用往往会部署在多台机器上(多节点),在有些场景中,为了保证数据不重复,要求在同一时刻,同一任务只在一个节点上运行,即保证某一方法同一时刻只能被一个线程执行。

yifeiliu338的博客 2526

Microsoft Bitlocker企业级管理部署方案

BitLocker是Windows 10以及更高版本中内置的磁盘加密工具,是一种可靠的数据保护机制,可以保护计算机上的敏感数据不被未经授权的人访问。BitLocker的工作原理是将整个系统卷或其他数据卷加密的同时,向某个可信软件或硬件模块请求密钥解密数据。BitLocker最大的好处在于其内置于Windows系统,所以用户无需安装其他软件或购买额外的许可证或硬件设备。本文将介绍Microsoft BitLocker企业级管理部署方案,包括:BitLocker部署前的准备工作。

xueyunshengling的博客 2073

Winform从入门到精通(2)——Button(史上最全)

本文介绍winform中Button控件的详细用法,包括所有属性和事件

qq_34059233的博客 1万+

Redis(九) - Redis之分布式锁

Redis(九) - Redis之分布式锁

及时当勉励,岁月不待人 1万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

redis7高级篇-基于redission在多机模式下实现分布式锁

其实只要客户端A一旦加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间。紧接着就会发送一段lua脚本到redis上,比如加锁的那个锁key就是"mylock",并且设置的时间是30秒,30秒后,mylock锁就会被释放。只要线程一加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。

健康平安的活着的专栏 4556

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

随手记录4:spring上下文ApplicationContext 两种获取方式和使用获取普通对象和FactoryBean的不同;

随手记录4:spring上下文ApplicationContext 获取和使用;简介关于ApplicationContext 获取关于ApplicationContext 使用1.UserService .java2.FactoryBeanConfig .java结论 简介 项目中基本上好多对象都是交给spring管理的; 但是有些静态的工具类中想获取该对象又不能通过 @Autowired直接注入; 或者有些方法里面需要使用到spring托管的对象; 可以通过ApplicationContext 直接获

weixin_43812522的博客 744

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Redis实现分布式锁

分布式锁及实现方案

4万+

[ttserver]一、ttserver的简单使用

1. 准备工作 检查三个编译工具是否存在,若不存在则直接安装 # yum install bzip2-devel # yum install zlib-devel # yum install gcc 2. download相关文件 Tokyocabinet: # wget http://tokyocabinet.sourceforge.net/tokyocabinet-1.4.27....

OneByOne 1896

cocos2dx之event事件(三):事件分发器EventDispatcher

@class EventDispatcher @brief This class manages event listener subscriptions and event dispatching. 首先上一段EventDispatcher的官方注释,解释了EventDispatcher的作用是管理listener和做事件分发. 进一步理解,EventDispatcher主要...

ellis1970的博客 1807

Redis健操作命令03

查找键,参数支持正则 127.0.0.1:6379> keys * 1) "abcd" 2) "a" 3) "abc" 127.0.0.1:6379> 判断键是否存在,如果存在返回1,不存在返回0 127.0.0.1:6379> exists 'ddd' (integer) 0 127.0.0.1:6379> 查看键对应的value的类型 127.0.0.1:6379> type 'a' string 127.0.0.1:6379> 删除键及对应的值 127.0.

dingpython的博客 192

【Redis】1.Redis入门简介以及基本操作命令

点击跳转

码农01号的博客 227

T项目服务器架构分析

tips. 图为T项目服务器架构分析图,其中world,game, proxy都是可扩展的。 一、服务器概述 1. proxy : 代理服务器 Game的代理服务器,和客户端client直接相连,类似于我们常说的&quot;负载均衡&quot;,每个proxy是和所有game都有连接的。 作用 : 1. 不需要将game、world设置外网ip,暴露在网络中,防止攻击,起到单点对外防火墙的作用。 ...

OneByOne 1027

多进程log4cxx区分日志

多进程log4cxx区分日志(金庆的专栏)网游客户端一般会多开,多个进程会写同一个日志文件。log4cxx看来会对文件加锁,防止多进程写同一文件写乱,截止目前还没发现错乱的日志。

金庆的专栏 4165

linux 磁盘利用率过高

线上服务器磁盘利用率过高,则删除一些不用的大文件。

OneByOne 8728

java多人聊天室

服务器 public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("聊天室创建成功"); Socket accept; ServerThread thread; while

ayou2020的博客 329

什么是分布式锁?几种分布式锁分别是怎么实现的?

一、什么是分布式锁: 1、什么是分布式锁: 分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。 2、分布式锁应该具备哪些条件: 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行 高可用的获取锁与释放锁 高性能的获取锁与释放锁 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误) ...

m0_67645544的博客 6万+

Lock锁及获取锁的四种方法

Lock锁

是小D吖!的博客 2万+

利用redis实现分布式锁(二) ——具体实现方式一

上一篇介绍了分布式锁的概念、作用、基本原理(http://guwq2014.iteye.com/blog/2365658), 这一篇看看如何使用redis实现一个分布式锁:   第一步:分布式锁实现类:  import redis.clients.jedis.ShardedJedis; import com.suning.framework.sedis.ShardedJedisA...

iteye_7682的博客 542
上一篇: 进制换算
下一篇: ReentrantLock公平锁的实现原理
yanbincn
博客等级 码龄16年 36粉丝 97原创
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值