网站首页 > 博客文章 正文
最近抽空优化了之前已有的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<Object>() { @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<String, Object> 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<String, Object> 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<String, Object> result = new HashMap<String, Object>(); 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<? extends Object> 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)); }
效果就是这样了
觉得还不错的朋友 后台回复“Java”领取资料 一起交流
猜你喜欢
- 2024-09-11 Spring Security 全局方法安全:预过滤和后过滤(2)
- 2024-09-11 「Spring Boot 源码研究 」- 自动化装配条件化配置Conditional剖析
- 2024-09-11 面试:Spring Boot 中的条件注解底层是如何实现的?
- 2024-09-11 SpringBoot系列(十五)整合缓存,项目必用的技术
- 2024-09-11 如何优雅地记录操作日志?(如何优雅地记录操作日志)
- 2024-09-11 SpEL应用实战(应用spc技术)
- 2024-09-11 有趣的SpEL注入(有趣的工作群名称大全)
- 2024-09-11 SpringBoot 实现异步记录复杂日志
- 2024-09-11 CVE-2022-22947分析(cve-2020-0796分析)
- 2024-09-11 Spring SPEL,自定义注解实现分布式锁
你 发表评论:
欢迎- 06-23MySQL合集-mysql5.7及mysql8的一些特性
- 06-23MySQL CREATE TABLE 简单设计模板交流
- 06-23MYSQL表设计规范(mysql设计表注意事项)
- 06-23MySQL数据库入门(四)数据类型简介
- 06-23数据丢失?别慌!MySQL备份恢复攻略
- 06-23MySQL设计规范(mysql 设计)
- 06-23MySQL数据实时增量同步到Elasticsearch
- 06-23MySQL 避坑指南之隐式数据类型转换
- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)