【JAVA高级】 redis分布式双重加锁(业务校验:防止接口并发调用时数据重复)

news2024/9/27 5:45:00

文章目录

    • 此问题的考虑思路
    • 使用Redis的key-value锁的基本思路
    • 结合Redis数据结构实现避免重复
    • 注意事项
    • 实现代码
      • 只避免 name和age的重复
      • 避免 name和age的和age和sex重复:使用双重的分布式锁实现:

在这里插入图片描述

背景:在日常开发过程中,遇到了一个需求,比如有一个对象User(name,age、sex)有三个属性,现在需要用户新增接口中,防止此接口被多人同时请求访问,产生了姓名&年龄相同的,还有年龄&性别相同的数据;

此问题的考虑思路

如果一个线程调用用户新增接口的时候,在业务中通过查询数据库中是否已有相关数据,从而不抛出异常提示,不让做保存到数据库的操作;这种考虑是我们最常见的考虑内容。还有个问题,如果是外部系统,涉及的操作并发量特别的大,那调用这个接口的并发量也很大的话,单纯在通过校验库中是否有重复的数据防止重复数据插入只能阻止一部分问题数据的入库。如果同时有两个用户甲乙,填写的姓名和年龄
(年龄和性别是同样的考虑方法);此时从库中查了,没有已有的数据,此时为了防止这两个用户甲乙操作的重复数据同时入库的情况,我们就得加上一个分布式锁了(如果在单体应用中可以使用synchronized),分布式架构中需要使用Redis分布式锁或者Redission分布式锁来实现相应的控制了;

在设计分布式Redis锁以避免在新增User时出现同name和age组合,或者同age和sex组合的情况,你需要构建一个能够唯一标识这些条件的key。由于Redis锁通常用于确保操作的原子性,而你的需求是检查并避免重复数据,这里实际上可能更偏向于使用Redis的其它数据结构(如集合、有序集合或哈希表)来辅助实现,而不是仅仅使用单独的key-value锁

使用Redis的key-value锁的基本思路

**1.定义锁的key:**锁的key应该能够唯一标识你想要保护的资源或操作。在你的场景中,由于涉及到多个字段的组合检查,你可以考虑将这些
字段组合成一个字符串作为key。例如:

  • 对于name和age的组合,可以使用user🔒name:{name}:age:{age}。
  • 对于age和sex的组合,可以使用user🔒age:{age}:sex:{sex}。
    **2.设置锁:**在尝试新增User之前,先尝试设置这个锁。如果锁设置成功(即没有其他进程或线程持有这个锁),则继续执行检查逻辑。
    **3.检查并插入:**在锁的保护下,检查数据库中是否已经存在具有相同name和age或age和sex组合的User。如果不存在,则执行插入操作。
    **4.释放锁:**无论操作成功还是失败,最后都要释放锁,以便其他进程或线程可以获取锁并执行操作。

结合Redis数据结构实现避免重复

然而,更有效的方法可能是使用Redis的集合(Set)或有序集合(Sorted Set)来存储已经存在的组合,并检查新组合是否已存在。

1.使用集合:

  • 对于name和age的组合,可以创建一个集合user:name_age,其中每个元素都是{name}:{age}的字符串。
  • 对于age和sex的组合,可以创建另一个集合user:age_sex,其中每个元素都是{age}:{sex}的字符串。
  • 在新增User时,先检查相应的集合中是否已经存在该组合。如果不存在,则添加到集合中,并执行数据库插入操作。
    2.使用有序集合(如果需要按某种顺序排序):
  • 类似于集合,但你可以为元素指定一个分数(score),以便按特定顺序存储和检索元素。

注意事项

**性能考虑:**随着集合中元素的增加,检查操作可能会变慢。因此,你可能需要考虑使用哈希表或其他数据结构来优化查找性能。
**事务性:**确保检查集合和插入数据库的操作是原子性的,以防止在检查之后但在插入之前发生数据变化。
**锁的超时:**设置锁的超时时间以防止死锁。
**锁的粒度:**根据你的应用场景,你可能需要调整锁的粒度。例如,如果操作非常频繁,并且可以接受一定程度的重复检查,则可以考虑放宽锁的粒度或使用更轻量级的同步机制。

实现代码

只避免 name和age的重复

