spring mvc + redis 实现分布式锁(注解实现可自动重试)

本文介绍了如何在Spring MVC中通过Redis实现分布式锁,重点在于注解的使用,允许自定义重试次数和超时时间。讨论了分布式锁的可靠性要求,包括互斥性、防止死锁、容错性和一致性。并展示了在并发情况下,未使用分布式锁可能导致的问题,以及如何通过分布式锁解决这些问题。最后,详细展示了从配置到使用的整个实现过程,包括POM配置、接口定义、抽象类、具体实现、注解类、切面处理以及应用配置。
摘要由CSDN通过智能技术生成

说明

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于 Redis 的分布式锁;3. 基于 ZooKeeper 的分布式锁。本文介绍基于 Redis 实现分布式锁。

关于实现分布式锁的三种方式,可以参考之前的博文: 分布式锁简单入门以及三种实现方式介绍

本文中的分布式锁通过注解的方式实现,可以自定义重试次数,锁超时时间等。

可靠性

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 具有容错性。只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁。
  4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

并发问题

在没有使用分布式锁之前,如果有两个线程并发操作同一条数据,可能会出现并发问题(脏读、不可重复读、幻读)。

举个例子,下面是一段将数据的值 +1 的代码:

public void addNum() {
   
    try {
   
        // 查询数据
        TE te = teManager.findOne(1L);
        System.out.println("start:" + te.getNum());
        // 模拟耗时操作
        Thread.sleep(2000);
        // 值加1
        te.setNum(te.getNum() + 1);
        // 保存数据
        teManager.save(te);
        System.out.println("end:" + te.getNum());
    } catch (Exception e) {
   

    }
}

有两个线程同时访问:

15:56:06,241 INFO  [stdout] (default task-39) start:0
15:56:07,548 INFO  [stdout] (default task-40) start:0
15:56:08,242 INFO  [stdout] (default task-39) end:1
15:56:09,555 INFO  [stdout] (default task-40) end:1

可以看到 task-39 和 task-40 两个线程读取到的值均是 0,执行两次后,值为 1 ,并不是想要的 2。

具体执行的情况如下:

task-39 task-40
读取 num,num = 0
读取 num,num = 0
num = num + 1 ,num 值变为 1
num = num + 1 ,num 值变为 1
将 1 存入库中
将 1 存入库中

如果是单机部署,那么可以用多线程的 18 般武艺来解决并发问题,比如加锁等,改动如下:

public synchronized void addNum() {
   
    try {
   
        // 查询数据
        TE te = teManager.findOne(1L);
        System.out.println("start:" + te.getNum());
        // 模拟耗时操作
        Thread.sleep(2000);
        // 值加1
        te.setNum(te.getNum() + 1);
        // 保存数据
        teManager.save(te);
        System.out.println("end:" + te.getNum());
    } catch (Exception e) {
   

    }
}

加了一个关键字 synchronized 就能解决单机下的并发问题,结果如下:

16:09:49,539 INFO  [stdout] (default task-46) start:0
16:09:51,541 INFO  [stdout] (default task-46) end:1
16:09:51,592 INFO  [stdout] (default task-47) start:1
16:09:53,597 INFO  [stdout] (default task-47) end:2
task-46 task-47
读取 num,num = 0
num = num + 1 ,num 值变为 1
将 1 存入库中
读取 num,num = 1
num = num + 1 ,num 值变为 2
将 2 存入库中

如果集群部署的话,这种方式就无法解决了(每台机器的 JVM 无法共享,无法加锁),只能使用分布式锁。

分布式锁的实现

pom.xml:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.10.2</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version<
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值