当前位置: 首页 > news >正文

定位线上同步锁仍然重复扣费的Bug定位及Redis分布式锁解决方案

在实际生产环境中,处理订单的并发请求时,我们经常会遇到重复扣费的问题。本文将通过一个具体的代码示例,分析在使用同步锁时仍然导致重复扣费的原因,并提供一个基于Redis分布式锁的解决方案。

背景:这个案例出现在商家在小程序端接单重复扣费,PC端也能接单,并且PC端和小程序端不是一套代码,但是接单的代码几乎一致

一、问题描述

在以下代码中,OrderServiceImpl 类使用了 Java 的同步锁来保证对订单状态变更的操作是线程安全的:

public class OrderServiceImpl {public Operating orderStateChange(OrderStateReq orderStateReq) {synchronized (OrderServiceImpl.class) {//订单idInteger orderId = orderStateReq.getOrderId();//根据订单id查看订单是否满足扣费  不满足则抛异常  满足则扣费}}  
}

二、问题分析

尽管我们在 orderStateChange 方法中使用了同步锁,但仍然可能导致重复扣费的问题,原因有以下几点:

锁粒度过大:synchronized (OrderServiceImpl.class) 锁定的是整个类,这样虽然可以避免多个线程同时进入临界区,但在分布式环境下,这种锁机制无法跨JVM工作。

锁的范围有限:Java 的 synchronized 锁仅在单个 JVM 中有效,如果你的应用程序部署在多台服务器上,每个服务器上的 JVM 都会有自己的锁,这就无法避免分布式环境下的并发问题。

业务逻辑不完善:即使在单机环境中,锁住整个类也会导致性能瓶颈,因为所有订单处理请求都必须排队进入同步块,无法充分利用多线程的优势。

三、解决方案

为了解决上述问题,我们可以使用 Redis 分布式锁。Redis 分布式锁的特点是可以跨多个 JVM 保证唯一性,从而避免分布式环境下的并发问题。

1. 引入 Redis 依赖
首先,在你的项目中引入 Redis 相关依赖(以 Spring Boot 为例):

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.2</version></dependency>

2. 实现 Redis 分布式锁
然后,我们实现一个简单的 Redis 分布式锁机制。可以使用 Redisson 库,这个库封装了 Redis 锁的实现,使用起来非常方便。

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;public class RedisLockUtil {private static RedissonClient redissonClient;// 有指定库和密码也需要赋值static {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379");config.useSingleServer().setPassword("redisPassword");config.useSingleServer().setDatabase("database");redissonClient = Redisson.create(config);}public static RLock getLock(String lockKey) {return redissonClient.getLock(lockKey);}
}

3. 使用 Redis 分布式锁
在 OrderServiceImpl 中使用 Redis 分布式锁来实现订单状态变更操作:

import org.redisson.api.RLock;public class OrderServiceImpl {public Operating orderStateChange(OrderStateReq orderStateReq) {String lockKey = "orderLock:" + orderStateReq.getOrderId();RLock lock = RedisLockUtil.getLock(lockKey);try {// 尝试加锁,等待时间为10秒,锁超时时间为30秒if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {try {//订单idInteger orderId = orderStateReq.getOrderId();//根据订单id查看订单是否满足扣费  不满足则抛异常  满足则扣费} finally {lock.unlock();}} else {// 获取锁失败,处理逻辑throw new RuntimeException("获取锁失败,请稍后再试");}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("线程中断", e);}}
}

在使用了分布式锁后上线一周内让DB再查看已经没有了重复扣费现象

四、总结

通过以上步骤,我们可以解决同步锁在分布式环境下无法避免重复扣费的问题。使用 Redis 分布式锁,不仅能在多台服务器上保证锁的唯一性,还能提高系统的并发处理能力,避免性能瓶颈。

希望本文对你在解决分布式系统中的并发问题有所帮助,如果有任何问题或建议,欢迎交流讨论。

相关文章:

  • JDK 为什么需要配置环境变量
  • 单片机IO
  • 项目实战--Spring Boot + Minio文件切片上传下载
  • PyTorch(六)优化模型参数
  • 2.2.5 C#中显示控件BDPictureBox 的实现----ROI交互续2
  • Golang中defer和return顺序
  • LabVIEW幅频特性测试系统
  • 前端工程化08-新的包管理工具pnpm
  • python系列30:各种爬虫技术总结
  • MySQL增删改查
  • Java Nio核心概念理解
  • 关于 Mybatis 的开启二级缓存返回对象不一致问题
  • 嵌入式PCB制图面试题及参考答案(2万字长文)
  • 【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化及多领域案例应用
  • 【2024德国签证】去德国读博士需要申请什么签证?
  • 2017届校招提前批面试回顾
  • Android Studio:GIT提交项目到远程仓库
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • Java应用性能调优
  • Js基础知识(四) - js运行原理与机制
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • python3 使用 asyncio 代替线程
  • 安装python包到指定虚拟环境
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 简单易用的leetcode开发测试工具(npm)
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • Semaphore
  • ​比特币大跌的 2 个原因
  • #NOIP 2014# day.2 T2 寻找道路
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • $forceUpdate()函数
  • (2)STL算法之元素计数
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (二)学习JVM —— 垃圾回收机制
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (四)Linux Shell编程——输入输出重定向
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (转)负载均衡,回话保持,cookie
  • (转)关于pipe()的详细解析
  • .Net - 类的介绍
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .net连接MySQL的方法
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • /dev下添加设备节点的方法步骤(通过device_create)
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [.net]官方水晶报表的使用以演示下载
  • [100天算法】-实现 strStr()(day 52)
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——