Spring Boot + Redis实战-利用自定义注解+分布式锁实现接口幂等性
场景
不管是传统行业还是互联网行业,我们都需要保证大部分操作是幂等性的,简单点说,就是无论用户点击多少次,操作多少遍,产生的结果都是一样的,是唯一的。而今次公司的项目里,又被我遇到了这么一个幂等性的问题,就是用户的余额充值、创建订单和订单支付,不管用户点击多少次,只会有一条充值记录,一条新订单记录,一条订单支付记录。
技术方案
现在使用比较广泛的方案都是基于Redis。
方案:Redis+token
- 处理流程:数据提交前,前端要向服务端的申请token,token(带有过期时间)放到redis;当数据提交时带上token,如果删除token成功则表明token未过期,然后进行业务逻辑,否则就是token已过期,提示前端请勿重复提交数据。
而我将使用不同的方案。因为此时前后端对接已走一半,不想让前端再增加请求token的接口(毕竟后端能搞定的,还是别麻烦前端同学了)。
方案:自定义注解+分布式锁
- 处理流程:将需要幂等性的接口加上自定义注解。然后编写一个切面,在around方法里逻辑:尝试获取分布式锁(带过期时间),成功表明没重复提交,否则就是重复提交了。
讲解开始
1、添加Redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、自定义注解:
/**
* @author Howinfun
* @desc 自定义注解:分布式锁
* @date 2019/11/12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheLock {
/** key前缀 */
String prefix() default "";
/** 过期秒数,默认为5秒 */
int expire() default 5;
/** 超时时间单位,默认为秒 */
TimeUnit timeUnit() default TimeUnit.SECONDS;
/** Key的分隔符(默认 :) */
String delimiter() default ":";
}
3、自定义切面:
/**
* @author Howinfun
* @desc 自定义切面
* @date 2019/11/12
*/
@Aspect
@Component
public class LockCheckAspect {
/** lua */
private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 增强带有CacheLock注解的方法
@Pointcut("@annotation(cn.gdmcmc.system.api.config.aop.CacheLock)")
public void pointCut(){}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
// 可以根据业务获取用户唯一的个人信息,例如手机号码
String phone = .....;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CacheLock cacheLock = method.getAnnotation(CacheLock.class);
String prefix = cacheLock.prefix();
if (StringUtils.isBlank(prefix)){
throw new GlobalException("CacheLock prefix can't be null");
}
// 拼接 key
String delimiter = cacheLock.delimiter();
StringBuilder sb = new StringBuilder();
sb.append(prefix).append(delimiter).append(phone);
final String lockKey = sb.toString();
final String UUID = cn.hutool.core.lang.UUID.fastUUID().toString();
try {
// 获取锁
boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey,UUID,cacheLock.expire(),cacheLock.timeUnit());
if (!success){
throw new CustomDeniedException("请勿重复提交");
}
Object result= joinPoint.proceed();
return result;
}finally {
// 最后记得释放锁
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT,Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),UUID);
}
}
}
4、到此,只要为需要保证幂等性的接口上加上@CacheLock注解,就可以了。
@RestController
@RequestMapping(value = "/charge")
@AllArgsConstructor
public class ChargeController {
@PostMapping("/startCharge")
@CacheLock(prefix = "recharge")
public Result startCharge(@RequestBody @Validated({ChargeQuery.QRCodeNotBlank.class}) ChargeQuery query){
return this.chargeChargeService.startCharge(query);
}
}
Spring Boot + Redis实战-利用自定义注解+分布式锁实现接口幂等性的更多相关文章
- spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,guava限流,定时任务案例, 发邮件
本文介绍spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,定时任务案例 集成swagger--对于做前后端分离的项目,后端只需要提供接口访问,swagger提供了接口 ...
- asp.net core mvc基于Redis实现分布式锁,C# WebApi接口防止高并发重复请求,分布式锁的接口幂等性实现
使用背景:在使用app或者pc网页时,可能由于网络原因,api接口可能被前端调用一个接口重复2次的情况,但是请求内容是一样的.这样在同一个短暂的时间内,就会有两个相同请求,而程序只希望处理第一个请求, ...
- 利用consul在spring boot中实现最简单的分布式锁
因为在项目实际过程中所采用的是微服务架构,考虑到承载量基本每个相同业务的服务都是多节点部署,所以针对某些资源的访问就不得不用到用到分布式锁了. 这里列举一个最简单的场景,假如有一个智能售货机,由于机器 ...
- Spring Boot系列——AOP配自定义注解的最佳实践
AOP(Aspect Oriented Programming),即面向切面编程,是Spring框架的大杀器之一. 首先,我声明下,我不是来系统介绍什么是AOP,更不是照本宣科讲解什么是连接点.切面. ...
- 【Spring】5、利用自定义注解在SpringMVC中实现自定义权限检查
转自:http://www.tuicool.com/articles/6z2uIvU 先描述一下应用场景,基于Spring MVC的WEB程序,需要对每个Action进行权限判断,当前用户有权限则允许 ...
- 分布式之分布式事务、分布式锁、接口幂等性、分布式session
一.分布式session session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 jsessionid cookie,就根据这个东西 ...
- Spring Boot Redis 实现分布式锁,真香!!
之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...
- Spring Boot 多站点利用 Redis 实现 Session 共享
如何在不同站点(web服务进程)之间共享会话 Session 呢,原理很简单,就是把这个 Session 独立存储在一个地方,所有的站点都从这个地方读取 Session. 通常我们使用 Redis 来 ...
- 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁
spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...
随机推荐
- 记录一次创建.net core 项目 并且发布到docekr【完全新手入门】
1]环境说明 操作系统:Window 10 专业版 开发工具 Vs2019专业版 Docker: Docker for Windows 2]创建.net core项目并且发布 2.0先打开并且运行 ...
- 不看好 git ,也看不懂为什么那么多人去使用 git
上来就亮明观点,符合我的性格.呵呵呵. 为什么不看好 git 呢? 首先,我们来看看 git 产生的背景. git 是 Linus 开发的,最初的目的,是为了管理 Linux 系统的源代码.这是一个分 ...
- Jmeter常用的两大性能测试场景
一.阶梯式场景 该场景主要应用在负载测试里面,通过设定一定的并发线程数,给定加压规则,遵循“缓起步,快结束”的原则,不断地增加并发用户来找到系统的性能瓶颈,进而有针对性的进行各方面的系统优化. 使用到 ...
- COSCon'19 | 如何设计新一代的图数据库 Nebula
11 月 2 号 - 11 月 3 号,以"大爱无疆,开源无界"为主题的 2019 中国开源年会(COSCon'19)正式启动,大会以开源治理.国际接轨.社区发展和开源项目为切入点 ...
- idea自用快捷键(非常实用)
最近看了下idea的快捷键,不禁感慨idea真是太强大了,这里记录一下我常用的快捷键 Ctrl+F:在当前文件中查找 Ctrl+R:替换字符串 Alt + shift + 鼠标点击 多处同时编辑 ...
- LeetCode 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树
第108题 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定有序数组: [-10 ...
- IDEA的Maven设置阿里镜像
作为一名.net开发学习java,虽然学习起来没啥难度,但是各种配置实在让人头大. 下面就简单记录下IDEA的Maven设置阿里镜像 进入开发软件的这个设置 点击进入 如图所示 第一个是你安装mave ...
- 番茄助手 最新 Visual Assist X 适应于VS2019 VS2017 VS2015 VS2013 亲测可用
番茄助手 最新 Visual Assist X 适应于VS2019 VS2017 VS2015 VS2013 亲测可用 如图: 颜色已经改变: 下载说明: /* INSTALLATION 0) Uni ...
- Mybatis的小技巧
一.使用resultMap字段关联对象属性太麻烦 eg:过于复杂,类似这种结果集转换的,只需要在配置文件中开启自动转换进行了,无需再手动写了很麻烦 替换办法:开启骆驼命名法进行匹配就ok了,实体类字段 ...
- 松软科技web课堂:JavaScript 布尔(逻辑)
JavaScript 布尔(逻辑)代表两个值之一:true 或 false. 布尔值 通常,在编程中,您会需要只能有两个值之一的数据类型,比如 YES / NO ON / OFF TRUE / FAL ...