springMVC 实现redis分布式锁
1.先配置spring-data-redis
首先是依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.4.RELEASE</version>
</dependency>
redisconfig 配置类
@Configuration
@PropertySource("classpath:irongbei.properties")
public class RedisConfig extends JCacheConfigurerSupport {
@Autowired
private Environment environment; @Bean
public RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory fac = new JedisConnectionFactory();
fac.setHostName(environment.getProperty("redis.host"));
fac.setPort(Integer.parseInt(environment.getProperty("redis.port")));
fac.setPassword(environment.getProperty("redis.password"));
fac.setTimeout(Integer.parseInt(environment.getProperty("redis.timeout")));
// fac.getPoolConfig().setMaxIdle(Integer.parseInt(environment.getProperty("redis.maxIdle")));
// fac.getPoolConfig().setMaxTotal(Integer.parseInt(environment.getProperty("redis.maxTotal")));
// fac.getPoolConfig().setMaxWaitMillis(Integer.parseInt(environment.getProperty("redis.maxWaitMillis")));
// fac.getPoolConfig().setMinEvictableIdleTimeMillis(
// Integer.parseInt(environment.getProperty("redis.minEvictableIdleTimeMillis")));
// fac.getPoolConfig()
// .setNumTestsPerEvictionRun(Integer.parseInt(environment.getProperty("redis.numTestsPerEvictionRun")));
// fac.getPoolConfig().setTimeBetweenEvictionRunsMillis(
// Integer.parseInt(environment.getProperty("redis.timeBetweenEvictionRunsMillis")));
// fac.getPoolConfig().setTestOnBorrow(Boolean.parseBoolean(environment.getProperty("redis.testOnBorrow")));
// fac.getPoolConfig().setTestWhileIdle(Boolean.parseBoolean(environment.getProperty("redis.testWhileIdle")));
return fac;
} @Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> redis = new RedisTemplate<>();
// 设置redis的String/Value的默认序列化方式
DefaultStrSerializer defaultStrSerializer = new DefaultStrSerializer();
redis.setKeySerializer(defaultStrSerializer);
redis.setValueSerializer(defaultStrSerializer);
redis.setHashKeySerializer(defaultStrSerializer);
redis.setHashValueSerializer(defaultStrSerializer);
redis.setConnectionFactory(redisConnectionFactory);
redis.afterPropertiesSet();
return redis;
}
} class DefaultStrSerializer implements RedisSerializer<Object> {
private final Charset charset; public DefaultStrSerializer() {
this(Charset.forName("UTF8"));
} public DefaultStrSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
} @Override
public byte[] serialize(Object o) throws SerializationException {
return o == null ? null : String.valueOf(o).getBytes(charset);
} @Override
public Object deserialize(byte[] bytes) throws SerializationException {
return bytes == null ? null : new String(bytes, charset); }
}
redisLock类 分布式锁实现的类
@Component
public class RedisLock { private static final Logger log = LoggerFactory.getLogger(RedisLock.class); @Autowired
private RedisTemplate<String, String> redisTemplate; /**
*
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key,String value){ if(redisTemplate.opsForValue().setIfAbsent(key,value)){
log.info(" [Redis分布式锁] key:[{}] 获取到锁",key);
return true;
}
//oldvalue 俩个线程都返回A
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期->这里如果一个key的value时间是小于当前当前时间 那就是过期了,如果大于当前时间才没有过期
if(StringUtils.isNotEmpty(currentValue) && Long.parseLong(currentValue) <System.currentTimeMillis()){ //获取上一个锁的时间
//第一个线程获取上一个oldvalue 然后设置一个新的值进去 第二个线程就获取到是新的值.
String oldValue = redisTemplate.opsForValue().getAndSet(key,value);
//3 这一步就只有第一个线程能匹配到了 第二个线程就获取不到了
if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)){
log.info(" [Redis分布式锁] key:[{}] 获取到锁",key);
return true;
} } return false; } /**
* 解锁
* @param key
* @param value
*/
public void unlock(String key ,String value){
try {
String currentValue = redisTemplate.opsForValue().get(key);
if(StringUtils.isNotEmpty(currentValue) && currentValue.equals(value)){
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error(" [redis分布式锁] 解锁异常, {} ",e);
} }
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {"/spring-context.xml","/spring-context-jedis.xml","/spring-context-shiro.xml"})
public class RedisLockTest {
@Resource(name = "threadPoolTaskExecutor")
private ThreadPoolExecutor taskExecutor; @Autowired
private RedisLock redisLock;
private long timeout = 5*1000; private static final Logger logger =LoggerFactory.getLogger(RedisLockTest.class); @Test
public void test (){
for (int i = 0; i <200 ; i++) {
taskExecutor.execute(()->{
String time = String.valueOf(System.currentTimeMillis()+timeout);
if(!redisLock.lock("testlock",time)){
logger.info("哎呦喂..人太多了 在排队中..");
return;
}else {
try {
Thread.sleep(4000);
redisLock.unlock("testlock",time);
} catch (InterruptedException e) {
e.printStackTrace();
}
} });
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}
补充
和同事讨论,结果确实如同事所说,解锁的时候有些不严谨,因为是分俩步操作的.可能会在del之前被别人获取到锁然后再del删除掉别人获取的锁.下面是新的解锁方式,目前有些公司redis服务器不支持这样的命令
为什么使用lua语言在操作Redis 主要是保证操作的原子性.一步操作
但是使用lua要非常小心,如果脚本错误可能会阻塞整个Redis实例
private static final Long SUCCESS = 1L;
/**
* 释放锁
* @param key
* @param value
* @return
*/
public boolean releaseLock(String key, String value) {
key =prifix+key;
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(key), value);
System.out.println(result);
if (SUCCESS.equals(result)) {
log.info("[redis 分布式锁解锁成功] key:[{}] 结果:{}",key,result);
return true;
}
log.info("[redis 分布式锁解锁失败] key:[{}] 结果{}",key,result);
} catch (Exception e) {
log.info("[redis 分布式锁解锁失败] key:[{}] msg:{}",key,e.getMessage());
e.printStackTrace();
} return false;
}
测试代码
@Test
public void testPrefix(){
redisLock.lock("test","10000");
redisLock.lock("test1","10000");
// redisLock.lock("test2","10000");
// redisLock.lock("test3","10000");
redisLock.releaseLock("test","10000");
redisLock.releaseLock("test1","10002");
}
springMVC 实现redis分布式锁的更多相关文章
- Lua脚本在redis分布式锁场景的运用
目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...
- 利用redis分布式锁的功能来实现定时器的分布式
文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...
- Redis分布式锁
Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...
- redis分布式锁和消息队列
最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...
- redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
- spring boot redis分布式锁
随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...
- Redis分布式锁的正确实现方式
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
- Redis分布式锁---完美实现
这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...
- redis分布式锁实践
分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...
随机推荐
- 数组属性的习题、Arrays工具、二维数组
一.数组的练习 1.声明一个char类型的数组, 从键盘录入6个字符: [1]遍历输出 [2]排序 [3]把char数组转化成一个逆序的数组. import java.util.Scanner; pu ...
- 基于vue-cli构建vue-router的入门级demo
前言 本案列仅针对刚刚入门vue学习的伙伴,博主也是刚刚在学基于vue-cli搭建脚手架项目,对于前端大牛,可以移步. 快速搭建vue-cli环境 如何搭建基于vue-cli项目,这里不再叙述,如果不 ...
- python接口自动化(六)--发送get请求接口(详解)
简介 如果想用python做接口测试,我们首先有不得不了解和学习的模块.它就是第三方模块:Requests. 虽然Python内置的urllib模块,用于访问网络资源.但是,它用起来比较麻烦,而且,缺 ...
- 【Android Studio安装部署系列】十九、Android studio使用SVN
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 在AndroidStudio中开发版本控制,除了Git就是SVN,和Eclipse不同,Android Studio没有提供单独的插 ...
- 分布式事务解决方案FESCAR
项目地址:FESCAR 以下是官网的文档.简介2019年,Fescar 是 阿里巴巴 开源的 分布式事务中间件,以 高效 并且对业务 0 侵入 的方式,解决 微服务 场景下面临的分布式事务问题. 1. ...
- Asp.Net Core 轻松学-实现跨平台的自定义Json数据包
前言 在前后端分离的业务开发中,我们总是需要返回各种各样的数据包格式,一个良好的 json 格式数据包是我们一贯奉行的原则,下面就利用 Json.Net 来做一个简单具有跨平台的序列化数据包实 ...
- SmartSql 快速使用指南
SmartSql 快速使用指南(https://github.com/Ahoo-Wang/SmartSql) ISmartSqlMapper 常用(部分)接口概述 函数 说明 Execute IDbC ...
- 命令行程序增加 GUI 外壳
Conmajia © 2012 Updated on Feb. 21, 2018 命令行大家都用过: 图 1 命令行程序工作界面 现在想办法为它做一个 GUI 外壳,实际效果参考图 2. 图 2 带 ...
- docker 安装 MySQL 8,并减少内存占用 记录
目前vps 1cpu 512m内存 MySQL内存占用77% ,约350m ,经过修改配置文件优化后如下 $ ps aux 进入docker bash $ docker exec -it pwc-my ...
- LINQ之道
提到LINQ首先我们要了解什么是委托:委托是一种引用方法的类型.一旦为委托分配了方法,委托将与该方法具有完全相同的行为.也就是说当你委托给一个人办一件事的时候,他就拥有这个能力去实现这件事,同样委托也 ...