spring boot基于redis的LUA脚本 实现分布式锁【都是基于redis单点下】

一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁

1.pom.xml

<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.RedisLock 工具类 (注入spring)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.util.Collections; /**
* spring boot 1.5.X
* 使用redis 的 lua脚本 基于单点实现分布式锁
*
* lua脚本作为原子性操作,保证加锁和设置超时时间 为原子性操作
* @author sxd
* @date 2019/5/27 10:52
*/
@Component
public class RedisLock { @Autowired
RedisTemplate redisTemplate; private static final Long SUCCESS = 1L; /**
* 获取锁
*
* @param lockKey redis的key
* @param value redis的value要求是随机串,防止释放其他请求的锁
* @param expireTime redis的key 的过期时间 防止死锁,导致其他请求无法正常执行业务
* @return
*/
public boolean lock(String lockKey, String value, int expireTime) { String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then " +
" if redis.call('get',KEYS[1])==ARGV[1] then " +
" return redis.call('expire',KEYS[1],ARGV[2]) " +
" else " +
" return 0 " +
" end " +
"end"; RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class); //对非string类型的序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, String.valueOf(expireTime)); return SUCCESS.equals(result); } /**
* 释放锁
*
* @param lockKey redis的key
* @param value redis的value 只有value比对一致,才能确定是本请求 加的锁 才能正常释放
* @return
*/
public boolean unlock(String lockKey, String value) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class); try {
Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
if (SUCCESS.equals(result)) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
} }

3.controller使用

/**
* 使用分布式锁 逻辑
* 1.准备好 key value expireTime
* value要求是随机字符串
* expireTime 是根据业务 衡量决定的 锁过期时间
*
* 2.获取锁
* 成功获取,则执行业务,执行完成,释放锁
* 失败获取,则重试获取,注意获取锁的时间间隔,直到获取成功,执行业务,最后释放锁
*
* 注意:
* 对于redis加锁的业务,尽量用在耗时短的业务上。
*
*/
@RequestMapping("/test")
public void test(){
boolean flag = false; //标识 是否正常获取锁
String uuid = UUID.randomUUID().toString(); //redis的value 是一串随机数
flag = lock.lock("mykey1",uuid,5);
if (flag){
business(uuid);
}else { //如果未正常获取锁 可以通过重试 直到获取锁成功
while (!flag){
try { //重试 时间间隔 减少与redis交互次数
Thread.sleep(3000);
System.out.println("重试");
flag = lock.lock("mykey1",uuid,5);
if (flag){
business(uuid);
}else {
continue;
} } catch (InterruptedException e) {
e.printStackTrace();
} }
}
} public void business(String uuid){ try {
System.out.println("加锁成功,执行业务");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//业务执行完成 正常释放锁
lock.unlock("mykey1",uuid);
}
}

二.spring boot 2.x 基于redis 的LUA脚本 实现分布式锁

1.pom.xml

<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.0集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
<!-- 使用redis的LUA脚本 需要序列化操作的jar-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

2.替代SpringBoot自动配置的RedisTemplate的RedisConfig类

