一、场景

  项目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. python - format函数 /class内置format方法

    format函数 # format函数 # 用于字符串格式化 # 基本用法: # 方式一:(位置方式) x = "{0}{1}{2}".format(1,2,3) print('1 ...

  2. python - setitem/getitem/delitem类的内置方法

    # class 内置方法: # __setitem__ # __getitem__ # __delitem__ class Test(): X = 100 def __getitem__(self, ...

  3. 用Quartz 2D画小黄人

    第一步: 先创建一个OneView类,并在storyboard里边拖拽一个UIview,将这个UIview的类改成OneView.如图所示: 第二步: 在新创建的Oneview里,补齐下列代码: // ...

  4. Shiro简介及入门(四)

    1.1     什么是shiro shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证.用户授权. spring中有spring security (原名Acegi),是一个权 ...

  5. JSON的理解

    官方解释: JSON的全称是”JavaScript Object Notation”,单单从字面上的理解就是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式. 理解: ...

  6. Linux用户组相关指令

    ⒈增加用户组 ①groupadd 用户组名 ⒉删除用户组 ①groupdel 用户组名 ⒊修改用户所在的用户组 ①usermod -g 用户组 用户名 ★用户和用户组的相关文件 ①/etc/passw ...

  7. 梯度优化算法总结以及solver及train.prototxt中相关参数解释

    参考链接:http://sebastianruder.com/optimizing-gradient-descent/ 如果熟悉英文的话,强烈推荐阅读原文,毕竟翻译过程中因为个人理解有限,可能会有谬误 ...

  8. 同步阿里云镜像到本地,在本地搭建YUM仓库

    1.下载阿里云镜像repo文件 项目使用CentOS6系统,因此我下载的文件是: # CentOS-Base.repo # # The mirror system uses the connectin ...

  9. windows上python上传下载文件到linux服务器指定路径【转】

    从windows上传文件到linux,目录下的文件夹自动创建 #!/usr/bin/env python # coding: utf-8 import paramiko import datetime ...

  10. 64位Win7系统WMware安装Mac OS

    1.         准备工作 l  VMWare Workstation,我的版本是 l  MAC OS安装光盘镜像文件,种子地址 http://www.kuaipan.cn/file/id_611 ...