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 一起交流學習
相關文章
- 十九、Redis分散式鎖、Zookeeper分散式鎖Redis分散式
- 分散式鎖-Redis分散式Redis
- Redis分散式鎖Redis分散式
- Redis分散式鎖解析Redis分散式
- redis系列:分散式鎖Redis分散式
- Springboot + redis分散式鎖Spring BootRedis分散式
- Redis 分散式鎖(一)Redis分散式
- Redis分散式鎖加鎖案例Redis分散式
- redis分散式鎖-可重入鎖Redis分散式
- 分散式鎖----Redis實現分散式Redis
- 細說Redis分散式鎖?Redis分散式
- Redis分散式鎖實戰Redis分散式
- Redis 應用-分散式鎖Redis分散式
- Redis實現分散式鎖Redis分散式
- 【180414】分散式鎖(redis/mysql)分散式RedisMySql
- 基於 Redis 分散式鎖Redis分散式
- 詳解Redis分散式鎖Redis分散式
- 【Redis】利用 Redis 實現分散式鎖Redis分散式
- 分散式鎖之Redis實現分散式Redis
- 利用Redis實現分散式鎖Redis分散式
- 分散式鎖--Redis小試牛刀分散式Redis
- Redis 分散式鎖解決方案Redis分散式
- redis分散式鎖-SETNX實現Redis分散式
- 基於redis的分散式鎖Redis分散式
- 分散式鎖實現(一):Redis分散式Redis
- Redis分散式鎖解決方案Redis分散式
- 基於redis做分散式鎖Redis分散式
- Redis之分散式鎖實現Redis分散式
- Redis-10-分散式鎖.mdRedis分散式
- 基於 Redis 的分散式鎖Redis分散式
- Redis如何實現分散式鎖Redis分散式
- redis分散式鎖的實現Redis分散式
- redis分散式鎖-java實現Redis分散式Java
- 關於分散式鎖原理的一些學習與思考-redis分散式鎖,zookeeper分散式鎖分散式Redis
- Redis面試系列:Redis實現分散式鎖Redis面試分散式
- 基於redis實現分散式鎖Redis分散式
- Redis優雅實現分散式鎖Redis分散式
- 實現一個 Redis 分散式鎖Redis分散式