【Redis】基于Redission实现分布式锁(代码实现)

目录

基于Redission实现分布式锁解决商品秒杀超卖的场景:

1.引入依赖:

2.加上redis的配置:

3.添加配置类:

4.编写代码实现:

5.模拟服务器分布式集群的情况:

        1.右键点击Copy Configuration

        2.点击Modify option

        3. 选择VM option(用于指定新的端口)

        4.输入想要指定的端口(比如):-Dserver.port=8082 点击Apply

        5.出现新的进程,点击启动,就可以进行分布式多节点测试。

使用Jmeter进行压测


        (单机部署)多线程高并发情况下对同一个共享资源进行读写时,会出现数据错乱(数据不一致)的问题;加锁(同步锁)可以解决出现数据不一致的问题;(其他线程进行等待持有锁的线程执行完成后才能进行正常的处理)。
        但是随着用户量日益增多,单个服务器压力越来越大,所以使用多个服务器进行分布式集群部署,虽然降低了服务器的压力,提高了服务器的吞吐量。但是还是会出现数据不一致的问题,这时候发现是是同步锁(synchronized)的问题,同步锁基于JVM的,他只能锁住单个服务器中一个线程,但是经过分布式集群部署过后,每台服务器在并发的情况下只能锁住一个线程,所以高并发情况下,还是会出现数据错乱的情况。(假如4个服务器,每台服务器都处于高并发的情况,然后同步锁只能锁住一个线程,这时候每个服务器锁住一个线程,最多可以出现4个线程同时对数据库进行操作,就会造成数据不一致的问题(例如常说的秒杀超卖的情况))

        经过查阅资料发现可以通过分布式锁可以解决,然后有三种主流的分布式锁的解决方案分别是使用基于Mysql/Zookeeper/redis实现分布式锁。由于我们系统使用到了Redis,考虑Zookeeper需要重新部署到服务器,避免间接增加服务器的成本,所以直接使用Redis来实现分布式锁。我们发现使用Redis的setNX可以很简单的实现分布式锁。
        那么setNX的特性是什么呢?
        当一个线程进来,往Redis当中通过setNX去存储一个值的时候,他会根据键值(key)去查看是否存在value值,没有就存储一个值返回true,有就返回一个false,注意一定要加上锁的过期时间,避免线程阻塞。(当用户在请求的过程当中,通过setNX进行加锁完成的时候,这个服务器挂掉了,当其他线程进行setNX进行上锁的时候,发现键当中一直会有值,造成了死锁,其他线程加锁不成功就会造成阻塞)。所以一定要加上过期时间。

        随着业务的扩展,又出现了一些问题,就是1.业务的处理时间超过了锁的过期时间和2.线程1可能释放了线程2所持有的锁,【线程1还没将业务处理完成就释放锁,导致线程2拿到锁处理自己的业务。当线程1执行完成后,释放了锁,但是此时线程2已经拿到了当前锁,所以线程1释放的是线程2的锁。】
        如何解决这些问题呢?
        1.加长锁的过期时间,并增加子线程每10秒去确认线程是否在线。在线则将过期时间重设(续命--他们所说的看门狗);
        2.给锁的值设置一个唯一ID(UUID)-(使用setNx进行尝试获取锁的时候,如果获取成功,将锁的值设置为一个唯一的ID,释放锁的时候会拿着key去获取锁的值是否与自己的唯一ID一致,一致才进行释放锁,从而就不会释放其他线程的锁)

        上述说这么多,如果让我们自己写起来确实有些麻烦,这时候查阅发现redis他本身已经提供了一个Redission的组件已经解决了这些问题。

那么下面我就提供一个

基于Redission实现分布式锁解决商品秒杀超卖的场景:

springboot版本:2.6.13

redission版本:3.22.0

1.引入依赖:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
 <dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.22.0</version>
 </dependency>

2.加上redis的配置:

server:
  port: 8083
spring:
  redis:
    host: 127.0.0.1
    password:
    port: 6379
    timeout: 1000

3.添加配置类:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;
    
    @Bean
    public RedissonClient getRedisson(){
        Config config = new Config();
        //单机模式  依次设置redis地址和密码
        config.useSingleServer().
                setAddress("redis://" + host + ":" + port)
                .setTimeout(30000); // 设置缓存过期时间为30秒
        return Redisson.create(config);
    }
}

4.编写代码实现:


import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Objects;

