redis 分布式鎖

最近抽空優(yōu)化了之前已有的redis分布式鎖,主要用于解決高并發(fā)的問題,比如搶紅包,多個(gè)人同時(shí)操作紅包庫(kù)存,當(dāng)在庫(kù)存只剩下1個(gè)的時(shí)候,一個(gè)人的減庫(kù)存的操作事務(wù)沒提交,另一個(gè)人的查庫(kù)存操作剛好同步執(zhí)行,這樣就會(huì)出現(xiàn)很尷尬的事情,1個(gè)紅包會(huì)被2個(gè)人搶走,這個(gè)時(shí)候,我們就要依托鎖,將請(qǐng)求入口鎖住,當(dāng)然鎖有很多種方式,這邊就記錄一下比較好用的redis分布式鎖。

方式有很多setNX 、set、incr等等,setNX只要通過(guò)邏輯防止死鎖就可以了

直接上代碼:

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){

//設(shè)置失敗,重新設(shè)置過(guò)期時(shí)間

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; // 重試次數(shù)

long retryWait() default 200; // 重試等待時(shí)間,單位 : ms

int keeyMinTime() default 1; //鎖自動(dòng)失效時(shí)間 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){

//執(zhí)行主程序

return pjp.proceed();

}else{

if(retryTimes <= 0){

log.info(String.format("{%s}已經(jīng)被鎖, 不重試", keyName));

throw new RuntimeException(String.format("{%s}已經(jīng)被鎖, 不重試", keyName));

}

int failCount = 1;

while (failCount <= retryTimes) {

// 等待指定時(shí)間ms

try {

Thread.sleep(retryWait);

} catch (InterruptedException e) {

e.printStackTrace();

}

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

// 執(zhí)行主邏輯

return pjp.proceed();

} else {

log.info(String.format("{%s}已經(jīng)被鎖, 正在重試[ %s/%s ],重試間隔{%s}毫秒", keyName, failCount, retryTimes, retryWait));

failCount++;

}

}

throw new RuntimeException("系統(tǒng)繁忙, 請(qǐng)稍等再試");

}

}

/**

* 獲取鎖參數(shù)

*

* @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表達(dá)式

*

* @param pjp

* @return

*/

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

// 獲取被攔截方法參數(shù)名列表(使用Spring支持類庫(kù))

LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();

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

// 使用SPEL進(jìn)行key的解析

ExpressionParser parser = new SpelExpressionParser();

// SPEL上下文

StandardEvaluationContext context = new StandardEvaluationContext();

// 把方法參數(shù)放入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)

模擬高并發(fā)測(cè)試

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

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

}

效果就是這樣了

覺得還不錯(cuò)的朋友可以加我的交流群:454377428 一起交流學(xué)習(xí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們知道分布式鎖的特性是排他、避免死鎖、高可用。分布式鎖的實(shí)現(xiàn)可以通過(guò)數(shù)據(jù)庫(kù)的樂觀鎖(通過(guò)版本號(hào))或者悲觀鎖(通過(guò)...
    Java大生閱讀 452評(píng)論 0 0
  • 前言 分布式鎖一般有三種實(shí)現(xiàn)方式:1. 數(shù)據(jù)庫(kù)樂觀鎖;2. 基于Redis的分布式鎖;3. 基于ZooKeeper...
    朦朧蜜桃閱讀 558評(píng)論 1 0
  • 中國(guó)股市為何股災(zāi)頻發(fā)? 股災(zāi)其實(shí)國(guó)內(nèi)外都有,美國(guó)股市在健全之前,也發(fā)生過(guò)各種類型的股災(zāi)。從當(dāng)年荷蘭的郁金香事件開始...
    彭斐導(dǎo)閱讀 415評(píng)論 0 0
  • 來(lái)到廣州,因?yàn)槭盏搅蓑v訊的面試還有從來(lái)沒有來(lái)過(guò)這個(gè)地方。所以在一小會(huì)兒的思考之后就乘車來(lái)到這兒了。因?yàn)檫€在實(shí)習(xí)以及...
    panruc閱讀 205評(píng)論 0 0
  • 圣彼得堡,位于俄羅斯西北部,波羅的海沿岸,涅瓦河口,處于北緯59°~60° 、東經(jīng)29°~30°之間,是俄羅斯的中...
    愛蓮說(shuō)Alice閱讀 3,251評(píng)論 16 0

友情鏈接更多精彩內(nèi)容