Redis系列之incr和decr命令是线程安全的?

news2024/9/25 21:26:18

Redis是一个单线程的服务,所以正常来说redis的命令是会排队执行的。incr/decr命令是redis提供的可以实现递增递减的命令,所以这两个命令也是具有原子性的?是线程安全的?这个也是互联网公司面试的常见题,话不多说,动手实践一下吧,假设这两个命令是线程安全的,既然是线程安全的,那么来模拟实现高并发场景的秒杀减库存业务

软件环境:

  • JDK 1.8

  • SpringBoot 2.2.1

  • Maven 3.2+

  • Mysql 8.0.26

  • spring-boot-starter-data-redis 2.2.1

  • redisson-spring-boot-starter 3.1.5.6

  • 开发工具

    • IntelliJ IDEA

    • smartGit

项目搭建

使用Spring官网的https://start.spring.io快速创建Spring Initializr项目
在这里插入图片描述
选择maven、jdk版本
在这里插入图片描述
选择需要的依赖
在这里插入图片描述

redisson的没搜到,那就手动配置

 <dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.15.6</version>
</dependency>

application.yml加上配置:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    database: 0
    redisson:
      config: |
        singleServerConfig:
          idleConnectionTimeout: 10000
          connectTimeout: 10000
          timeout: 3000
          retryAttempts: 3
          retryInterval: 1500
          password: null
          subscriptionsPerConnection: 5
          clientName: null
          address: "redis://127.0.0.1:6379"
          subscriptionConnectionMinimumIdleSize: 1
          subscriptionConnectionPoolSize: 50
          connectionMinimumIdleSize: 32
          connectionPoolSize: 64
          database: 0
          dnsMonitoringInterval: 5000
        threads: 0
        nettyThreads: 0
        codec: !<org.redisson.codec.JsonJacksonCodec> {}
        transportMode: "NIO"

动手实践

新建一个Redis配置类:

package com.example.redis.configuration;

import cn.hutool.core.util.StrUtil;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfiguration {

  
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new RedissonConnectionFactory();
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

    @Bean
    public RedissonClient redissonClient() {
        RedissonClient redissonClient = Redisson.create();
        return redissonClient;
    }


}

写一个测试类,初始化商品库存为1000,然后开启1024个线程去抢

package com.example.redis;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.example.redis.configuration.RedisConfiguration;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.stream.IntStream;

@Slf4j
@SpringBootTest
@ContextConfiguration(classes = RedisConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
public class SpringbootRedisIncrTests {

    private static final String REDIS_ID_KEY = "testKey:id:%s";
    private static final String REDIS_LOCK_KEY = "testKey:lock";

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @BeforeEach
    public void init(){
        log.info("init...");
        // 初始化库存为1000
        String idKey = String.format(REDIS_ID_KEY,  DateUtil.format(new Date() , DatePattern.PURE_DATE_PATTERN));
        redisTemplate.opsForValue().set(idKey, 1000);
    }

    @Test
    public void testIncr() throws InterruptedException {
        // 开启1024个线程去抢
        CountDownLatch countDownLatch = new CountDownLatch(1024);
        IntStream.range(0, 1024).forEach(e->{
            new Thread(new RunnableTest(countDownLatch)).start();
        });

        countDownLatch.await();
    }

    class RunnableTest implements Runnable {

        CountDownLatch countDownLatch;

        public RunnableTest(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @SneakyThrows
        @Override
        public void run() {
            invoke();
            countDownLatch.countDown();
        }
    }


    private void invoke() throws InterruptedException {   
        String idKey = String.format(REDIS_ID_KEY, DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN));
        long incr = Convert.toLong(redisTemplate.opsForValue().get(idKey));
        if (incr > 0) {
            redisTemplate.opsForValue().decrement(idKey);
        }
        log.info("increment:{}", incr);
        
    }




}

跑起来,测试,这个库存数量竟然为负数了,这个业务场景肯定是不合理的,所以这两个命令也不是线程安全的,不可以用来做秒杀减库存业务

在这里插入图片描述

所以,我们可以用分布式锁来简单改造一下代码

private void invoke() throws InterruptedException {
    RLock rLock = redissonClient.getLock(REDIS_LOCK_KEY);
    rLock.lock();
    try {
        String idKey = String.format(REDIS_ID_KEY, DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN));
        long incr = Convert.toLong(redisTemplate.opsForValue().get(idKey));
        if (incr > 0) {
            redisTemplate.opsForValue().decrement(idKey);
        }
        log.info("increment:{}", incr);
    } finally {
        rLock.unlock();
    }
}