package com.sxd.swapping.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import java.io.Serializable; /**
* @author sxd
* @date 2019/5/27 16:13
*/
/**
* @Description Redis配置类,替代SpringBoot自动配置的RedisTemplate,参加RedisAutoConfiguration
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig { @Bean
public RedisTemplate<String, Serializable> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//Jackson序列化器
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//字符串序列化器
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//普通Key设置为字符串序列化器
template.setKeySerializer(stringRedisSerializer);
//Hash结构的key设置为字符串序列化器
template.setHashKeySerializer(stringRedisSerializer);
//普通值和hash的值都设置为jackson序列化器
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
} }

3.RedisLock工具类,自动注入Spring

package com.sxd.swapping.utils;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List; /**
*
* spring boot 2.x版本
* @author sxd
* @date 2019/5/27 16:11
*/
@Component
public class RedisLock2 { Logger logger = Logger.getRootLogger(); static final Long LOCK_SUCCESS = 1L; static final Long LOCK_EXPIRED = -1L; @Autowired
RedisTemplate redisTemplate; //定义获取锁的lua脚本
private final static DefaultRedisScript<Long> LOCK_LUA_SCRIPT = new DefaultRedisScript<>(
"if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('pexpire', KEYS[1], ARGV[2]) else return 0 end"
, Long.class
); //定义释放锁的lua脚本
private final static DefaultRedisScript<Long> UNLOCK_LUA_SCRIPT = new DefaultRedisScript<>(
"if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return -1 end"
, Long.class
); /**
* 加锁
* @param key redis键值对 的 key
* @param value redis键值对 的 value 随机串作为值
* @param timeout redis键值对 的 过期时间 pexpire 以毫秒为单位
* @param retryTimes 重试次数 即加锁失败之后的重试次数,根据业务设置大小
* @return
*/
public boolean lock(String key,String value ,long timeout, int retryTimes) {
try { logger.debug("加锁信息:lock :::: redisKey = " + key + " requestid = " + value);
//组装lua脚本参数
List<String> keys = Arrays.asList(key);
//执行脚本
Object result = redisTemplate.execute(LOCK_LUA_SCRIPT, keys,value,timeout);
//存储本地变量
if(LOCK_SUCCESS.equals(result)) { logger.info("成功加锁:success to acquire lock:" + Thread.currentThread().getName() + ", Status code reply:" + result);
return true;
} else if (retryTimes == 0) {
//重试次数为0直接返回失败
return false;
} else {
//重试获取锁
logger.info("重试加锁:retry to acquire lock:" + Thread.currentThread().getName() + ", Status code reply:" + result);
int count = 0;
while(true) {
try {
//休眠一定时间后再获取锁,这里时间可以通过外部设置
Thread.sleep(100);
result = redisTemplate.execute(LOCK_LUA_SCRIPT, keys);
if(LOCK_SUCCESS.equals(result)) { logger.info("成功加锁:success to acquire lock:" + Thread.currentThread().getName() + ", Status code reply:" + result);
return true;
} else {
count++;
if (retryTimes == count) {
logger.info("加锁失败:fail to acquire lock for " + Thread.currentThread().getName() + ", Status code reply:" + result);
return false;
} else {
logger.warn(count + " times try to acquire lock for " + Thread.currentThread().getName() + ", Status code reply:" + result);
continue;
}
}
} catch (Exception e) {
logger.error("加锁异常:acquire redis occured an exception:" + Thread.currentThread().getName(), e);
break;
}
}
}
} catch (Exception e1) {
logger.error("加锁异常:acquire redis occured an exception:" + Thread.currentThread().getName(), e1);
}
return false;
} /**
* 释放KEY
* @param key 释放本请求对应的锁的key
* @param value 释放本请求对应的锁的value 是不重复随即串 用于比较,以免释放别的线程的锁
* @return
*/
public boolean unlock(String key,String value) {
try { //组装lua脚本参数
List<String> keys = Arrays.asList(key);
logger.debug("解锁信息:unlock :::: redisKey = " + key + " requestid = " + value);
// 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 Object result = redisTemplate.execute(UNLOCK_LUA_SCRIPT, keys, value);
//如果这里抛异常,后续锁无法释放
if (LOCK_SUCCESS.equals(result)) {
logger.info("解锁成功:release lock success:" + Thread.currentThread().getName() + ", Status code reply=" + result);
return true;
} else if (LOCK_EXPIRED.equals(result)) {
//返回-1说明获取到的KEY值与requestId不一致或者KEY不存在,可能已经过期或被其他线程加锁
// 一般发生在key的过期时间短于业务处理时间,属于正常可接受情况
logger.warn("解锁异常:release lock exception:" + Thread.currentThread().getName() + ", key has expired or released. Status code reply=" + result);
} else {
//其他情况,一般是删除KEY失败,返回0
logger.error("解锁失败:release lock failed:" + Thread.currentThread().getName() + ", del key failed. Status code reply=" + result);
}
} catch (Exception e) {
logger.error("解锁异常:release lock occured an exception", e);
} return false;
} }

4.使用

 @Autowired
