Redis解决超卖Demo(分布式锁)

引言

多并发引起的超卖问题一直都是秒杀业务的核心,在极短的时间内没有中间件进行过渡,同时向底层DB发起请求,DB很容易崩溃,引入缓存中间件又引起了两者双写一致的问题,真令人头大,紧接着又引入一个异步处理的消息中间件。解决超卖的问题大致流程是,并发下单,先在缓存中对库存进行预减,然后向消息中间件推送成功的消息,然后更新DB数据的接口从消息队列中消费消息更新库存(这是性能较优的一种方案)。本文章是专注于探讨分布式锁的案例。

引入依赖以及yml配置文件

  • pom文件
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • yml文件(根据自身环境修改)
server:
  port: 9000
spring:
  application:
    name: redis
  redis:
    host: 192.168.136.130
    port: 6379
    password: 123456
    lettuce:
      pool:
        max-active: 10
        max-idle: 10
        min-idle: 1
        time-between-eviction-runs: 10s

 配置Redis序列化

package redis.chaomai.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
/*
* redis序列化配置
* */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory)
    {
        //缓存序列化配置避免存储乱码

        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return  redisTemplate;
    }
}

 数据准备

  • 在Redis中添加一条Key为Apple,Value为30000的数据。

        set Apple 30000

不加锁的接口

  • 代码
@RequestMapping("test")
 void cherkAndReduceStock(){
     String stock = redisTemplate.opsForValue().get("Apple").toString();

     if (stock!=null)
     {
         Integer value = Integer.valueOf(stock);
         if (value>0){
             redisTemplate.opsForValue().set("Apple",String.valueOf(--value));
         }
     }

 }
  •  使用Jmeter压测

  • 压测结果 ,库存不为0

 添加分布式锁

  • redis命令setnx,同时为了避免死锁,同时设置锁的过期时间,redis命令set key value ex 2 nx
@RequestMapping("testAddsetxAddFinally")
void cherkAndReduceStockAddSetnxAddFinally()
{
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock-stock", "0000",2, TimeUnit.SECONDS);
    //获取锁失败,停止50ms,递归调用
    if (!lock){
        try {
            Thread.sleep(50);
            this.cherkAndReduceStockAddSetnxAddFinally();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }else {
        try {
            String stock = redisTemplate.opsForValue().get("Apple").toString();
            if(stock!=null&&stock.length()!=0)
            {
                Integer valueOf = Integer.valueOf(stock);
                if (valueOf>0)
                {
                    redisTemplate.opsForValue().set("Apple",String.valueOf(--valueOf));
                }else {
                    System.out.println("商品售罄!!!");
                }
            }
        }finally {
            redisTemplate.delete("lock-stock");
        }

    }
}
  • 使用Jmeter压测 ,查看结果

库存为0,问题解决

  • 锁过期被删除,代码还没结束

等同于在半路锁过期被删除了,如下模拟场景

  • 使用Jmeter压测后依旧存在超卖
  • 解决方案:1.使用Redisson提供的自动延期机制(看门狗机制)。            

                         2.配合使用Lua脚本原子性指令操作。

总结

本人对redis分布式锁的一些理解,欢迎大家指正!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值