redis 分布式锁

最近抽空优化了之前已有的redis分布式锁,主要用于解决高并发的问题,比如抢红包,多个人同时操作红包库存,当在库存只剩下1个的时候,一个人的减库存的操作事务没提交,另一个人的查库存操作刚好同步执行,这样就会出现很尴尬的事情,1个红包会被2个人抢走,这个时候,我们就要依托锁,将请求入口锁住,当然锁有很多种方式,这边就记录一下比较好用的redis分布式锁。

方式有很多setNX 、set、incr等等,setNX只要通过逻辑防止死锁就可以了

直接上代码:

public boolean keyLock(final String key, final long keepMin) {

boolean obj = false;

try {

obj = (boolean) redisTemplateSerializable.execute(new RedisCallback() {

@Override

public Object doInRedis(RedisConnection connection)

throws DataAccessException {

try{

Long incr = connection.incr(key.getBytes());

if(incr == 1){

connection.setEx(key.getBytes(), keepMin, incr.toString().getBytes());

return true;

}else{

Long ttl = connection.ttl(key.getBytes());

if(ttl == -1){

//设置失败,重新设置过期时间

connection.setEx(key.getBytes(), keepMin, incr.toString().getBytes());

return true;

}

}

}catch (Exception e) {

logger.error("加锁异常", e);

connection.del(key.getBytes());

return true;

}

return false;

}

});

}catch (Exception e) {

logger.error(e.getMessage());

}

return obj;

}

注解

package com.tp.soft.common.interceptor;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import java.util.concurrent.TimeUnit;

/**

* redis锁注解

*

* @author taop

*/

@Retention(RetentionPolicy.RUNTIME)

@Target({ ElementType.METHOD })

@Documented

public @interface RedisLock {

String lockName() default ""; // 锁名

int retryTimes() default 0; // 重试次数

long retryWait() default 200; // 重试等待时间,单位 : ms

int keeyMinTime() default 1; //锁自动失效时间 1秒

}

aop

package com.tp.soft.aop.redis;

import java.lang.reflect.Method;

import java.util.HashMap;

import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.Signature;

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.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

import org.springframework.expression.ExpressionParser;

import org.springframework.expression.spel.standard.SpelExpressionParser;

import org.springframework.expression.spel.support.StandardEvaluationContext;

import org.springframework.stereotype.Component;

import cn.hutool.core.lang.Assert;

import com.tp.soft.common.interceptor.Cacheable;

import com.tp.soft.common.interceptor.RedisLock;

import com.tp.soft.redis.RedisCacheSvc;

@Aspect

@Component

public class RedisLockAop {

private static final Logger log = LoggerFactory.getLogger(RedisLockAop.class);

private static final String LOCK_NAME = "lockName";

private static final String RETRY_TIMES = "retryTimes";

private static final String RETRY_WAIT = "retryWait";

private static final String KEEP_MIN_TIME = "keepMinTime";

@Resource

private RedisCacheSvc redisCacheSvc;

@Pointcut("@annotation(com.tp.soft.common.interceptor.RedisLock)")

public void redisLockAspect() {

}

@Around("redisLockAspect()")

public Object lockAroundAction(ProceedingJoinPoint pjp) throws Throwable {

Method method = returnMethod(pjp);

Map annotationArgs = this.getAnnotationArgs(pjp);

String lockPrefix = (String) annotationArgs.get(LOCK_NAME);

Assert.notNull(lockPrefix, "分布式,锁名不能为空");

int retryTimes = (int) annotationArgs.get(RETRY_TIMES);

long retryWait = (long) annotationArgs.get(RETRY_WAIT);

int keepMinTime = (int) annotationArgs.get(KEEP_MIN_TIME);

String keyName = parseKey(lockPrefix, method, pjp.getArgs());

// 获取redis锁,防止死锁

boolean keyLock = redisCacheSvc.keyLock(keyName, keepMinTime);

if(keyLock){

//执行主程序

return pjp.proceed();

}else{

if(retryTimes <= 0){

log.info(String.format("{%s}已经被锁, 不重试", keyName));

throw new RuntimeException(String.format("{%s}已经被锁, 不重试", keyName));

}

int failCount = 1;

while (failCount <= retryTimes) {

// 等待指定时间ms

try {

Thread.sleep(retryWait);

} catch (InterruptedException e) {

e.printStackTrace();

}

if (redisCacheSvc.keyLock(keyName, keepMinTime)) {

// 执行主逻辑

return pjp.proceed();

} else {

log.info(String.format("{%s}已经被锁, 正在重试[ %s/%s ],重试间隔{%s}毫秒", keyName, failCount, retryTimes, retryWait));

failCount++;

}

}

throw new RuntimeException("系统繁忙, 请稍等再试");

}

}

/**

* 获取锁参数

*

* @param proceeding

* @return

*/

private Map getAnnotationArgs(ProceedingJoinPoint proceeding) {

Class target = proceeding.getTarget().getClass();

Method[] methods = target.getMethods();

String methodName = proceeding.getSignature().getName();

for (Method method : methods) {

if (method.getName().equals(methodName)) {

Map result = new HashMap();

RedisLock redisLock = method.getAnnotation(RedisLock.class);

result.put(LOCK_NAME, redisLock.lockName());

result.put(RETRY_TIMES, redisLock.retryTimes());

result.put(RETRY_WAIT, redisLock.retryWait());

result.put(KEEP_MIN_TIME, redisLock.keeyMinTime());

return result;

}

}

return null;

}

private Method returnMethod(ProceedingJoinPoint pjp)

throws NoSuchMethodException {

Signature signature = pjp.getSignature();

Class cls = pjp.getTarget().getClass();

MethodSignature methodSignature = (MethodSignature) signature;

Method targetMethod = methodSignature.getMethod();

Method method = cls.getDeclaredMethod(signature.getName(),

targetMethod.getParameterTypes());

return method;

}

/**

* 获取缓存的key key 定义在注解上,支持SPEL表达式

*

* @param pjp

* @return

*/

private String parseKey(String key, Method method, Object[] args) {

// 获取被拦截方法参数名列表(使用Spring支持类库)

LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();

String[] paraNameArr = u.getParameterNames(method);

// 使用SPEL进行key的解析

ExpressionParser parser = new SpelExpressionParser();

// SPEL上下文

StandardEvaluationContext context = new StandardEvaluationContext();

// 把方法参数放入SPEL上下文中

for (int i = 0; i < paraNameArr.length; i++) {

context.setVariable(paraNameArr[i], args[i]);

}

return parser.parseExpression(key).getValue(context, String.class);

}

}

搭建完成后直接在需要锁住的接口上注解

@RedisLock(lockName="'lock_'+#tbbId",retryTimes=5)

模拟高并发测试

for (int i = 0; i < 2; i++) {

threadPoolTaskExecutor.execute(new StartTaskThread(redisCacheSvc, i, threadPoolTaskExecutor));

}

效果就是这样了

觉得还不错的朋友可以加我的交流群:454377428 一起交流学习

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容