分布式系统定时任务,保证只有一个服务执行了改任务--采用redis分布式锁来实现(文章摘自:https://www.cnblogs.com/0201zcr/p/5942748.html)
文章摘自:https://www.cnblogs.com/0201zcr/p/5942748.html
package com.abtc.server.mine.common.utils; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer; import java.util.UUID; public class RedisLock
{
private static Logger logger = LoggerFactory.getLogger(RedisLock.class); private RedisTemplate redisTemplate; private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 10; // 记录当前获取锁的标识
private String uuid = UUID.randomUUID().toString(); /**
* Lock key path.
*/
private String lockKey; /**
* 锁超时时间,防止线程在入锁以后,无限的执行等待
*/
private int expireMsecs = 60 * 1000; /**
* 锁等待时间,防止线程饥饿
*/
private int timeoutMsecs = 10 * 1000; private volatile boolean locked = false; /**
* Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
*
* @param lockKey lock key (ex. account:1, ...)
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey)
{
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
this.uuid = uuid;
} /**
* Detailed constructor with default lock expiration of 60000 msecs.
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs)
{
this(redisTemplate, lockKey);
this.timeoutMsecs = timeoutMsecs;
} /**
* Detailed constructor.
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs)
{
this(redisTemplate, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
} /**
* @return lock key
*/
public String getLockKey()
{
return lockKey;
} private String get(final String key)
{
Object obj = null;
try
{
obj = redisTemplate.execute(new RedisCallback<Object>()
{
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException
{
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] data = connection.get(serializer.serialize(key));
connection.close();
if (data == null)
{
return null;
}
return serializer.deserialize(data);
}
});
}
catch (Exception e)
{
logger.error("get redis error, key : {}", key);
}
return obj != null ? obj.toString() : null;
} private boolean setNX(final String key, final String value)
{
Object obj = null;
try
{
obj = redisTemplate.execute(new RedisCallback<Object>()
{
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException
{
StringRedisSerializer serializer = new StringRedisSerializer();
Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
connection.close();
return success;
}
});
}
catch (Exception e)
{
logger.error("setNX redis error, key : {}", key);
}
return obj != null ? (Boolean) obj : false;
} private String getSet(final String key, final String value)
{
Object obj = null;
try
{
obj = redisTemplate.execute(new RedisCallback<Object>()
{
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException
{
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
connection.close();
return serializer.deserialize(ret);
}
});
}
catch (Exception e)
{
logger.error("setNX redis error, key : {}", key);
}
return obj != null ? (String) obj : null;
} /**
* 获得 lock.
* 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.
* reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)
* 执行过程:
* 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
* 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
*
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException in case of thread interruption
*/
public synchronized boolean lock() throws InterruptedException
{
int timeout = timeoutMsecs;
while (timeout >= 0)
{
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires) + "&" + uuid; //锁到期时间 + uuid
if (this.setNX(lockKey, expiresStr))
{
// lock acquired
locked = true;
return true;
}
String currentValueStr = this.get(lockKey); //redis里的时间
if (currentValueStr != null && !"".equals(currentValueStr))
{
String[] split = currentValueStr.split("&");
if (split[0] != null && Long.parseLong(split[0]) < System.currentTimeMillis())
{
// 判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
// lock is expired
String oldValueStr = this.getSet(lockKey, expiresStr);
// 获取上一个锁到期时间,并设置现在的锁到期时间,
// 只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
if (oldValueStr != null && oldValueStr.equals(currentValueStr))
{
// 防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受 // [分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
// lock acquired
locked = true;
return true;
}
}
}
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
return false;
} /**
* Acqurired lock release.
*/
public synchronized void unlock()
{
if (locked)
{
String keyValue = this.get(lockKey);
if (keyValue != null && !"".equals(keyValue))
{
String[] split = keyValue.split("&");
// 判断uuid是否已经被修改
if (split.length == 2 && uuid.equals(split[1]))
{
redisTemplate.delete(lockKey);
}
}
locked = false;
}
} }
调用:
RedisLock lock = null;
try {
//先获取状态,如果为Y则任务已经发送完毕
if("Y".equals(redisutil.getString("flg"))){
return;
}
lock = new RedisLock(redisTemplate, "flg",10000,20000);//这里的flg最终会变成flg_lock
if(lock.lock()){
//再次检索,因为有可能开始获取状态不是Y,但是在获取锁的过程中被别人置成Y,所以再次检索确保不重发
if("Y".equals(redisutil.getString("flg"))){
return;
}
redisutil.set("flg", "Y",300);
//======================================start
//执行自己需要的任务
//======================================end
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(lock != null)
{lock.unlock();}
}
redisutil网上应该有很多,应该能搜到
注意:虽然可以实现我想要的效果,但是我个人有个疑问,分布式系统的时间是怎样统一的,如果时间不一样,可能会出问题
分布式系统定时任务,保证只有一个服务执行了改任务--采用redis分布式锁来实现(文章摘自:https://www.cnblogs.com/0201zcr/p/5942748.html)的更多相关文章
- Redis 分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!(转)
基于Redis使用分布式锁在当今已经不是什么新鲜事了. 本篇文章主要是基于我们实际项目中因为redis分布式锁造成的事故分析及解决方案.我们项目中的抢购订单采用的是分布式锁来解决的,有一次,运营做了一 ...
- SpringCloud微服务实战——搭建企业级开发框架(三十九):使用Redis分布式锁(Redisson)+自定义注解+AOP实现微服务重复请求控制
通常我们可以在前端通过防抖和节流来解决短时间内请求重复提交的问题,如果因网络问题.Nginx重试机制.微服务Feign重试机制或者用户故意绕过前端防抖和节流设置,直接频繁发起请求,都会导致系统防重 ...
- 利用redis 分布式锁 解决集群环境下多次定时任务执行
定时任务: @Scheduled(cron= "0 39 3 * * *") public void getAllUnSignData(){ //检查任务锁,若其它节点的相同定时任 ...
- Redis分布式锁服务(八)
阅读目录: 概述 分布式锁 多实例分布式锁 总结 概述 在多线程环境下,通常会使用锁来保证有且只有一个线程来操作共享资源.比如: object obj = new object(); lock (ob ...
- Redis分布式锁服务(转)
原文:http://www.cnblogs.com/mushroom/p/4752499.html 概述 在多线程环境下,通常会使用锁来保证有且只有一个线程来操作共享资源.比如: object obj ...
- Redis分布式锁服务
阅读目录: 概述 分布式锁 多实例分布式锁 总结 概述 在多线程环境下,通常会使用锁来保证有且只有一个线程来操作共享资源.比如: object obj = new object(); lock (ob ...
- 干货,不小心执行了rm -f,除了跑路,如何恢复?https://www.cnblogs.com/justmine/p/10359186.html
前言 每当我们在生产环境服务器上执行rm命令时,总是提心吊胆的,因为一不小心执行了误删,然后就要准备跑路了,毕竟人不是机器,更何况机器也有bug,呵呵. 那么如果真的删除了不该删除的文件,比如数据库. ...
- 你懂什么是分布式系统吗?Redis分布式锁都不会?
分布式系统涉及到很多的技术.理论与协议,很多人也说,分布式系统是“入门容易,深入难”,有一些人简历上写着熟悉分布式系统,很多人都是管中窥豹只见一斑. 究竟什么是分布式系统? 分布式系统是由一组通过网络 ...
- thinkphp5使用workerman的定时器定时任务在某一个时间执行
1.首先通过 composer 安装workerman,在thinkphp5完全开发手册的扩展->coposer包->workerman有详细说明: #在项目根目录执行以下指令compos ...
随机推荐
- 数据库SQL的分组函数
分组函数:(五个) 1···max(expr):求expr的最大值 }\ 2···min(expr):求expr的最小值 }-- 数据类型是有规定的 3···sum(expr):求expr的总和 }- ...
- linux下安装svn服务器
http://www.cnblogs.com/zhoulf/archive/2013/02/02/2889949.html 安装说明系统环境:CentOS-6.3安装方式:yum install (源 ...
- ASP.NET页面之间传值的方式之Session(个人整理)
Session Session在ASP.NET中,表示客户端(Goggle,Firefox,IE等)与服务器端的会话,用来存储特定会话信息,准确来说,是用来存储特定用户信息.当客户端向服务器发送一个请 ...
- 1*1的卷积核与Inception
https://www.zhihu.com/question/56024942 https://blog.csdn.net/a1154761720/article/details/53411365 本 ...
- 使用Sitecore的可视化路径分析器工具洞察客户360度行为
Path Analyzer背后的想法是观察,分析和理解您的潜在客户和客户在浏览您的网站时所遵循的网络旅程.他们最初来自哪里?他们的路径是“有机”(通过搜索引擎)还是来自付费广告?社交帖子?甚至是你发起 ...
- DRF之认证组件源码解析
认证组件 认证的几种方法:cookie,session,token几种.但是session会使服务器的压力增大,所以我们经常使用的是token.获取唯一的随机字符串: 登陆携带token值的处理: ...
- 创建servlet程序知识点详解---servlet-day03
#1.表单包含有中文参数值,如何处理? ##(1)为什么会有乱码? 表单提交时,浏览器会对中文参数值进行编码/ 注:会按照表单所有的页面打开时所使用的字符集进行编码 比如,浏览器会使用utf-8对中文 ...
- samba服务器笔记 (一)
Samba安装 samba:主服务包:samba-client:客户端:samba-common:通用工具:samba4-libs:库:samba-winbind:windows域映射:samba-w ...
- Python assert断言
assert断言:指定某个对象判断类型,不成立则报错. 使用环境 :接下来程序的执行,如果依赖前面的类型,不能报错的情况下使用. assert type(obj) is str print(&quo ...
- 【Python65--tkinter:button】
一.需求:在Label页面增加一个按钮,点击此按钮,页面内容进行变化 思路: 1.上面放一个Label,下面放一个Button 2.采用frame框架 from tkinter import * #定 ...