Redis分布式锁解决超卖问题的使用示例

 更新时间:2023年09月11日 11:21:10   作者:猿究院杨树林  
超卖问题通常出现在多用户并发操作的情况下,即多个用户尝试购买同一件商品,导致商品库存不足或者超卖,本文就来介绍一下超卖问题,感兴趣的可以了解一下

前言

超卖问题通常出现在多用户并发操作的情况下,即多个用户尝试购买同一件商品,导致商品库存不足或者超卖。解决超卖问题的方法有很多:乐观锁、Redis分布式锁、消息队列等。分布式锁是一种多节点共享的同步机制,通过在多个节点之间协调访问资源,确保在同一时间只有一个节点能够获取锁并执行关键操作。在电商网站中,可以将每个商品的库存作为共享资源,使用分布式锁来控制并发访问。

分布式锁的目的是保证在分布式部署的应用集群中,多个服务在请求同一个方法或者同一个业务操作的情况下,对应业务逻辑只能被一台机器上的一个线程执行,避免出现并发问题。

分布式锁要满足的条件:

  • 多进程互斥:同一时刻,只有一个进程可以获取锁
    ● 阻塞锁(可选):获取锁失败时可否重试
    重入锁(可选):获取锁的代码递归调用时,依然可以获取锁
  • 保证锁可以释放:任务结束或出现异常,锁一定要释放,避免死锁

Redis分布式锁:

在实现Redis分布式锁之前,我们先来看看为什么Redis能实现分布式锁:

在Redis中,利用Redis的setnx命令,这个命令的特征时如果多次执行,只有第一次执行会成功,可以实现互斥的效果。这满足了多线程互斥的要求

SETNX lock thread1

在Redis中,利用Redis的del命令,可以删除一个key,即释放锁。

DEL lock

如果获取锁成功后服务宕机,发生了不释放锁的问题,Redis也可以通过给Key加有效时间,让超时自动释放。这样一来,也满足了保证锁可以释放,达到了分布式锁必须满足的条件!

EXPIRE lock 10  

并且,也不用担心在EXPIRE设置有效期之前服务宕机,Redis的set命令可以满足setnx和expirr的原子性,用一个指令完成两个步骤!

set lock thread1 EX 10 NX

分布式架构中实现

加Redis坐标

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

配置Redis端口信息

Spring:
	redis:
	    port: 6379
	    host: localhost

构造加锁工具类

public interface ILock {
    /**
     * 尝试获取锁
     * @param timeoutSec 锁持有的超时时间,过期后自动释放
     * @return true代表获取锁成功; false代表获取锁失败
     */
    boolean tryLock(long timeoutSec);
    /**
     * 释放锁
     */
    void unlock();
}
public class SimpleRedisLock implements ILock  {
    private StringRedisTemplate stringRedisTemplate;
    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    public boolean tryLock(long timeoutSec) {
        Boolean secuss = stringRedisTemplate.opsForValue()
                .setIfAbsent("lock", "thread", timeoutSec, TimeUnit.SECONDS);
        return secuss;
    }
    @Override
    public void unlock() {
        stringRedisTemplate.delete("lock");
    }
}

在实际秒杀业务代码上加锁、解锁

@PostMapping("/kill")
    public String executeSeckillProduct(int productId,int seckillCount){
        //获取锁对象
        SimpleRedisLock redisLock =new SimpleRedisLock(stringRedisTemplate);
        //尝试获取锁对象
        boolean isLock = redisLock.tryLock(1200);
        if(!isLock){
            return "获取锁失败";
        }
        Product product = productService.findByPid(productId);
        if(product.getStock()<seckillCount){
            return "库存不足";
        }
        product.setStock(product.getStock()-seckillCount);
        int row = productService.reduceInventory(product);
        if(row>0){
            //新增秒杀记录
            SeckillRecord record = new SeckillRecord();
            record.setPid(product.getPid());
            record.setCount(seckillCount);
            record.setPanme(product.getPname());
            record.setTime(new Timestamp(System.currentTimeMillis()));
            recodeService.save(record);
        }
        //释放锁
        redisLock.unlock();
        return "秒杀成功";
    }

