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

news/2024/7/8 10:40:18 标签: redis, 分布式, spring boot, java

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

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

一、问题描述

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

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

二、问题分析

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

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

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

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

三、解决方案

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

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

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

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

java">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 分布式锁来实现订单状态变更操作:

java">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 {
                    //订单id
                    Integer orderId = orderStateReq.getOrderId();
                    //根据订单id查看订单是否满足扣费  不满足则抛异常  满足则扣费
                } finally {
                    lock.unlock();
                }
            } else {
                // 获取锁失败,处理逻辑
                throw new RuntimeException("获取锁失败,请稍后再试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("线程中断", e);
        }
    }
}

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

四、总结

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

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


http://www.niftyadmin.cn/n/5537137.html

相关文章

科技助力农业——土壤化肥测试仪

在农业生产中&#xff0c;土壤养分是作物健康生长的关键因素。然而&#xff0c;如何科学、精准地评估土壤养分含量&#xff0c;指导农民合理施肥&#xff0c;一直是农业科研和技术人员努力的方向。近年来&#xff0c;随着科技的进步&#xff0c;土壤化肥测试仪作为一种新型农业…

「Java开发指南」如何用MyEclipse完成Spring Web Flow 2.0搭建?

本教程将引导您完成Spring Web Flow的软件组件生成&#xff0c;这是Spring的一个项目&#xff0c;用于简化Web应用程序的开发。虽然Spring Web Flow与Spring MVC兼容&#xff0c;但Spring Web Flow使用流而不是控制器来实现应用程序的Web层。在本教程中&#xff0c;您将学习如何…

Django + Vue 实现图片上传功能的全流程配置与详细操作指南

文章目录 前言图片上传步骤1. urls 配置2. settings 配置3. models 配置4. 安装Pillow 前言 在现代Web应用中&#xff0c;图片上传是一个常见且重要的功能。Django作为强大的Python Web框架&#xff0c;结合Vue.js这样的现代前端框架&#xff0c;能够高效地实现这一功能。本文将…

实战某大型连锁企业域渗透

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 实战域渗透测试流程 对黑客来说&#xff0c;拿下域控制器是终极目标。然而攻击者空间是如何通过采取信息收集、权限提升、横向移动等一系列手段&#xff0c;从而一步步…

笔记:Git学习之应用场景和使用经验

目标&#xff1a;整理Git工具的应用场景和使用经验 一、开发环境 Git是代码版本控制工具&#xff1b;Github是代码托管平台。 工具组合&#xff1a;VSCode Git 需要安装的软件&#xff1a;vscode、Git 其中vscode需要安装的插件&#xff1a;GitLens、Git History 二、应用…

Linux系统部署MongoDB开源文档型数据库并实现无公网IP远程访问

文章目录 前言1. 安装Docker2. 使用Docker拉取MongoDB镜像3. 创建并启动MongoDB容器4. 本地连接测试5. 公网远程访问本地MongoDB容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定TCP地址远程访问 &#x1f4a1; 推荐 前些天发现了一个巨牛的人工智能学习网站&am…

SpringBoot 启动流程六

SpringBoot启动流程六 这句话是创建一个上下文对象 就是最终返回的那个上下文 我们这个creatApplicationContext方法 是调用的这个方法 传入一个类型 我们通过打断点的方式 就可以看到context里面的东西 加载容器对象 当我们把依赖改成starter-web时 这个容器对象会进行…

江协科技51单片机学习- p23 DS1302实时时钟

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…