网站首页 > 精选教程 正文
场景
活动商品是采用预售形式,用户在活动进行中需要提前将选购的商品进行提交(无支付逻辑),活动结束后采用“抽签”的方式决定用户抽中哪个商品,然后将结果短信通知个用户。流程如下:
问题
管理员反馈某次活动用户抽中了2次,并且商品一模一样。小编赶紧去查了数据库发现这个用户一共提交到数据库 200 个商品,一模一样的,抽签的逻辑中没有去重,导致该用户抽中的概率是普通用户的 200 倍。
原因排查
1、用户快速刷新页面导致重复提交?
检查了提交商品的代码逻辑,对同一个用户、同一场活动、同一款商品 在 Redis 中加了锁,也就是说用户在页面多次提交不会引起上述问题。
2、追踪用户提交链路
通过ELK 日志追踪到用户一次提交的入参里面存放了 200 个一模一样的商品,到这里问题基本找到了。
问题定位
用户是通过非正常手段触发的提交商品API,因为每次请求前端和后端都有做重复提交的校验,但是对于用户的一次提交内容没有做严格的去重校验,导致一次提交了 N 多商品进入到数据库。
解决方案
这里采用的是Set 集合独一无二的性质判断是否有相同的元素,代码示例如下:
订单类.java
public class Order {
private String iteamId; // 商品ID
省略其他......
}
Set 去重
private static Boolean itemIsRepeat(List<Order> orderList) {
Set<Order> set = new TreeSet<Order>(new Comparator<Order>() {
public int compare(Order a, Order b) {
// 字符串则按照asicc码升序排列
return a.getItemId().compareTo(b.getItemId());
}
});
set.addAll(orderList);
if (set.size() < orderList.size()) {
return true;
}
return false;
}
public static void main(String[] args) {
List<Order> orderList = new ArrayList<Order>(){{
add(new Order("123"));
add(new Order("123"));
add(new Order("123"));
}};
if(registItemIsRepeat(registItemRqDtos)) {
System.out.println("有重复元素");
} else {
System.out.println("没有重复元素");
}
}
总结
到这里为止,线上问题已经解决了,有小伙伴会问 为什么数据库不增加联合主键呢?这样就保证用户提交信息的唯一性了。增加数据库联合主键是没问题的,可能之前小伙伴太相信自己写的 Redis 锁了。
那防止用户重复提交其实分为两部分:
防君子:
1、前端js提交禁止按钮可以用一些js组件。
2、使用Post/Redirect/Get模式
在收到用户提交请求后立即执行页面重定向逻辑,这就是所谓的Post-Redirect-Get (PRG)模式。简单的说,当用户提交了表单后,立马执行一个客户端的重定向,转到提交成功信息页面。这能避免用户按F5刷新导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。
3、在session中存放一个加密字符串
在服务器端,生成一个唯一无规则加密字符串,将它写入session中,并且还需要将session它存放在 HTML 表单的隐藏字段中,然后将表单页面发给浏览器,用户录入信息后点击提交,在服务器端,获取表单中隐藏字段的值,与session中的唯一标识符比较,相等说明是首次提交,就处理本次请求,然后将session中的唯一标识符移除;不相等说明是重复提交,就不再处理。
防小人:
1、数据库唯一索引
insert 插入一条数据的时候 使用唯一索引 update使用 乐观锁 version版本法
但是这种方案在大数据量和高并发下效率是非常依赖数据库硬件能力。
2、借助悲观锁
使用select … for update ,这种和 synchronized 锁住先查再insert or update一样,但要避免死锁,效率也较差。
同样的在高并发下不建议使用。
3、本地锁(适合单机)
原理:
使用了 ConcurrentHashMap 并发容器 putIfAbsent 方法,和 ScheduledThreadPoolExecutor 定时任务,也可以使用guava cache的机制, gauva中有配有缓存的有效时间也是可以的key的生成
Content-MD5
Content-MD5 是指 Body 的 MD5 值,只有当 Body 非Form表单时才计算MD5,计算方式直接将参数和参数名称统一加密MD5
MD5在一定范围类认为是唯一的 近似唯一 当然在低并发的情况下足够了
本地锁只适用于单机部署的应用。
4、分布式锁
基于Redis实现分布式锁。
- 上一篇: 消息队列如何保证不重复消费消息
- 下一篇: 阿里二面:RabbitMQ中如何保证消息不被重复消费?
猜你喜欢
- 2024-12-05 Java11新特性-效能翻倍的HttpClient
- 2024-12-05 LeetCode每日一题,无重复字符的最长子串
- 2024-12-05 类型安全的http客户端retrofit介绍、使用、实现原理分析
- 2024-12-05 RabbitMQ消息重复消费问题如何解决
- 2024-12-05 高频面试题:kafka怎么避免重复消费?
- 2024-12-05 RabbitMQ消息丢失、积压、重复等解决方案
- 2024-12-05 程序员们一定要注意避免重复记录日志撑爆ELK而被辞退
- 2024-12-05 面试:如何保证接口的幂等性?常见的实现方案有哪些?
- 2024-12-05 每日分享- 如何保证 Java 语言接口的幂等性?
- 2024-12-05 Kafka如何防止消费速度过慢触发rebalance导致重复消费
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)