RedisLock2 lock2; @Autowired
RedisTemplate redisTemplate; @RequestMapping("/test3")
public void test3(){
ValueOperations vops = redisTemplate.opsForValue();
String uuid = UUID.randomUUID().toString(); //加锁
if (lock2.lock("mykey1",uuid,5000,3)){ try {
// 执行业务
System.out.println("加锁成功,做业务"); vops.increment(REDIS_COUNT_KEY,1);
Thread.sleep(3000); System.out.println("业务执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//解锁
lock2.unlock("mykey1",uuid);
}
}
}

【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁的更多相关文章

  1. redis集群+JedisCluster+lua脚本实现分布式锁(转)

    https://blog.csdn.net/qq_20597727/article/details/85235602 在这片文章中,使用Jedis clien进行lua脚本的相关操作,同时也使用一部分 ...

  2. Redis学习笔记(三)使用Lua脚本实现分布式锁

    Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行. 使用Lua脚本的好处如下: 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放 ...

  3. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  4. redis整理:常用命令,雪崩击穿穿透原因及方案,分布式锁实现思路,分布式锁redission(更新中)

    redis个人整理笔记 reids常见数据结构 基本类型 String: 普通key-value Hash: 类似hashMap List: 双向链表 Set: 不可重复 SortedSet: 不可重 ...

  5. redis中使用java脚本实现分布式锁

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/115.html?1455860390 edis被大量用在分布式的环境中,自 ...

  6. 在redis一致性hash(shard)中使用lua脚本的坑

    redis 2.8之前的版本,为了实现支持巨量数据缓存或者持久化,一般需要通过redis sharding模式来实现redis集群,普遍大家使用的是twitter开源的Twemproxy. twemp ...

  7. 【笔记】《Redis设计与实现》chapter20 Lua脚本

    chapter20 Lua脚本 Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务器端原子地执行多个Redis命令 20.1 ...

  8. 【Redis的那些事 · 上篇】Redis的介绍、五种数据结构演示和分布式锁

    Redis是什么 Redis,全称是Remote Dictionary Service,翻译过来就是,远程字典服务. redis属于nosql非关系型数据库.Nosql常见的数据关系,基本上是以key ...

  9. 基于zk“临时顺序节点“的分布式锁

    import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; im ...

随机推荐

  1. ELK日志系统之kibana的使用操作

    1.ELK日志系统打开后,打开kibana的操作界面,第一步创建索引模式: 第2步:创建日志索引 第3步:创建成功 第4步:查看30分钟时间段内的日志数据,也可以查今天的,今月的,今年的 放牛去

  2. Windows 2016 & Windows 10 中IIS安装和配置PHP的步骤

    Windows 2016 和 Windows 10 内核是相同的,我们首先需要安装 Internet Information Services (IIS),当然 Win2016 跟 Win10 安装  ...

  3. 剑指offer-08 二叉树的下一个节点

    剑指offer第8题,本来想找leetcode上对应的题,后来没找到,直接去牛客网上刷了. 题目描述: 给定一个二叉树和其中的一个结点(pNode),请找出中序遍历顺序的下一个结点并且返回.注意,树中 ...

  4. win10 任务栏上的工具栏,重启消失的解决方法

    首先谈下 <任务栏的工具栏> 对于很多人来言,还是有可取性的 任务栏的工具栏对编程者的作用 一般来说,我们会经常查看某些API文档,虽然现在是联网也很方便,但如果都下载下来,整理到一个文件 ...

  5. N种自动化测试框架(包含自动化和性能,总有一款适合你)

    不知不觉,分享的框架已经6个了(准确说应该是4个),仅仅是接口的. 这些框架都是最基础的框架,需要根据实际使用场景进行完善,大家就当练手实践吧. 不需要写代码的自动化框架 JMeter + Ant+ ...

  6. AcWing 24. 机器人的运动范围

    习题地址 https://www.acwing.com/solution/acwing/content/2970/ 题目描述地上有一个 m 行和 n 列的方格,横纵坐标范围分别是 0∼m−1 和 0∼ ...

  7. 18-C#笔记-继承

    1. 子类可以使用父类的成员和函数. 和C++不同,使用的是一个冒号 2. 不支持多重继承 但是可以通过接口(interface)这种结构实现.后续讲解. using System; namespac ...

  8. JavaScript 实用工具库 : lodashjs

    首页地址:https://www.lodashjs.com/

  9. Pandas | 03 DataFrame 数据帧

    数据帧(DataFrame)是二维数据结构,即数据以行和列的表格方式排列. 数据帧(DataFrame)的功能特点: 潜在的列是不同的类型 大小可变 标记轴(行和列) 可以对行和列执行算术运算 结构体 ...

  10. Chrome DevTools的使用

    一.Chrome DevTools 简介 Chrome 开发者工具是一套内置于Google Chrome中的Web开发和调试工具,可用来对网站进行迭代.调试和分析 手册:Chrome 开发者工具中文手 ...