下面的实现的一些代码:希望能帮到大家理解思路。

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.data.redis.core.ValueOperations;  
import org.springframework.stereotype.Service;  
  
import java.util.concurrent.TimeUnit;  
  
@Service  
public class UserService {  
  
    @Autowired  
    private RedisTemplate<String, String> redisTemplate;  
  
    // 假设的锁过期时间  
    private static final long LOCK_EXPIRATION_TIME = 10L; // 10秒  
  
    // 尝试获取锁  
    private boolean tryLock(String key) {  
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();  
        // 尝试设置锁,如果键不存在则设置成功,并设置过期时间  
        return opsForValue.setIfAbsent(key, "locked", LOCK_EXPIRATION_TIME, TimeUnit.SECONDS);  
    }  
  
    // 释放锁  
    private void releaseLock(String key) {  
        redisTemplate.delete(key);  
    }  
  
    // 新增User的逻辑  
    public void addUserIfNotExists(User user) {  
        String nameAgeLockKey = "user:lock:name:" + user.getName() + ":age:" + user.getAge();  
  
        // 尝试获取锁  ,获取锁成功才会继续执行下面业务上的校验
        if (tryLock(nameAgeLockKey)) {  
            try {  
                // 在这里执行数据库检查(是否已存在同name和age)  
                
                // 如果不存在,则执行插入操作  
  
                // 假设检查通过,执行插入操作(这里省略了具体的数据库操作)  
                System.out.println("User added successfully");  
  
            } finally {  
                // 释放锁  
                releaseLock(nameAgeLockKey);  
            }  
        } else {  
            // 未能获取锁,可能是其他进程正在处理相同的组合  
            System.out.println("Failed to acquire lock(s), user addition may be in progress");  
        }  
    }  
  
    // ... 其他代码 ...  
}

避免 name和age的和age和sex重复:使用双重的分布式锁实现:

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.data.redis.core.ValueOperations;  
import org.springframework.stereotype.Service;  
  
import java.util.concurrent.TimeUnit;  
  
@Service  
public class UserService {  
  
    @Autowired  
    private RedisTemplate<String, String> redisTemplate;  
  
    // 假设的锁过期时间  
    private static final long LOCK_EXPIRATION_TIME = 10L; // 10秒  
  
    // 尝试获取锁  
    private boolean tryLock(String key) {  
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();  
        // 尝试设置锁,如果键不存在则设置成功,并设置过期时间  
        return opsForValue.setIfAbsent(key, "locked", LOCK_EXPIRATION_TIME, TimeUnit.SECONDS);  
    }  
  
    // 释放锁  
    private void releaseLock(String key) {  
        redisTemplate.delete(key);  
    }  
  
    // 新增User的逻辑  
    public void addUserIfNotExists(User user) {  
    	//加两次锁
        String nameAgeLockKey = "user:lock:name:" + user.getName() + ":age:" + user.getAge();  
        String ageSexLockKey = "user:lock:age:" + user.getAge() + ":sex:" + user.getSex();  
  
        // 尝试获取两个锁  
        if (tryLock(nameAgeLockKey) && tryLock(ageSexLockKey)) {  
            try {  
                // 在这里执行数据库检查(是否已存在同name和age或同age和sex的User)  
                // 如果不存在,则执行插入操作  
  
                // 假设检查通过,执行插入操作(这里省略了具体的数据库操作)  
                System.out.println("User added successfully");  
  
            } finally {  
                // 释放锁  释放两次
                releaseLock(nameAgeLockKey);  
                releaseLock(ageSexLockKey);  
            }  
        } else {  
            // 未能获取锁,可能是其他进程正在处理相同的组合  
            System.out.println("Failed to acquire lock(s), user addition may be in progress");  
        }  
    }  
  
    // ... 其他代码 ...  
}

双重加锁的 注意点
上面的代码示例简化了错误处理和重试逻辑。在实际应用中,你可能需要处理各种异常情况,例如Redis服务器不可用、锁被意外删除或过期等。此外,如果业务逻辑复杂或执行时间较长,你可能需要考虑使用更高级的锁机制,如Redis的发布/订阅模式、Lua脚本或Redis的RedLock算法来确保锁的安全性和可靠性。

