redis分布式锁小试
一、场景
项目A监听mq中的其他项目的部署消息(包括push_seq, status, environment,timestamp等),然后将部署消息同步到数据库中(项目X在对应环境[environment]上部署的push_seq[项目X的版本])。那么问题来了,mq中加入包含了两个部署消息 dm1 和 dm2,dm2的push_seq > dm1的push_seq,在分布式的情况下,dm1 和 dm2可能会分别被消费(也就是并行),那么在同步数据库的时候可能会发生 dm1 的数据保存 后于 dm2的数据保存,导致保存项目的部署信息发生异常。
二、解决思路
将mq消息的并行消费变成串行消费,这里借助redis分布式锁来完成。同一个服务在分布式的状态下,监听到mq消息后,触发方法的执行,执行之前(通过spring aop around来做的)首先获得redis的一个分布式锁,获取锁成功之后才能执行相关的逻辑以及数据库的保存,最后释放锁。
三、主要代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @author: hujunzheng
* @create: 17/9/29 下午2:49
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RedisLock {
/**
* redis的key
* @return
*/
String value();
/**
* 持锁时间,单位毫秒,默认一分钟
*/
long keepMills() default 60000;
/**
* 当获取失败时候动作
*/
LockFailAction action() default LockFailAction.GIVEUP; public enum LockFailAction{
/**
* 放弃
*/
GIVEUP,
/**
* 继续
*/
CONTINUE;
}
/**
* 睡眠时间,设置GIVEUP忽略此项
* @return
*/
long sleepMills() default 500;
}
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* @author: hujunzheng
* @create: 17/9/29 下午2:49
*/
@Component
@Aspect
public class RedisLockAspect {
private static final Log log = LogFactory.getLog(RedisLockAspect.class); @Autowired
private RedisCacheTemplate.RedisLockOperation redisLockOperation; @Pointcut("execution(* com.hjzgg..StargateDeployMessageConsumer.consumeStargateDeployMessage(..))" +
"&& @annotation(me.ele.api.portal.service.redis.RedisLock)")
private void lockPoint(){} @Around("lockPoint()")
public Object arround(ProceedingJoinPoint pjp) throws Throwable{
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
RedisLock lockInfo = method.getAnnotation(RedisLock.class); /*
String lockKey = lockInfo.value();
if (method.getParameters().length == 1 && pjp.getArgs()[0] instanceof DeployMessage) {
DeployMessage deployMessage = (DeployMessage) pjp.getArgs()[0];
lockKey += deployMessage.getEnv();
System.out.println(lockKey);
}
*/
boolean lock = false;
Object obj = null;
while(!lock){
long timestamp = System.currentTimeMillis()+lockInfo.keepMills();
lock = setNX(lockInfo.value(), timestamp);
//得到锁,已过期并且成功设置后旧的时间戳依然是过期的,可以认为获取到了锁(成功设置防止锁竞争)
long now = System.currentTimeMillis();
if(lock || ((now > getLock(lockInfo.value())) && (now > getSet(lockInfo.value(), timestamp)))){
log.info("得到redis分布式锁...");
obj = pjp.proceed();
if(lockInfo.action().equals(RedisLock.LockFailAction.CONTINUE)){
releaseLock(lockInfo.value());
}
}else{
if(lockInfo.action().equals(RedisLock.LockFailAction.CONTINUE)){
log.info("稍后重新请求redis分布式锁...");
Thread.currentThread().sleep(lockInfo.sleepMills());
}else{
log.info("放弃redis分布式锁...");
break;
}
}
}
return obj;
}
private boolean setNX(String key,Long value){
return redisLockOperation.setNX(key, value);
}
private long getLock(String key){
return redisLockOperation.get(key);
}
private Long getSet(String key,Long value){
return redisLockOperation.getSet(key, value);
}
private void releaseLock(String key){
redisLockOperation.delete(key);
} @Pointcut(value = "execution(* me.ele..StargateBuildMessageConsumer.consumeStargateBuildMessage(me.ele.api.portal.service.mq.dto.BuildMessage)) && args(buildMessage)" +
"&& @annotation(me.ele.api.portal.service.redis.RedisLock)", argNames = "buildMessage")
private void buildMessageLockPoint(BuildMessage buildMessage){} @Around(value = "buildMessageLockPoint(buildMessage)", argNames = "pjp,buildMessage")
public Object buildMessageAround(ProceedingJoinPoint pjp, BuildMessage buildMessage) throws Throwable {
final String LOCK = buildMessage.getAppId() + buildMessage.getPushSequence();
Lock lock = redisLockRegistry.obtain(LOCK);
try {
lock.lock();
return pjp.proceed();
} finally {
try {
lock.unlock();
} catch (Exception e) {
log.error("buildMessage={}, Lock {} unlock failed. {}", buildMessage, lock, e);
}
}
} }
四、遇到的问题
开始是将锁加到deploy的方法上的,但是一直aop一直没有作用,换到consumeStargateDeployMessage方法上就可以了。考虑了一下是因为 @Transactional的原因。这里注意下。
在一篇文章中找到了原因:SpringBoot CGLIB AOP解决Spring事务,对象调用自己方法事务失效.
只要脱离了Spring容器管理的所有对象,对于SpringAOP的注解都会失效,因为他们不是Spring容器的代理类,SpringAOP,就切入不了。也就是说是 @Transactional注解方法的代理对象并不是spring代理对象。
参考: 关于proxy模式下,@Transactional标签在创建代理对象时的应用
五、使用spring-redis中的RedisLockRegistry
import java.util.concurrent.locks.Lock;
import org.springframework.integration.redis.util.RedisLockRegistry; @Bean
public RedisLockRegistry redisLockRegistry(@Value("${xxx.xxxx.registry}") String redisRegistryKey,
RedisTemplate redisTemplate) {
return new RedisLockRegistry(redisTemplate.getConnectionFactory(), redisRegistryKey, 200000);
} Lock lock = redisLockRegistry.obtain(appId); lock.tryLock(180, TimeUnit.SECONDS);
....
lock.unlock();
六、参考
其他工具类,请参考这里。
七、springboot LockRegistry
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;
import redis.clients.jedis.JedisShardInfo; @Ignore
public class RedisLockTest { private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockTest.class);
private static final String LOCK = "xxx.xxx";
private RedisLockRegistry redisLockRegistry; @Before
public void setUp() {
JedisShardInfo shardInfo = new JedisShardInfo("127.0.0.1");
JedisConnectionFactory factory = new JedisConnectionFactory(shardInfo);
redisLockRegistry = new RedisLockRegistry(factory, "test", 50L);
} private class TaskA implements Runnable { @Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Lock lock = redisLockRegistry.obtain(LOCK);
try {
lock.lock();
LOGGER.info("Lock {} is obtained", lock);
Thread.sleep(10);
lock.unlock();
LOGGER.info("Lock {} is unlocked", lock);
} catch (Exception ex) {
LOGGER.error("Lock {} unlock failed", lock, ex);
}
}
} private class TimeoutTask implements Runnable { @Override
public void run() {
Lock lock = redisLockRegistry.obtain(LOCK);
try {
lock.lock();
LOGGER.info("Lock {} is obtained", lock);
Thread.sleep(5000);
lock.unlock();
LOGGER.info("Lock {} is unlocked", lock);
} catch (Exception ex) {
LOGGER.error("Lock {} unlock failed", lock, ex);
}
}
} @Test
public void test() throws InterruptedException, TimeoutException {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new TimeoutTask());
service.execute(new TaskA());
service.shutdown();
if (!service.awaitTermination(1, TimeUnit.MINUTES)) {
throw new TimeoutException();
}
}
}
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,完全是手动实现的 我 ...
- Redis分布式锁的try-with-resources实现
Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...
随机推荐
- .Net Core中使用RabbitMQ
(1).引入依赖 RabbitMQ.Client (2).编写发布者代码 var connectionFactory = new ConnectionFactory() { HostName=&quo ...
- 求web前端面试题库及答案
1.对WEB标准以及W3C的理解与认识 标签闭合.标签小写.不乱嵌套.提高搜索机器人搜索几率.使用外 链css和js脚本.结构行为表现的分离.文件下载与页面速度更快.内容能被更多的用户所访问.内容能被 ...
- 恶意代码分析实战-x86反汇编速成班
x86反汇编速成 x86体系结构 3种硬件构成: 中央处理器:负责执行代码 内存(RAM):负责存储所有的数据和代码 输入/输出系统(I/O):为硬盘.键盘.显示器等设备提供接口 内存 一个程序的内存 ...
- 【Mysql sql inject】POST方法BASE64编码注入write-up
翻到群里的小伙伴发出一道POST型SQL注入题,简单抓包判断出题目需要base64编码后才执行sql语句,为学习下SQL注入出题与闯关的思路+工作不是很忙,所以花点时间玩了一下,哈哈哈哈哈哈哈哈哈 ...
- Redis消息通知(任务队列和发布订阅模式)
Redis学习笔记(十)消息通知(任务队列和发布订阅模式) 1. 任务队列 1.1 任务队列的特点 任务队列:顾名思义,就是“传递消息的队列”.与任务队列进行交互的实体有两类,一类是生产者(produ ...
- rsync同步(winxdows到linux/linux到linxu同步)
1.什么是rsync? -rsync是类unix系统下的数据镜像备份工具——remote sync.一款快速增量备份工具 Remote Sync,远程同步 支持本地复制,或者与其他SSH.rsync主 ...
- zabbix安装及简单配置
Zabbix基本介绍: zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案.它能监视各种网络参数,保证服务器系统的安全运营:并提供柔软的通知机制以让系统管理员快 ...
- Linux内核移植
实验步骤:(1)准备工作(2)修改顶层Makefile(3)修改falsh 分区(4)配置编译内核 下面以Linux2.6.30.4内核移植到gec2440为例: 一.准备工作:建立工作目录,下载内核 ...
- S5PV210 看门狗定时和复位
第一节 S5PV210的看门狗定时器S5PV210上的看门狗定时器相当于一个普通的16bit的定时器,它与PWM定时器的区别是看门狗定时器可以产生reset信号而PWM定时器不能,S5PV210看门狗 ...
- sklearn.model_selection模块
后续补代码 sklearn.model_selection模块的几个方法参数