大厂秒杀系统后端Redis高并发分布式锁实战

一,秒杀下单减库存实例讲解

这里模拟一个下单场景,具体架构如下

比如说tomcat1和tomcat2是同一个下单系统,springboot项目,分布式处理在两台机器上,使用nginx进行负载,然后商品数量存储在redis中,对nginx请求且高并发压测。

1,首先先搭建一下基础环境

springboot集成redis参考文章:https://www.cnblogs.com/fantongxue/p/12626620.html

创建两个springboot工程(复制两份),接口如下

@RestController
public class TestController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/order")
    public void order(){
        //redis中的库存
        stringRedisTemplate.opsForValue().set("order","200");
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
        if(stock>0){
            int realStock = stock -1;
            stringRedisTemplate.opsForValue().set("order",realStock+"");
            System.out.println("扣减成功,剩余库存:"+realStock);
        }else{
            System.out.println("扣减失败,库存不足");
        }
    }
}

然后配置nginx负载均衡,如何负载参考文章:https://www.cnblogs.com/fantongxue/p/12443583.html

然后访问nginx地址和端口,http://localhost:90/order,会负载到两个springboot上

2,如果是单机环境,问题就很简单了

使用synchronized代码块,每次执行只能保证一个线程进入方法执行代码,但如果是分布式的话,怎么处理?

 @RequestMapping("/order")
    public void order(){
        //redis中的库存
        synchronized (this){
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
            if(stock>0){
                int realStock = stock -1;
                stringRedisTemplate.opsForValue().set("order",realStock+"");
                System.out.println("扣减成功,剩余库存:"+realStock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        }
    }

3,入门级分布式锁

在redis处理之前,先存一个K,V,这里的K的作用就是作为一个锁,使用redisTemplate的setIfAbsent方法可以实现锁机制。

setIfAbsent就是如果存在这个K,不做任何处理,如果不存在,设置V的值,仔细品!

 @RequestMapping("/order")
    public String order(){
       try{
            
        String lockKey="lockKey";//给一个锁起个名字
        String uuid = UUIDUtil.getUUID();//随机值
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid,10,TimeUnit.SECONDS);//和jedis.setnx(k,v)一样
        if(!result){
            return "error_code";
        }
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
            if(stock>0){
                int realStock = stock -1;
                stringRedisTemplate.opsForValue().set("order",realStock+"");
                System.out.println("扣减成功,剩余库存:"+realStock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
       }finally{
           if(uuid.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                //删除锁
        		stringRedisTemplate.delete(lockKey);
           }
       }
         return "下单成功";
    }

设置超时时间的目的:如果后端服务器宕机,当加了锁之后宕机了,那么其他线程就进不来这个方法了,就这么简单!

设置UUID的目的:把每个线程区分开,如果不设置会导致什么结果?比如执行完这个方法需要15秒(慢查询,高并发等因素),而执行到10秒的时候,lockKey自动失效,那么其他线程就可以获取锁进入方法。

上面的分布式锁表面看着没多大问题了对吧?其实还有问题!超时时间设置多久合适?

结果:设置多久都不合适!如果设置时间短,线程A还没有执行完方法中的代码,那边锁就已经自动释放了,不合适;如果设置时间过长,线程A加了锁之后宕机了,其他线程都必须要等到自动释放锁之后才可以继续进入方法。

二,大厂分布式锁redission框架实战

redission内部优化了上面的锁机制,解决了超时时间的设置问题。

它的实现思路:redissonLock.lock()方法的底层会启动另一个线程每隔一段时间检查锁是否快到期了,如果快到期了,延长超时时间。

Redisson实现Redis分布式锁的底层原理:https://www.cnblogs.com/windpoplar/p/11964088.html

手写一个redission分布式锁见下篇文章:手写一个基于redis的分布式锁(watch dog看门狗 / redisson分布式锁的底层原理)

引入依赖

		<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.5</version>
        </dependency>

初始化

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    //初始化
    @Bean
    public Redisson redisson(){
        //此为单机模式
        Config config=new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson)Redisson.create(config);
    }
}

然后使用即可

@RestController
public class TestController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private Redisson redisson;

    @RequestMapping("/order")
    public void order(){
        RLock redissonLock = redisson.getLock("lockName");
        try{
            //加锁
            redissonLock.lock();
            //redis中的库存
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
            if(stock>0){
                int realStock = stock -1;
                stringRedisTemplate.opsForValue().set("order",realStock+"");
                System.out.println("扣减成功,剩余库存:"+realStock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        }finally {
            //释放锁
            redissonLock.unlock();
        }
    }
}
posted @ 2021-04-23 17:41  你樊不樊  阅读(299)  评论(0编辑  收藏  举报