广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Redis分布式锁介绍与使用
  • 716
分享到

Redis分布式锁介绍与使用

2024-04-02 19:04:59 716人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录分布式锁业务逻辑分析Redis命令代码实现分布式锁误删问题问题原因分析代码实现lua脚本首先,使用idea模拟搭建一个Tomcat服务器集群,并使用Nginx对集群中的服务器实现

首先,使用idea模拟搭建一个Tomcat服务器集群,并使用Nginx对集群中的服务器实现负载均衡

配置完负载均衡之后,发送两次请求就会在idea的运行窗口中发现,两次请求的运行是分别在两个服务器中完成,这就是集群的轮询机制

分布式锁

业务逻辑分析

  在单JVM虚拟机多线程执行的情况下,可以使用JVM内部的锁机制来控制多进程的并发执行,借此可以保证一个用户只能下一个优惠券订单。但是在分布式的情况下,每一个JVM虚拟机都有一个锁监视器,不同JVM里的不同线程之间的访问的并不是同一个锁监视器,所以说此时再使用synchronized锁就无法满足一个用户限买一单的业务情况了,于是就需要使用分布式锁

  分布式锁就是满足分布式系统或集群模式下多进程可见并且互斥的锁。一般实现分布式锁的技术主要就是Mysql、Redis和ZooKeeper,但是综合对比来看的话,Redis作分布式锁的性能更高一些,Redis是在JVM虚拟机之外的一种应用可以满足多线程都可见,互斥可以使用setnx这种的互斥命令来实现,但是使用Redis会存在安全性问题,如果Redis崩溃的话会导致锁无法释放而出现死锁现象,解决这一问题的方案就是使用TTL过期时间,就算崩溃也可以实现到期自动释放。

Redis命令

  使用Redis实现分布式锁的步骤主要就是使用setnx体现互斥锁,然后expire过期时间防止宕机死锁,但是如果服务在setnx之后expire之前宕机的话,依旧会造成死锁现象。于是我们可以使用以下命令在互斥的同时设置超时时间,这样的话即是在设置锁之后宕机,依旧可以凭借超时时间释放锁

SET lock thread NX EX ttl超时时间

代码实现

  将获取锁和释放锁业务抽取出来,使用接口和实现类来完成

public interface ILock {
    
    boolean tryLock(long timeoutSec);
    
    void unLock();
}
public class SimpleRedisLock implements ILock {
    private String name;
    
    private StringRedisTemplate stringRedisTemplate;
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    public boolean tryLock(long timeoutSec) {
        // 获取当前操作线程的标识
        long threadId = Thread.currentThread().getId();
        // 获取锁
        Boolean res = stringRedisTemplate.opsForValue()
                .setIfAbsent(RedisConstants.KEY_PREFIX + name, threadId + "", timeoutSec, TimeUnit.SECONDS);
        // res是Boolean的包装类,返回结果的时候涉及到拆箱问题,有可能存在结果为null的情况,此时就需要返回结果与true的比较,避免了空指针风险
        return Boolean.TRUE.equals(res);
    }
    @Override
    public void unLock() {
        // 释放锁
        stringRedisTemplate.delete(RedisConstants.KEY_PREFIX + name);
    }
}

  定义了分布式锁的获取和释放,接下来就是在一人一单业务代码中将锁机制升级成多线程锁了,主要修改的代码为就是5~14行,由单体的synchronized锁改为使用自定义的Redis锁,并根据不同线程获取锁的不同结果定义了不同的业务

public Result secKillVoucher(Long voucherId) {
    // 单用户id(拦截器中做登录验证的用户id)
    Long userId = UserHolder.getUser().getId();
    // 创建锁对象
    SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
    // 获取锁
    boolean isLock = lock.tryLock(1200);
    // 判断是否获取锁成功
    if (!isLock) {
        // 获取锁失败,返回错误或者重试
        return Result.fail("不允许重复下单!" );
    }
    // 获取锁成功,继续下单的业务逻辑
    try {
        // 查询优惠券
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        // 获取时间 判断秒杀活动是否开始或者结束
        if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("活动暂未开始");
        } else if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("活动已经结束");
        }
        // 判断库存是否充足
        if (seckillVoucher.getStock() < 1) {
            return Result.fail("库存不足,活动结束");
        }
        // user_id和voucher_id联合查询订单数
        int count = query().eq("user_id", userId)
                .eq("voucher_id", voucherId)
                .count();
        // 订单数为1 就说明已经下过单了
        if (count > 0) {
            return Result.fail("您已经购买过该商品了");
        }
        // 扣减库存
        boolean update = seckillVoucherService.update()
                .setsql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();
        if (!update) {
            return Result.fail("库存不足!");
        }
        // 创建订单 并返回id
        VoucherOrder order = new VoucherOrder();
        // 订单id(redis全局唯一id) 下单用户id(拦截器中做登录验证的用户id) 优惠券id(直接传过来的id)
        long orderId = generator.nextId("order");
        order.setId(orderId);
        order.setUserId(userId);
        order.setVoucherId(voucherId);
        save(order);
        return Result.ok(orderId);
    } finally {
        // 释放锁
        lock.unLock();
    }
}

