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

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.1port: 6379password:database: 0redisson:config: |singleServerConfig:idleConnectionTimeout: 10000connectTimeout: 10000timeout: 3000retryAttempts: 3retryInterval: 1500password: nullsubscriptionsPerConnection: 5clientName: nulladdress: "redis://127.0.0.1:6379"subscriptionConnectionMinimumIdleSize: 1subscriptionConnectionPoolSize: 50connectionMinimumIdleSize: 32connectionPoolSize: 64database: 0dnsMonitoringInterval: 5000threads: 0nettyThreads: 0codec: !<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 {@Beanpublic RedisConnectionFactory redisConnectionFactory() {return new RedissonConnectionFactory();}@Beanpublic 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;}@Beanpublic 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";@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate RedissonClient redissonClient;@BeforeEachpublic void init(){log.info("init...");// 初始化库存为1000String idKey = String.format(REDIS_ID_KEY,  DateUtil.format(new Date() , DatePattern.PURE_DATE_PATTERN));redisTemplate.opsForValue().set(idKey, 1000);}@Testpublic 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@Overridepublic 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.hqwc.cn/news/247327.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AGI智能新时代,大模型为软件开发带来范式变革

导语 | 人工智能作为新一轮科技革命和产业变革的重要驱动力量&#xff0c;尤其是在当下新一轮 AI 大模型、生成式 AI 浪潮背景下&#xff0c;重视通用人工智能&#xff08;AGI&#xff09;成为行业的共识。在当前&#xff0c; AGI 技术背后的逻辑究竟是怎样的&#xff1f;技术创…

如何使用cpolar内网穿透工具实现公网SSH远程访问Deepin

文章目录 前言1. 开启SSH服务2. Deppin安装Cpolar3. 配置ssh公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 前言 Deepin操作系统是一个基于Debian的Linux操作系统&#xff0c;专注于使用者对日常办公、学习、生活和娱乐的操作体验的极致&#xff0…

idea__SpringBoot微服务01——了解Springboot

了解Springboot 一、回顾学习与现在三、回顾什么是Spring三、Spring是如何简化Java开发的四、什么是SpringBoot五、看图————————创作不易&#xff0c;如觉不错&#xff0c;随手点赞&#xff0c;关注&#xff0c;收藏(*&#xffe3;︶&#xffe3;)&#xff0c;谢谢~~ 一…

java常用知识点记忆

类的继承与多态 类的继承不支持多重继承非private 方法才可以被覆盖覆盖的方法要求&#xff0c;子类中的方法的名字&#xff0c;参数列表&#xff0c;返回类型与父类相同方法的重载是在一个类中定义方法名字相同&#xff0c;但是参数列表不同的方法要是在子类中定义了与父类名字…

Kubernetes存储搭建NFS挂载失败处理

搞NFS存储时候发现如下问题&#xff1a; Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 5m1s default-scheduler Successful…

WordPress定时文章自动发布技巧

对于许多WordPress站长来说&#xff0c;文章的管理和发布计划往往是一个头疼的问题。随着内容的不断增加&#xff0c;时间表的调整以及发布频率的把握成为了让人焦头烂额的挑战。 一、时间管理难题 对于博客管理员来说&#xff0c;时间管理一直是个令人困扰的问题。在忙碌的生…

Redis Desktop Manager for Mac:高效管理Redis数据的必备工具

Redis是一种快速、可扩展的内存数据库&#xff0c;被广泛应用于缓存、消息队列和实时分析等领域。而Redis Desktop Manager for Mac作为一款专为Mac用户设计的Redis桌面管理工具&#xff0c;为用户提供了高效便捷的方式来管理和操作Redis数据。 首先&#xff0c;Redis Desktop…

ADB命令集锦,一起来学吧

前言 在测试APP时&#xff0c;我们常常会用到adb命令来协助测试&#xff0c;那么adb命令到底是什么&#xff1f;有什么用&#xff1f;怎么用&#xff1f; 今天我就整理了一些工作中常用的adb知识点&#xff0c;希望对大家有所帮助。 ADB学习全攻略 ADB是什么&#xff1f; a…

1.nacos注册与发现及源码注册流程

目录 概述nacos工程案例nacos服务注册案例版本说明本地启动 nacos-server搭建 spring cloud alibaba 最佳实践服务注册案例服务订阅案例 nacos注册源码流程源码关键点技巧 结束 概述 通过本文&#xff0c;学会如何确定项目组件版本(减少可能出现的jar包冲突)&#xff0c;nacos…

网络类型解析(基础):探索通信世界的多样面貌

在当今数字化时代&#xff0c;网络已经成为人们生活和工作中不可或缺的一部分。从个人设备之间的直接通信到全球范围的数据传输&#xff0c;不同类型的网络为我们提供了多种连接方式和通信选择。透过对这些网络类型的解析&#xff0c;我们将更好地理解它们的特点、优势和适用场…

二值图像分割统一项目

1. 项目文件介绍 本章为二值图像的分割任务做统一实现&#xff0c;下面是项目的实现目录 项目和文章绑定了&#xff0c;之前没用过&#xff0c;不知道行不行 data 文件夹下负责摆放数据的训练集测试集inference 负责放待推理的图片(支持多张图片预测分割)run_results 是网络训…

gitlab-jenkins-shell-helm-chart-k8s自动化部署微服务

1.准备好编译环境的容器&#xff0c;所有容器的镜像制作在gemdale-dockerfile这个代码库里面&#xff0c;也可以直接拉取官方镜像部署 docker run --name node1420-patternx -v /data/var/www/:/data/var/www/ -v /var/jenkins_home/:/var/jenkins_home/ -v /mnt/hgfs/:/mnt/h…