测试

在测试Redis分布式锁之前,我先用了GetWay网关同意了API接口,由网关分发路由请求,通过OpenFegin实现通信并做到负载均衡效果,并对秒杀服务做了节点扩展,尽可能的模拟除了分布式架构:网关配置:

server:
  port: 7000
spring:
  application:
    name: gateway-application
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: reckill_route
          uri: lb://service-reckill
          order: 1
          predicates:
            - Path=/reckill-serv/**
          filters:
            - StripPrefix=1

先测试不加分布式锁的效果:数据库库存:10

秒杀记录:0

运行服务:

JMeter测试数据:每秒500个请求

JMeter端口信息:

测试结果:

库存还剩6个,消耗4个。

秒杀记录已经一片糊涂了:

加上分布式锁测试:测试数据如上。库存为0:

秒杀记录:

节点拓展后的三个秒杀服务:

三个服务都共同通过负载均衡消费了请求!

但还要注意的一点是,对于锁的把控一定要按时释放,一次的不释放,可能都会导致后续请求无法成功!

到此这篇关于Redis分布式锁解决超卖问题的使用示例的文章就介绍到这了,更多相关Redis 超卖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redis整合Lua脚本的实现操作

    Redis整合Lua脚本的实现操作

    Redis对lua脚本的支持是从Redis2.6.0版本开始引入的,它可以让用户在Redis服务器内置的Lua解释器中执行指定的lua脚本,本文就来介绍一下Redis整合Lua脚本的实现,感兴趣的可以了解一下
    2024-03-03
  • redis模糊批量删除key的方法

    redis模糊批量删除key的方法

    这篇文章主要介绍了redis模糊批量清除key的操作方法,包括命令行删除和golang代码删除,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • Redis遍历海量数据集的几种实现方法

    Redis遍历海量数据集的几种实现方法

    Redis作为一个高性能的键值存储数据库,广泛应用于各种场景,包括缓存、消息队列、排行榜,本文主要介绍了Redis遍历海量数据集的几种实现方法,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-02-02
  • Redis简单动态字符串SDS的实现示例

    Redis简单动态字符串SDS的实现示例

    Redis没有直接复用C语言的字符串,而是新建了SDS,本文主要介绍了Redis简单动态字符串SDS的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • 利用Redis进行数据缓存的项目实践

    利用Redis进行数据缓存的项目实践

    在实际的业务场景中,Redis 一般和其他数据库搭配使用,用来减轻后端数据库的压力,本文就介绍了利用Redis进行数据缓存的项目实践,具有一定的参考价值,感兴趣的可以了解一下
    2022-06-06
  • Redis三种常用的缓存读写策略步骤详解

    Redis三种常用的缓存读写策略步骤详解

    Redis有三种读写策略分别是:旁路缓存模式策略、读写穿透策略、异步缓存写入策略,接下来通过本文给大家详细介绍下Redis三种常用的缓存读写策略,感兴趣的朋友一起看看吧
    2022-05-05
  • 百行代码实现基于Redis的可靠延迟队列

    百行代码实现基于Redis的可靠延迟队列

    本文主要介绍了百行代码实现基于Redis的可靠延迟队列,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • 基于Redis+Lua脚本实现分布式限流组件封装的方法

    基于Redis+Lua脚本实现分布式限流组件封装的方法

    这篇文章主要介绍了基于Redis+Lua脚本实现分布式限流组件封装,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • 动态添加Redis密码认证的方法

    动态添加Redis密码认证的方法

    本篇文章主要介绍了动态添加Redis密码认证的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • 浅谈Redis缓存击穿、缓存穿透、缓存雪崩的解决方案

    浅谈Redis缓存击穿、缓存穿透、缓存雪崩的解决方案

    这篇文章主要介绍了浅谈Redis缓存击穿、缓存穿透、缓存雪崩的解决方案,缓存是分布式系统中的重要组件,主要解决在高并发、大数据场景下,热点数据访问的性能问题,需要的朋友可以参考下
    2023-03-03

最新评论