分布式锁误删问题

问题原因分析

  这个问题出现在Redis锁设置的超时时间上,由于设置了超时时间,所以可能出现一下情况:即当线程1获取到锁之后执行下单业务,但是由于业务堵塞锁已经超出TTL时间自动释放;此时线程2趁机获取Redis锁成功执行下单业务,线程2的下单业务执行到一半时线程1完成下单使用del命令释放锁;此时线程1释放的是线程2的锁,于是现在锁又处于闲置状态,于是线程3来获取Redis锁成功执行下单业务;此时,一共有同一个用户的两个线程在同时操作

  为了解决以上出现的问题,需要在每次释放锁之前都通过锁的线程标识(Redis锁对应的值)判断一下是不是自己的锁,如果是就使用del命令释放锁,否则就不做操作。但是有一点值得注意,之前锁的线程标识使用的是线程的name,这样的话很容易就造成不同JVM虚拟机里的线程name冲突影响判断,于是可以使用UUID随机生成一组数字加上线程name作为线程的标识,这样更能确保唯一性

代码实现

  综上所述,一共有两处需要改进的地方,一个是使用UUID加线程name作为线程标识(主要修改的是获取锁方法加上UUID的获取),一个是在使用del释放锁之前判断一下是否是自己的锁

public static final String ID_PREFIX = UUID.randomUUID(true) + "-";
public boolean tryLock(long timeoutSec) {
    // 获取当前操作线程的标识
    String threadId = RedisConstants.ID_PREFIX + Thread.currentThread().getId();
    // 获取锁
    Boolean res = stringRedisTemplate.opsForValue()
            .setIfAbsent(RedisConstants.KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
    // res是Boolean的包装类,返回结果的时候涉及到拆箱问题,有可能存在结果为null的情况,此时就需要返回结果与true的比较,避免了空指针风险
    return Boolean.TRUE.equals(res);
}
public void unLock() {
    // 获取当前操作线程的标识
    String threadId = RedisConstants.ID_PREFIX + Thread.currentThread().getId();
    // 通过锁名 获取redis中存储的锁对应的标识
    String rid = stringRedisTemplate.opsForValue().get(RedisConstants.KEY_PREFIX + name);
    if (threadId.equals(rid)) {
        // 释放锁
        stringRedisTemplate.delete(RedisConstants.KEY_PREFIX + name);
    }
}

Lua脚本

  Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:传送门

使用Redis命令调用脚本的常见命令可以是:

EVAL “redis.call(‘set’, ‘key’, ‘value’)” num

  上述命令解释为EVAL是调用,后面双引号中就是所调用的脚本语句,而最后的num即脚本语句中的KEYS类型参数的个数,num之外的就是ARGV(value)类型的参数。比如说,接下来这一个语句就代表着:setname为Rose,其中KEYS类型的参数有1个,就是num后面的第一个name,剩下的都是ARGV(value)类型的数据,其中调用的是KEYS[1]和ARGV[2],也就是name和Rose

EVAL “redis.call(‘set’, ‘KEYS[1]’, ‘ARGV[2]’)” 1 name age Rose

到此这篇关于Redis分布式锁介绍与使用的文章就介绍到这了,更多相关Redis分布式锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Redis分布式锁介绍与使用

本文链接: https://www.lsjlt.com/news/167690.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Redis分布式锁介绍与使用
    目录分布式锁业务逻辑分析Redis命令代码实现分布式锁误删问题问题原因分析代码实现Lua脚本首先,使用idea模拟搭建一个tomcat服务器集群,并使用Nginx对集群中的服务器实现...
    99+
    2022-11-13
  • Redis分布式锁详细介绍
    目录分布式锁redis实现分布式锁的原理死锁问题超时问题锁误放问题可重入性Redlock分布式锁 在单进程应用中,当一段代码同一时间内只能由一个线程执行时, 多线程下可能会出错,例如...
    99+
    2022-11-12
  • etcd与分布式锁的介绍
    本篇内容主要讲解“etcd与分布式锁的介绍”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“etcd与分布式锁的介绍”吧!1. 实现分布式锁的组件们在分布式系统中,...
    99+
    2022-10-19
  • 巧用Redis实现分布式锁详细介绍
    目录前言手写Redis分布式锁Redissonlock()lock(long leaseTime, TimeUnit unit)tryLock(long waitTime, long...
    99+
    2022-11-12
  • Redis分布式锁的实现原理介绍
    这篇文章主要讲解了“Redis分布式锁的实现原理介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Redis分布式锁的实现原理介绍”吧!一、写在前面现在面试,一般都会聊聊分布式系统这块的东西...
    99+
    2023-06-02
  • Golang分布式锁详细介绍
    目录进程内加锁trylock基于redis的setnx基于zk基于etcdredlock如何选择在单机程序并发或并行修改全局变量时,需要对修改行为加锁以创造临界区。为什么需要加锁呢?...
    99+
    2022-11-11
  • redis分布式锁与zk分布式锁的对比分析
    目录Redis实现分布式锁原理能实现的锁类型注意事项 zk实现分布式锁原理能实现的锁类型两种锁的对比在分布式环境下,传统的jvm级别的锁会失效,那么分布式锁就是非常有必要的一个技术,一般我们可以通过redis,...
    99+
    2022-11-18
    redis分布式锁 分布式锁 zk分布式锁
  • go语言分布式id生成器及分布式锁介绍
    目录分布式 id 生成器worker_id 如何分配开源示例:标准雪花算法分布式锁进程内加锁尝试加锁 tryLock基于 Redis 的 setnx 分布式锁基于 ZooKeeper...
    99+
    2023-05-14
    go 分布式id生成器 锁 go 分布式锁
  • 怎么使用redis分布式锁
    本篇内容介绍了“怎么使用redis分布式锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. redis在实际的应用中不仅可以用来缓存数据,...
    99+
    2023-06-25
  • redis分布式锁怎么使用
    使用Redis分布式锁的一般步骤如下: 获取锁:在Redis中使用SET命令尝试设置一个带有过期时间的键值对作为锁。可以使用命令...
    99+
    2023-10-21
    redis
  • 详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)
    目录1. redis在实际的应用中2.如何使用redis的功能进行实现分布式锁2.1 redis分布式锁思想2.1.1设计思想:2.1.2 根据上面的设计思想进行代码实现2.2 使用...
    99+
    2022-11-12
  • Redis中锁的介绍和使用
    其实说多线程修改数据也不合适,毕竟redis服务端是单线程的,所有命令串行执行,只是在客户端并发发送命令的时候,导致串行的命令一些排列问题和网络时间差等造成数据不一致。本文虽然是数字的加减,但是为了说明锁的...
    99+
    2022-10-18
  • Redis数据库分布式设计方案介绍
    目录1 哈希取余分区2 一致性哈希算法分区2.1 一致性哈希环2.2 节点映射2.3 落键规则 2.4 优缺点3 哈希槽计算总结问题:1-2亿数据需要缓存,如何设计? 1 ...
    99+
    2022-11-13
  • Go与Redis实现分布式互斥锁和红锁
    目录前言互斥锁TryLock和Unlock实现Lock实现实现看门狗机制看门狗实现红锁加锁实现看门狗实现解锁实现前言 在项目中我们经常有需要使用分布式锁的场景,而Redis是实现分布式锁最常见的一种方式,这篇文章主要是使...
    99+
    2022-09-29
  • 使用Redis如何实现分布式锁
    这篇文章主要介绍了使用Redis如何实现分布式锁的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇使用Redis如何实现分布式锁文章都会有所收获,下面我们一起来看看吧。 ...
    99+
    2022-10-19
  • 如何使用Redis实现分布式锁
    这篇文章将为大家详细讲解有关如何使用Redis实现分布式锁,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。加锁部分解锁部分主要原理是使用了 redis 的 s...
    99+
    2022-10-18
  • Laravel中怎么使用Redis分布式锁
    这篇文章主要介绍“Laravel中怎么使用Redis分布式锁”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Laravel中怎么使用Redis分布式锁”文章能帮助大家解决问题。创建锁use ...
    99+
    2023-07-04
  • Redis分布式非公平锁的使用
    目录前言redis分布式锁第一版redis分布式锁第二版redis分布式锁第三版redis分布式锁最终版前言 看了很多博客,和资料,这里只针对redis做分布式锁做一下深入探讨,希望...
    99+
    2022-11-12
  • Redis分布式锁怎么应用
    这篇文章主要讲解了“Redis分布式锁怎么应用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Redis分布式锁怎么应用”吧!分布式锁在单进程应用中,当一段代码同一时间内只能由一个线程执行时,...
    99+
    2023-06-21
  • Redis分布式锁有什么用
    这篇文章给大家分享的是有关Redis分布式锁有什么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1. 什么是分布式锁分布式与单机情况下最大的不同在于其不是多线程而是多进程,而数据只有一份(或有限制),也就是说单...
    99+
    2023-06-25
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作