另外,请注意,tryLock 方法中的 setIfAbsent 操作是原子的,这意味着它会在单个Redis命令中完成检查和设置操作,从而避免了竞态条件。但是,由于网络延迟、Redis服务器性能等因素,多个客户端仍可能几乎同时尝试获取相同的锁。因此,即使使用了锁,也需要谨慎地设计你的业务逻辑和错误处理策略。

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

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

相关文章

FGPA实验——触摸按键

本文系列都基于正点原子新起点开发板 FPGA系列 1&#xff0c;verlog基本语法&#xff08;随时更新&#xff09; 2&#xff0c;流水灯&#xff08;待定&#xff09; 3&#xff0c;FGPA实验——触摸按键 一、触摸操作原理实现 分类&#xff1a;电阻式&#xff08;不耐用&…

SVN文件不显示修改状态图标

今天安装试用SVN时发现文件不显示修改状态 以下为解决方法&#xff1a; 1&#xff0c;在有.svn的文件夹中右键--tortoiseSvn--setting 2&#xff0c;选中icon Overlays&#xff0c;右侧的status cache 选shell 3&#xff0c;点击icon set 如下图所示 4&#xff0c;修改icon…

MySQL扩展

一、慢查询&#xff08;慢日志&#xff09; 默认关闭的 定位慢SQL 简单&#xff1a;show profile&#xff0c;启用时会对服务器的性能产生额外的负担 -- 启用性能监控 mysql> set profiling1;-- 执行SQL mysql> SELECT * from member-- 性能分析 mysql> show p…

AOT源码解析4.4 -decoder生成预测mask并计算loss

3、生成ref_imgs的预测mask和loss 这一步在训练阶段调用 3.1 数据处理 图1&#xff0c;如图1所示&#xff0c;将enc_embs的最后一个比例的特征图和有ref_imgs相关的特征图得到的LSTT特征图相拼接作为输入 curr_enc_embs self.curr_enc_embscurr_lstt_embs self.curr_lstt_o…

卷轴模式商城APP开发搭建全流程解析

卷轴模式商城APP的开发搭建是一个综合性强、涉及多个关键步骤和技术环节的过程。本文将详细介绍从需求分析到最终发布的各个阶段&#xff0c;旨在为开发者renxb001提供一个清晰的开发指导方案。 一、需求分析 目标用户群体&#xff1a;首先&#xff0c;明确APP的目标用户&…

openKylin--安装 .net6.0

编辑profile文件 cd .. //切换到根目录 cd /etc //切换到etc目录 vim profile //b编辑profile文件 1. 按→键移动到文件末尾 2. 按Insert键进入编辑模式 3. 按Enter另起一行开始编辑 export DOTNET_ROOT/home/dotnetexport PATH$PATH:/home/dotnet 可以通过右键--粘贴 的…

基于skopt的贝叶斯优化基础实例学习实践

贝叶斯方法是非常基础且重要的方法&#xff0c;在前文中断断续续也有所介绍&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《数学之美番外篇&#xff1a;平凡而又神奇的贝叶斯方法》 《贝叶斯深度学习——基于PyMC3的变分推理》 《模型优化调参利器贝叶斯优化bay…

Brave编译指南2024 MacOS篇-引言与准备工作(一)

引言 随着互联网隐私和安全问题日益突出,用户对安全浏览器的需求不断增加。Brave浏览器作为一款注重隐私保护和性能优化的开源浏览器,吸引了越来越多开发者的关注。本系列文章将详细介绍如何在MacOS环境下编译Brave浏览器,为有兴趣深入了解和定制Brave的开发者提供指导。 1. …

【智能控制】16章 基于Hopfield网络的路径优化,TSP问题

目录 15.6 基于Hopfield网络的路径优化 15.6.1 TSP问题 15.6.2 求解TSP问题的Hopfield神经网络设计 15.6 基于Hopfield网络的路径优化 15.6.1 TSP问题 旅行商问题&#xff08;Traveling Salesman Problem&#xff0c;简称TSP&#xff09;可描述为&#xff1a;已知N个城市之…

CloudMusic:免费听歌

本文所涉及所有资源均在 传知代码平台可获取。 目录 概述 演示效果 视频演示 图片展示 核心逻辑 获取歌曲图片 提取搜索结果 使用方式 部署方式 Docker部署1 构建镜像 Web站点部署2 附件下载 概述 CloudMusic是一款全网歌曲免费听的web项目&#xff0c;无需任何数据库&#x…

