我是靠谱客的博主 可靠老虎,最近开发中收集的这篇文章主要介绍来了解下高大上的高并发分布式----redis实战分布式锁前言一、搭建环境二、Jmeter压测引出分布式锁三、进一步优化四、Redisson,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Redis高并发分布式锁实战

  • 前言
    • 分布式锁的应用场景
  • 一、搭建环境
  • 二、Jmeter压测引出分布式锁
  • 三、进一步优化
  • 四、Redisson
    • 主从架构锁失效情况
    • 压测


前言

分布式锁的应用场景

  • 互联网秒杀

  • 抢优惠卷

  • 接口幂等性校验

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


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

一、搭建环境

我们要搭建这么个模拟环境
在这里插入图片描述

项目:
在这里插入图片描述
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

在这里插入图片描述
搭建完毕,我们访问下
在这里插入图片描述
在这里插入图片描述

二、Jmeter压测引出分布式锁

默认是50个库存

运行如下:

在这里插入图片描述
在这里插入图片描述
可以很明显的发现超卖了,因为两边有相同的剩余库存。所以我们知道

//加锁
 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服务器
在这里插入图片描述
8080服务器
在这里插入图片描述
可以很明显的发现锁是成功的了

三、进一步优化

那这么写有没有什么问题

@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";
}

如果发生异常怎么办?所以要加处理
在这里插入图片描述
如果执行到中间的代码,直接宕机了?就要加个超时时间,我们要把超时的设置和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的原理可以参考此图

采用lua脚本 hset product_1 1000 1,假设线程id为1000的抢到了。

加锁后,每隔10秒判断当前线程(抢到锁的线程)是否还持有锁,有的话进行续命30秒。

支持可重入锁,和JUC中的可重入锁机制一样,利用hash的命令incrby对持有锁的线程的值加1。释放锁的时候每个减1。

没有抢到锁的线程,判断锁的还有多久时间失效,失效后去抢锁。
在这里插入图片描述

主从架构锁失效情况

若出现主从架构的锁失效情况,可以采用RedLock来实现,但是不推荐,因为原理是至少有1个从节点响应成功才能保证加锁成功。性能吞吐量上会受影响。
若是出现网络波动还得回滚redis。
当然若非要保证一致性,Zookeeper可以实现,但是性能上不如redis。

压测

库存为50
在这里插入图片描述
库存为50的zk

在这里插入图片描述
库存为100时
在这里插入图片描述
库存为100时的zk
在这里插入图片描述

最后

以上就是可靠老虎为你收集整理的来了解下高大上的高并发分布式----redis实战分布式锁前言一、搭建环境二、Jmeter压测引出分布式锁三、进一步优化四、Redisson的全部内容,希望文章能够帮你解决来了解下高大上的高并发分布式----redis实战分布式锁前言一、搭建环境二、Jmeter压测引出分布式锁三、进一步优化四、Redisson所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(34)

评论列表共有 0 条评论

立即
投稿
返回
顶部