跑起来,可以看到执行效率会慢一点,但是业务是正确的,不会出现库存为负数的情况

在这里插入图片描述

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

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

相关文章

linux 命令 tmux 用法详解

一、tmux 解决的痛点&#xff08;screen命令一样可以解决&#xff0c;但是tmux功能更强大&#xff09; 痛点一&#xff1a;大数据传输的漫长一夜 相信做过 Linux 服务运维的同学&#xff0c;都用 scp 进行过服务器间的大文件网络传输。一般这需要很长的时间&#xff0c;这期间…

react结合vant的Dialog实现签到弹框操作

1.需求 有时候在开发的时候&#xff0c;需要实现一个签到获取积分的功能&#xff0c;使用react怎么实现呢&#xff1f; 需求如下&#xff1a; 1.当点击“签到”按钮时&#xff0c;弹出签到框 2.展示签到信息&#xff1a; 签到天数&#xff0c; 对应天数签到能够获取的积分&…

封装时间轴组件 timeline

要求时间轴的点展示进度百分比&#xff0c;线也根据进度不同展示不同长度的颜色 实现效果&#xff1a; 使用的组件库是vant的circle 子组件&#xff1a; <template><div class"m-timeline-area" :style"width: ${width}px"><div class&qu…

XXL-Job详解(五):动态添加、启动任务

目录 前言XXL-Job API接口添加任务API动态添加任务动态启动任务 前言 看该文章之前&#xff0c;最好看一下之前的文章&#xff0c;比较方便我们理解 XXL-Job详解&#xff08;一&#xff09;&#xff1a;组件架构 XXL-Job详解&#xff08;二&#xff09;&#xff1a;安装部署 X…

SQLserver通过字符串中间截取然后分组

当我们存的数据是json的时候可以全部取出在模糊查询但是有多个重复数据的时候就没办法准确的模糊出来这个时候我们就需要用的字符串截取 --创建函数create FUNCTION [dbo].[Fmax] (str varchar(50),start VARCHAR(50),length VARCHAR(50)) RETURNS varchar(max) AS BEGINDEC…

如何使用Cloudreve搭建本地云盘系统并实现随时远程访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

nginx部署和安装-后端程序多端口访问-后端代理设置

部分补充 查看nginx是否安装http_ssl_module模块 ./nginx -V 看到有 configure arguments: --with-http_ssl_module, 则已安装。 如果没有安装&#xff1a;参考文档 nginx官网地址&#xff1a;nginx: download 这里下载nginx-1.18.0稳定版tar.gz 下载后&#xff0c;利用…

失落的艺术:无着色器3D渲染

假设你想创建一个甜蜜的弹跳立方体&#xff0c;如下所示&#xff1a; 一个弹跳的立方体 你可以使用 3D 框架&#xff0c;例如 OpenGL 或 Metal。 这涉及编写一个或多个顶点着色器来变换 3D 对象&#xff0c;以及编写一个或多个片段着色器来在屏幕上绘制这些变换后的对象。 然…

Docker Image(镜像)——5

目录&#xff1a; Docker 镜像是什么镜像生活案例镜像分层生活案例为什么需要镜像镜像命令详解 镜像命令清单docker imagesdocker tagdocker pulldocker pushdocker rmidocker savedocker loaddocker historydocker importdocker image prunedocker build镜像操作案例 查找镜像…

为什么Nginx被称为反向代理

