一、场景

  项目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

  

分布式锁-RedisLockRegistry源码分析[转]

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分布式锁小试的更多相关文章

  1. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  2. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  3. redis分布式锁和消息队列

    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...

  4. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  5. spring boot redis分布式锁

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...

  6. Redis分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  7. Redis分布式锁---完美实现

    这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...

  8. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  9. Redis分布式锁的try-with-resources实现

    Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...

随机推荐

  1. JavaScript学习 - 基础(六) - DOM基础操作

    DOM: DOM定义了访问HTML 和XML 文档的标准:1.核心DOM 针对结构化文档的标准模型2.XMK DOM 针对XML文档的标准模型3.HTML DOM 针对HTML文档的标准模型 DOM节 ...

  2. [转]C++赋值运算符重载函数(operator=)

    写在前面: 关于C++的赋值运算符重载函数(operator=),网络以及各种教材上都有很多介绍,但可惜的是,内容大多雷同且不全面.面对这一局面,在下在整合各种资源及融入个人理解的基础上,整理出一篇较 ...

  3. maven私服内容补充

    1.添加阿里云中央仓库 注意Download Remote Indexes选项为True 1.登陆nexus私服(默认账号密码:admin/admin123) 2.点击右侧Repositories 3 ...

  4. Gaussian discriminant analysis 高斯判别分析

    高斯判别分析(附Matlab实现) 生成学习算法 高斯判别分析(Gaussian Discriminant analysis,GDA),与之前的线性回归和Logistic回归从方法上讲有很大的不同,G ...

  5. 单页应用 WebApp SPA 骨架 框架 路由 页面切换 转场

    这里收录三个同类产品,找到他们花了我不少时间呢. 张鑫旭写的mobilebone自述:mobile移动端,PC桌面端页面无刷新过场JS骨架,简单.专注!http://www.zhangxinxu.co ...

  6. [转]GDB-----2.watchpoint

    TODO需要在ARM下验证 1. 前言 watchpoint,顾名思义,其一般用来观察某个变量/内存地址的状态(也可以是表达式),如可以监控该变量/内存值是否被程序读/写情况. 在gdb中可通过下面的 ...

  7. springboot系列三、springboot 单元测试、配置访问路径、多个配置文件和多环境配置,项目打包发布

    一.单元测试 生成的demo里面包含spring-boot-starter-test :测试模块,包括JUnit.Hamcrest.Mockito,没有的手动加上. <dependency> ...

  8. oracle 11g 空表导出

    背景 oracle9用了一段时间,10用了一段时间,11现在算是主流了.11g也是坑人,空表竟然不导出,解决方法到时很多.这里只是记录下,知道有这个事情. 9的特点是还要用客户端管理工具链接服务器 1 ...

  9. centos7.2环境nginx+mysql+php-fpm+svn配置walle自动化部署系统详解

    centos7.2环境nginx+mysql+php-fpm+svn配置walle自动化部署系统详解 操作系统:centos 7.2 x86_64 安装walle系统服务端 1.以下安装,均在宿主机( ...

  10. sqlserver2008r2数据库关联孤立账号的方法

    重新安装mssql数据库后可能有这样的问题,     在数据还原到其他服务器时,登录名会丢失,重建非常麻烦     特別是数据库用户较多的的情况,其实这个问题解决非常简单,     sql serve ...