来了解下高大上的高并发分布式----redis实战分布式锁

Redis高并发分布式锁实战

  • 前言
    • 分布式锁的应用场景
  • 一、搭建环境
  • 二、Jmeter压测引出分布式锁
  • 三、进一步优化
  • 四、Redisson


前言

分布式锁的应用场景

  • 互联网秒杀

  • 抢优惠卷

  • 接口幂等性校验

本篇将会通过实战模拟一下用redis解决高并发的分布式锁,循序渐进让大家觉得分布式不再那么遥不可及


用到的工具:Springboot项目,ngnix,redis,Jmeter

一、搭建环境

我们要搭建这么个模拟环境
来了解下高大上的高并发分布式----redis实战分布式锁_第1张图片

项目:
来了解下高大上的高并发分布式----redis实战分布式锁_第2张图片
indexController

@RequestMapping("/deduct_stock0")
    public String deductStock0() {
     

        try {
     
            //加锁
            synchronized (this){
     
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
                if (stock > 0) {
     
                    int realStock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", realStock + "");
                    System.out.println("扣减成功,剩余库存:" + realStock);
                } else {
     
                    System.out.println("扣减失败,库存不足");
                }
            }
        } catch (Exception e){
     
            e.printStackTrace();
        }
        return "end";
    }

电商平台的减库存操作。

启动两个SpringBoot来模拟2台tomcat集群下的场景。一个是8080端口,一个是8090端口在这里插入图片描述
那么接下来组件ngnix代理访问这俩个

Linux安装ngnix不会的参考这篇Linux下安装ngnix,

修改配置文件,做个反向代理,运行:

 /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

来了解下高大上的高并发分布式----redis实战分布式锁_第3张图片
搭建完毕,我们访问下
来了解下高大上的高并发分布式----redis实战分布式锁_第4张图片
来了解下高大上的高并发分布式----redis实战分布式锁_第5张图片

二、Jmeter压测引出分布式锁

默认是50个库存

运行如下:

来了解下高大上的高并发分布式----redis实战分布式锁_第6张图片
来了解下高大上的高并发分布式----redis实战分布式锁_第7张图片
可以很明显的发现超卖了,因为两边有相同的剩余库存。所以我们知道

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

synchronized 的加锁是JVM级别,也就是集群下是不会有效的。那对于分布式的场景应该怎么控制呢?分布式锁就来了。


我的之前博客说过这么一句话setnx,redis里没有就会执行成功

    String lockKey = "product_001";
        //redis里没有就会执行成功
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "tzlock");
        if (!result) {
     
            return "error_code";
        }
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
        if (stock > 0) {
     
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            System.out.println("扣减成功,剩余库存:" + realStock);
        } else {
     
            System.out.println("扣减失败,库存不足");
        }
        stringRedisTemplate.delete(lockKey);
        return "end";

那我们看下结果8090服务器
来了解下高大上的高并发分布式----redis实战分布式锁_第8张图片
8080服务器
来了解下高大上的高并发分布式----redis实战分布式锁_第9张图片
可以很明显的发现锁是成功的了

三、进一步优化

那这么写有没有什么问题

@RequestMapping("/deduct_stock0")
public String deductStock0() {
     
    String lockKey = "product_001";
    //redis里没有就会执行成功
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "tzlock");
    if (!result) {
     
        return "error_code";
    }
    int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
    if (stock > 0) {
     
        int realStock = stock - 1;
        stringRedisTemplate.opsForValue().set("stock", realStock + "");
        System.out.println("扣减成功,剩余库存:" + realStock);
    } else {
     
        System.out.println("扣减失败,库存不足");
    }
    stringRedisTemplate.delete(lockKey);
    return "end";
}

如果发生异常怎么办?所以要加处理
来了解下高大上的高并发分布式----redis实战分布式锁_第10张图片
如果执行到中间的代码,直接宕机了?就要加个超时时间,我们要把超时的设置和key绑定起来。

@RequestMapping("/deduct_stock0")
public String deductStock0() {
     
    String lockKey = "product_001";
    String clientId = UUID.randomUUID().toString();
    try{
     
        //redis里没有就会执行成功
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
        if (!result) {
     
            return "error_code";
        }
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
     
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            System.out.println("扣减成功,剩余库存:" + realStock);
        } else {
     
            System.out.println("扣减失败,库存不足");
        }
        stringRedisTemplate.delete(lockKey);
    } finally {
     
        if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
     
            stringRedisTemplate.delete(lockKey);
        }
    }
    return "end";
}

String clientId = UUID.randomUUID().toString();代表我自己线程加的锁不能被别的线程动。谁加的锁谁去释放!!

到这里,我们的分布式锁其实已经是差不多了,那我们看下其他的方案,介绍一款成熟的框架Redisson

四、Redisson

这个在分布式场景下功能很强大,分布式锁,分布式对象等等

在启动类加入

@SpringBootApplication
public class Application {
     

    public static void main(String[] args) {
     
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Redisson redisson() {
     
        // 此为单机模式
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.3.27:6379").setDatabase(0);
        //集群模式
        //config.useClusterServers().addNodeAddress("你的ip");
        return (Redisson) Redisson.create(config);
    }

}

IndexController看下

@RequestMapping("/deduct_stockByRedision")
    public String deductStock() {
     
        String lockKey = "product_001";
        RLock redissonLock = redisson.getLock(lockKey);
        try {
     
            //加锁
            redissonLock.lock();  // setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS)
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
            if (stock > 0) {
     
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
     
                System.out.println("扣减失败,库存不足");
            }
        } finally {
     
            redissonLock.unlock();
        }
        return "end";
    }

redisson的原理可以参考此图
来了解下高大上的高并发分布式----redis实战分布式锁_第11张图片
它是利用lua脚本来保证原子操作。

你可能感兴趣的:(redis)