下图显示了 &#x1d41f;&#x1d428;&#x1d42b;&#x1d430;&#x1d41a;&#x1d42b;&#x1d41d; &#x1d429;&#x1d42b;&#x1d428;&#x1d431;&#x1d432; 和 &#x1d42b;&#x1d41e;&#x1d42f;&#x1d41e;&#x1d42b;&#x1d42c;&#…

如何制定公司网络安全战略

网络安全可以保护公司的重要信息免受恶意软件和数据泄露等威胁。网络安全策略列出了您公司的 IT 系统当前面临的风险、您计划如何预防这些风险&#xff0c;以及如果发生这些风险该怎么办。 让本文成为您制定有效网络安全策略的一站式指南。我们将讨论网络安全风险评估以及策略…

LeetCode 1212 查询球队积分(PostgreSQL)

数据准备 Create table If Not Exists Teams (team_id int, team_name varchar(30)) Create table If Not Exists Matches (match_id int, host_team int, guest_team int, host_goals int, guest_goals int) Truncate table Teams insert into Teams (team_id, team_name) va…

创新领航 | 竹云参编《基层智治系统安全接入规范》团体标准正式发布!

近日&#xff0c;由杭州市委办公厅&#xff08;市密码管理局&#xff09;、杭州市基层治理综合指挥保障中心、杭州市拱墅区社会治理中心、杭州市拱墅区数据资源管理局、杭州竹云数字智能科技有限公司、杭州智诚质量标准技术评定中心共同参与编写的《基层智治系统安全接入规范》…

01、pytest:帮助你编写更好的程序

简介 ​pytest框架可以很容易地编写小型、可读的测试&#xff0c;并且可以扩展以支持应用程序和库的复杂功能测试。使用pytest至少需要安装Python3.7或PyPy3。PyPI包名称为pytest 一个快速的例子 content of test_sample.py def inc(x):return x1def test_ansewer():assert i…

nodejs+vue+微信小程序+python+PHP就业求职招聘信息平台的设计与实现-计算机毕业设计推荐

主要有前端和后端&#xff0c;前端显示整个网站的信息&#xff0c;后端主要对前端和网站的基本信息进行管理。用户端模块主要是系统中普通用户在注册、登录系统可以看到自己的基本信息&#xff0c;维护自己的信息&#xff1b;管理员端模块主要是管理员登录后对整个系统相关操作…

05、pytest断言确定的异常

官方用例 # content of test_sysexit.py import pytestdef f():raise SystemExit(1)def test_mytest():with pytest.raises(SystemExit):f()解读与实操 ​ 标准python raise函数可产生异常。pytest.raises可以断言某个异常会发现。异常发生了&#xff0c;用例执行成功&#x…

AF自动登录应用--实现无源码系统单点登录

在企业信息化的进程中&#xff0c;许多组织拥有一系列的老应用系统&#xff0c;这些系统在多年的运行中积累了大量的业务数据和流程。然而&#xff0c;这些老应用系统往往没有设计或实现单点登录&#xff08;SSO&#xff09;功能&#xff0c;用户需要在不同系统之间频繁输入账号…

【算法】算法题-20231205

这里写目录标题 一、LCS 01. 下载插件二、已知一个由数字组成的列表&#xff0c;请将列表中的所有0移到右侧三、实现一个trim()函数&#xff0c;去除字符串首尾的空格&#xff08;不能使用strip()方法&#xff09; 一、LCS 01. 下载插件 简单 小扣打算给自己的 VS code 安装使…

pycharm打断点调试

在PyCharm中使用断点调试可以帮助逐行执行代码并查看变量的值&#xff0c;以便更好地理解程序的执行过程。以下是在PyCharm中设置断点和使用调试功能的步骤和注意事项&#xff1a; 步骤&#xff1a; 打开PyCharm并打开要调试的项目。找到要设置断点的代码行。您可以在行号区…

如何使用Node.js快速创建本地HTTP服务器并实现异地远程访问

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…