基于redis实现分布式锁

Posted BUG速递

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于redis实现分布式锁相关的知识,希望对你有一定的参考价值。

 在上一篇文章中介绍了动态配置定时任务,其中的原理跟spring 定时任务注解@Scheduled一样的,都是通过线程池和定义执行时间来控制。来思考一个问题,如果我们的定时任务在分布式微服务里面呢?在分布式微服务里面一个微服务肯定可以有多个实例的,在上一篇文章当中配置的定时任务就会有可能存在多个,显然定时任务被多次执行并不是我们想要的结果,这个时候我们的分布式锁机制就出现了!



(分布式锁有很多实现方式,以前我们都是使用synchronized来处理并发请求,虽然也支持分布式,但是总有一些业务不适合,比如秒杀系统的多个商品同时开启秒杀,同一时刻只能完成一件商品的减库存操作,这样就造成了系统的性能瓶颈,也不符合秒杀系统的设计思想。由于 synchronized 无法做到细粒度的控制,从而引进了分布式锁,分布式锁能够完成 synchronized 无法做到的点。下面我们要介绍的是基于redis的实现方式)。



01



引入redis依赖



引入springboot官方的redis依赖。

引入一个hutool工具包的依赖,功能很全的一个java工具包,强烈推荐使用。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.2</version></dependency>



02




基于redis实现


怎么使用redis实现呢,先来看下redis的两个命令。

setnx:如果key不存在就跟set一样的作用,如果key存在则什么都不做

getandset:返回上一次的value,并设置新的value

import cn.hutool.core.util.StrUtil;import cn.hutool.log.StaticLog;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Component;
/** * redis分布式锁 * * @author zhongxiaojian * @date 2020/4/17 **/@Componentpublic class LockUtil {
@Autowired private StringRedisTemplate redisTemplate;
/** * 加锁 * * @param key 主键 * @param value 当前时间+超时时间 * @return true or false */ public boolean lock(String key, String value) { Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, value); if (lock != null && lock) { return true; } String currentValue = redisTemplate.opsForValue().get(key); //如果锁过期 if (!StrUtil.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) { String oldValue = redisTemplate.opsForValue().getAndSet(key, value); if (StrUtil.isBlank(oldValue) || (!StrUtil.isEmpty(oldValue) && oldValue.equals(currentValue))) { return true; } } return false; }
/** * 解锁 * * @param key 主键 * @param value 当前时间+超时时间 */ public void unlock(String key, String value) { try { String currentValue = redisTemplate.opsForValue().get(key); if (!StrUtil.isEmpty(currentValue) && currentValue.equals(value)) { redisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { StaticLog.error("redis分布式锁解锁异常,{}", e.getMessage()); } }}

在上一篇文章当中的代码中使用

public class ScheduleTask implements Runnable {
private static final int TIMEOUT = 30000;
private String id; private TaskService service; private String keyword; private LockUtil lockUtil;
public String getId() { return id; }
/** * @param id 任务ID * @param service 业务类 * @param keyword 关键字参数 */ public ScheduleTask(String id, TaskService service,LockUtil lockUtil, String keyword) { this.id = id; this.service = service; this.lockUtil = lockUtil; this.keyword = keyword; }
@Override public void run() { String currentTime = DateUtil.now(); long time = System.currentTimeMillis() + TIMEOUT; if (lockUtil.lock(id, String.valueOf(time))) { System.out.println("ScheduleTask start taskId: " + this.id + " time: " + currentTime);
try { service.work(keyword); } catch (Exception e) { StaticLog.error(e.getMessage()); } finally { lockUtil.unlock(id, String.valueOf(time)); } } }}



03




秒杀系统下的应用


这里我们来解释一下为何在lock方法当中加上 “//如果锁过期” 后面的代码,我们以商品秒杀系统举例比较好理解。


假如我们不加上这段代码,在加锁之后的业务流程抛出了一个异常,且这个异常我们没有捕获并处理,那么我们接下来的解锁操作是不会执行的,这个时候我们的锁就变成了死锁,我们就可以使用getandset命令来进行解锁,举个

以上是关于基于redis实现分布式锁的主要内容,如果未能解决你的问题,请参考以下文章

Redis实现分布式锁(设计模式应用实战)

redis基于redis实现分布式并发锁

基于 Redis 分布式锁实现“秒杀”(含代码)

基于Redis实现分布式锁

基于redis和zookeeper的分布式锁实现方式

分布式锁的两种实现方式(基于redis和基于zookeeper)