@RestController
@RequestMapping("/redisLock")
public class RedisLockController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redisson;

    // 秒杀商品key
    private static final String REDIS_KEY = "secKillProductKey:";

    private static final String LOCK_KEY = "secKillProduct";

    // 秒杀商品总个数
    private static final int PRODUCT_SIZE = 1000;


    /**
     * 初始化秒杀商品总个数到redis中
     * 注意:测试的时候,先调用这个接口初始化库存
     */
    @GetMapping("/init")
    public void init() {
        // 初始化库存 将库存存到redis中
        stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(PRODUCT_SIZE));
    }

    /**
     * 秒杀
     */
    @GetMapping("/secKill")
    public void secKill() {

        // 获取锁
        RLock lock = redisson.getLock(LOCK_KEY);
        try {
            // 加锁
            lock.lock();
            int s = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(REDIS_KEY)));
            if (s > 0) {
                // 扣库存
                s--;
                System.out.printf("秒杀商品个数剩余:" + s + "\n");
                // 更新库存
                stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(s));
            } else {
                System.out.println("活动太火爆了,商品已经被抢购一空了!");
            }
        } catch (Exception e) {
            System.out.println(Thread.currentThread().getName() + "异常:");
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

5.模拟服务器分布式集群的情况:

        同一个服务不同端口,同时运行两个相同的主程序。

在service中复制一个进程,指定不同端口

        1.右键点击Copy Configuration

        2.点击Modify option

        3. 选择VM option(用于指定新的端口)

        4.输入想要指定的端口(比如):-Dserver.port=8082 点击Apply

        5.出现新的进程,点击启动,就可以进行分布式多节点测试。

使用Jmeter进行压测

首先需要先调用一下初始化的接口:127.0.0.1:8082/redisLock/init或127.0.0.1:8083/redisLock/init

1.设置线程组

2.两个HTTP请求,请求不同的端口(8082、8083)进行测试高并发多线程的情况:

3.服务器打印结果

经过测试,一秒钟1000个线程同时请求秒杀接收并没有出现超卖的问题。  

拓展:

       Redis本身是一个CP(一致性和分区容错性)模式的数据库,它通过主从复制实现高可用性,当主节点挂掉时,从节点会自动进行选举,选出一个新的主节点继续提供服务。但是,在主节点挂掉之前,它可能还来不及将最新的数据同步到从节点,这时就会出现数据不一致的问题。

         如果redis采用主从模式进行部署,当往redis中通过setNX进行加锁的过程中,主节点挂了,主节点的数据并没有同步到从节点当中,这种怎么办?

        可以使用RedLock(红锁)解决,RedLock是一个分布式锁的实现,它可以通过访问多个Redis节点来实现更高的可用性和一致性。RedLock的工作原理是,在加锁时,向多个Redis节点发送请求,只有当所有节点都成功返回时,才认为加锁成功。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智慧路灯:照亮未来城市的智慧之光

智慧路灯&#xff0c;顾名思义&#xff0c;是在传统路灯基础上集成物联网、大数据、云计算、人工智能等现代信息技术的新型照明系统。它不仅提供节能高效的照明服务&#xff0c;更成为城市信息采集、传输、发布的载体&#xff0c;以及多种增值服务的平台。 核心功能与技术创新 …

Cookie-SameSite属性 前端请求不带cookie的问题解决方案

最近遇到了前端请求后端不带cookie的问题&#xff0c; 请求时header里面就是没有cookie 查看响应应该是这个问题 SameSite是一个cookie属性&#xff0c;用于控制浏览器是否在跨站点请求中发送cookie。它有三个可能的值&#xff1a; 1. Strict&#xff08;严格模式&#xff09…

LUA移植到STM32F4,移植REPL,通过RTT Viewer交互

概述 站内移植LUA多数是使用C函数调用LUA&#xff0c;并没有移植REPL交互端口 本文将REPL也移植进去&#xff0c;做了简单的适配 LUA源码使用标准C库函数&#xff0c;如fgets&#xff0c;fwrite等&#xff0c;在嵌入式环境中要使用fgets&#xff0c;fwrite等C库函数&#xff…

【MATLAB源码-第227期】基于matlab的北方苍鹰优化算法(NGO)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 鼠群优化算法&#xff08;Rat Swarm Optimization, RSO&#xff09; 简介 鼠群优化算法&#xff08;Rat Swarm Optimization, RSO&#xff09;是一种模仿鼠类群体觅食行为的优化算法。该算法属于群体智能算法&#xff0c;通…

mysql密码过期的修改(Your password has expired. ..)

参考文章&#xff1a;mysql密码过期的修改方法&#xff08;your password has expired&#xff09;_我是知青-RuoYi 若依 (csdn.net) 问题&#xff1a;Your password has expired. To log inyou must change it using a clientthat supports expired passwords. 解决方式&…

代码随想录第28天|回溯算法

491. 非递减子序列 思路: 不可以排序, 否则会改变元素的顺序对收获的结果有要求, num.size() > 2, 且 num[i - 1] < num[i]需要进行去重, 不能使用排序后的方法去重每一层可用 unordered_set 去重组合问题, for 遍历需要标记起始位置 bug: 一定要先判断元素是否重复, …

【干货】什么是客户裂变系统?它如何帮助saas企业实现业绩转化?

在数字化时代&#xff0c;客户裂变系统已成为SaaS企业实现业绩转化的重要工具。简而言之&#xff0c;客户裂变系统是一种通过现有客户吸引更多新客户的策略。 什么是客户裂变系统&#xff1f; 该系统基于用户行为和数据分析&#xff0c;通过精心设计的激励机制&#xff0c;如…

王思聪隐形女儿曝光

王思聪"隐形"女儿曝光&#xff01;黄一鸣独自面对怀孕风波&#xff0c;坚持生下爱情结晶近日&#xff0c;娱乐圈掀起了一场惊天波澜&#xff01;前王思聪绯闻女友黄一鸣在接受专访时&#xff0c;大胆揭露了她与王思聪之间的爱恨纠葛&#xff0c;并首度公开承认&#…

Suno AI如何解决制作多语言混合的歌曲~

导读 你想不想制作一首有中文和粤语混合的歌曲&#xff1f; 你想不想制作一首有中文和日语混合的歌曲&#xff1f; 你想不想制作一首有中文和英语混合的歌曲&#xff1f; 如果你想不知道怎么操作&#xff0c;可以阅读下本文。 说明 本文让AI唱一首中文和日语混合的歌曲&am…

OpenCV中的圆形标靶检测——findCirclesGrid()(三)

前面说到cv::findCirclesGrid2()内部先使用SimpleBlobDetector进行圆斑检测,然后使用CirclesGridClusterFinder算法类执行基于层次聚类的标靶检测。如下图所示,由于噪声的影响,SimpleBlobDetector检出的标靶可能包含噪声。 而CirclesGridClusterFinder算法类会执行基…

【CT】LeetCode手撕—手撕快排

目录 题目1-思路-快排1-1 快排的核心思想快速排序算法步骤优美的调整区间 1-2 ⭐快排的实现 2- 实现⭐912. 排序数组——题解思路 3- ACM 实现 题目 原题连接&#xff1a;912. 排序数组 1-思路-快排 1-1 快排的核心思想 选择一个基准 基准左侧的元素都小于该元素基准右侧的元…

数据分析必备:一步步教你如何用matplotlib做数据可视化(6)

1、Matplotlib 网格 axes对象的grid()函数将图中网格的可见性设置为on或off。还可以显示网格的主要/次要(或两者)刻度。另外&#xff0c;可以在grid()函数中设置color&#xff0c;linestyle和linewidth属性。 参考以下示例代码 import matplotlib.pyplot as plt import numpy…

GLib库内存块数据类型简单用法

代码; #include <glib.h> int main() {GMemChunk *chunk; // 定义内存块gchar *mem[10]; // 定义指向原子的指针数组gint i, j;chunk g_mem_chunk_new( // 创建内存块"Test MemChunk", // 名称5, // 原子的长度50, …

在3dmax软件中如何快速创建毛发?---模大狮模型网

在3D建模和渲染中&#xff0c;为角色或物体添加逼真的毛发效果是提升场景真实感的重要步骤之一。然而&#xff0c;手动一根一根创建毛发是非常繁琐的&#xff0c;因此掌握如何在软件中快速生成和调整毛发效果至关重要。模大狮将详细介绍如何利用3ds Max 2018创建毛发&#xff0…

上市公司-社会责任报告、ESG报告文本(2006-2023年)

上市公司社会责任报告是企业对外公布的一份关于其社会责任实践和成果的详细文件&#xff0c;涵盖环境保护、社会贡献和公司治理等方面的表现。通常包含公司在减少环境影响、提升社会福祉、维护员工权益、促进社区发展以及确保透明和道德的管理实践等方面的信息和数据。有助于了…

宠物空气净化器爆火的背后...小米、希喂、安德迈性能大揭秘?

广东省 猫友们都清楚&#xff0c;猫咪虽然魅力无穷&#xff0c;但它们掉毛的问题确实令人头疼。家具、衣物上、空气中都散布着猫浮毛&#xff0c;给铲屎官造成困扰。其中最难处理的便是空气中的浮毛啦&#xff0c;如果不及时处理会对家庭成员造成健康威胁。接下来&#xff0c;我…

MySQL8,Navicat能登陆成功,密码却忘记了

执行成功的图&#xff1a; 以下为步骤&#xff1a;本文一共8个简单步骤。 环境&#xff1a;mysql8、window10、navicat11 1、打开本地电脑window10的命令窗&#xff08;俗称黑窗口&#xff09;&#xff0c;windowR 2、输入regegit&#xff0c;回车&#xff0c;打开注册表 3、…

判断单链表是否带环且返回节点

今天鄙人为大家带来的是一道简单的逻辑运算题。用用到了一个我们在链表中提及过的方法快慢法。这道题其实没啥考的实际意义。只是我们如果能了解这道题的解决方法的话。对我们后面梳理逻辑会有很大的帮助。 单链表的题目 我们可以看到上面的题目。就是让我们判断是否带环。也许…

深入了解 Android 中的 ViewStub

在 Android 开发中&#xff0c;性能优化一直是一个重要的话题。ViewStub 作为一种轻量级视图容器&#xff0c;可以帮助我们在合适的时机延迟加载视图&#xff0c;从而优化应用性能。本文将详细介绍 ViewStub 的概念、使用方法以及在实际开发中的应用场景。 什么是 ViewStub&am…