如何隐藏Windows10「安全删除硬件」里的USB无线网卡

本方法参照了原文《如何隐藏Windows10「安全删除硬件」里的USB无线网卡》里面的方法&#xff0c;但是文章中的描述我的实际情况不太一样&#xff0c;于是我针对自己的实际情况进行了调整&#xff0c;经过测试可以成功隐藏Windows10「安全删除硬件」里的USB无线网卡。 先说一下…

QT学习笔记之文件操作

你千万不要跟任何人谈起任何事。你只要一谈起&#xff0c;就会想念起每一个人来。 在ui界面添加一个LineEdit(lEt)、QPushButton(btn)、QWidget widget.cpp #include "widget.h" #include "ui_widget.h" #include <QFile> #include <QFileDialo…

node.js从入门到快速开发一个简易的web服务器

浏览器中JavaScript学习路径: JavaScript基础语法浏览器内置API(DOMBOM)第三方库(jQuery,art-template等) Node.js的学习路径 JavaScript基础语法Node.js内置API模块(fs、path、http等)第三方API模块(express、mysql等) Node.js安装 通过Node.js 来运行Javascript 代码&am…

坝上草原与闪电湖多伦湖自驾行程记录与攻略

本文介绍河北坝上草原、内蒙古多伦湖2天2夜自驾自由行&#xff08;坝上草原1日、多伦湖1日&#xff09;的每日详细行程、游览心得、避坑经历等。 2024年09月中秋节期间&#xff0c;我们一行4人从北京出发&#xff0c;自驾前往河北省与内蒙古自治区等2地&#xff0c;进行了一共为…

几个可以给pdf加密的方法,pdf加密详细教程。

几个可以给pdf加密的方法&#xff0c;pdf加密详细教程。在信息快速传播的今天&#xff0c;PDF文件已经成为重要的文档格式&#xff0c;被广泛应用于工作、学习和个人事务中。然而&#xff0c;随着数字内容的增加&#xff0c;数据安全和隐私保护的问题愈发凸显。无论是商业机密、…

高级算法设计与分析 学习笔记9 跳表

单链表的样子我们很熟悉了&#xff1a; 怎么加快查找&#xff1f;&#xff1a; 查找的具体方法&#xff1a; 超过了就回头下去。 这条“快速路”最好是几个节点呢&#xff1f;&#xff1a; 假如我们弄好多层跳表呢&#xff1f;&#xff1a; 给弄成2叉树了&#xff01; 如何插入…

设计模式、系统设计 record part01

技术路线&#xff1a; 工程师》设计师》分析师》架构师 管理路线&#xff1a; 项目经理》技术经理 工程师&#xff1a; 编程技术、测试技术 设计师&#xff1a; 工程师设计技术 分析师&#xff1a; 设计师分析技术 架构师&#xff1a; 分析师架构技术 项目经理&#xff1a; 时间…

发掘3D文件格式的无限潜力:打造沉浸式虚拟世界

在当今数字化时代&#xff0c;3D技术的应用范围日益广泛&#xff0c;涵盖电影后期制作、产品原型设计、虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;、游戏等众多领域。而3D文件格式作为3D技术的核心组成部分&#xff0c;对于实现3D数据和模型的存…

【linux进程】进程状态僵尸进程孤儿进程

目录 一&#xff0c;linux下的特定进程状态1. R状态 vs S状态2. T状态 vs t 状态3. D状态 vs S状态 二&#xff0c;OS中的进程状态1. 运行状态2. 阻塞状态3. 挂起状态 三&#xff0c;僵尸进程和孤儿进程1. 僵尸状态和僵尸进程2. 孤儿进程 一&#xff0c;linux下的特定进程状态 …

kafka分区和副本的关系?

概念来一波 比如一个topic的消息存放在两个分区中&#xff0c;分区1和分区2.每个分区都有自己的一个副本。即比如分区1有副本1/副本2/副本3&#xff0c;分区2也有分区2的副本1/副本2/副本3。一个节点上的一个topic的可以由多个分区存放&#xff0c;但是每个分区的leader副本会尽…