专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java 高并发下几种防重方式(防止超卖)

temp10 2025-03-20 19:08:15 java教程 5 ℃ 0 评论

场景描述

我正在开发一个在线支付系统,用户在完成支付操作后,系统需要更新订单状态为 “已支付”,并扣除相应的库存。在高并发场景下,由于网络波动等原因,部分用户的支付请求可能会被重复发送。在一次促销活动中,短时间内有大量用户同时进行支付,系统每秒可能会接收到数以千计的支付请求。如果没有有效的防重机制,就可能出现同一笔订单被多次更新状态和重复扣除库存的情况,这将给商家带来巨大的经济损失。

问题分析

出现重复操作的根本原因在于高并发环境下,多个线程同时对共享资源(如订单状态、库存数据)进行操作。由于线程执行的不确定性,当多个线程同时检测到订单未支付且库存充足时,就会同时执行更新订单状态和扣除库存的操作,从而导致”超卖现象“。从 Java 代码层面来看,假设场景:

Java 高并发下几种防重方式(防止超卖)

public class PaymentService {

private Order order;

private Inventory inventory;

public void processPayment() {

if (order.getStatus() == OrderStatus.UNPAID && inventory.hasStock()) {

order.setStatus(OrderStatus.PAID);

inventory.deductStock();

}

}

}



在高并发情况下,多个线程同时进入processPayment方法,并且都通过了if条件判断,进而导致重复操作。

数据库层面的防重

唯一索引

在数据库表中针对关键业务字段(如订单号、交易流水号等)创建唯一索引。当插入重复数据时,数据库会抛出唯一约束冲突异常,应用程序可以捕获该异常并进行相应处理,告知用户操作重复。例如,在订单表中对订单号字段创建唯一索引:

CREATE UNIQUE INDEX idx_order_number ON orders (order_number);


在 Java 代码中,当插入订单数据时:

try {

// 执行插入订单的SQL语句

jdbcTemplate.update("INSERT INTO orders (order_number, amount, status) VALUES (?,?,?)", orderNumber, amount, status);

} catch (DataIntegrityViolationException e) {

// 捕获唯一约束冲突异常,提示用户订单已存在

System.out.println("订单已存在,请勿重复提交");

}


乐观锁

利用数据库的乐观锁机制,在更新数据时,通过版本号或时间戳来确保数据在更新前没有被其他线程修改。例如,在订单表中添加一个version字段:

CREATE TABLE orders (

id INT PRIMARY KEY,

order_number VARCHAR(50),

amount DECIMAL(10, 2),

status VARCHAR(20),

version INT

);

在 Java 代码中,更新订单状态时:

int updatedRows = jdbcTemplate.update("UPDATE orders SET status =?, version = version + 1 WHERE order_number =? AND version =?", OrderStatus.PAID, orderNumber, currentVersion);

if (updatedRows == 0) {

// 说明数据已被其他线程修改,提示用户操作失败或重试

System.out.println("操作失败,请重试");

}


应用层面的防重

基于内存的缓存

使用如 Redis 等内存缓存来存储已经处理过的请求标识(如订单号)。在处理请求前,先查询缓存中是否存在该标识。如果存在,则说明该请求已被处理过,直接返回结果;否则,将请求标识存入缓存,并处理请求。以下是使用 Jedis 操作 Redis 实现防重的示例代码:

public class PaymentService {

private Jedis jedis;

public PaymentService() {

jedis = new Jedis("localhost", 6379);

}

public void processPayment(String orderNumber) {

String key = "processed_order:" + orderNumber;

if (jedis.exists(key)) {

System.out.println("订单已处理,请勿重复提交");

return;

}

// 处理支付逻辑

//...

// 支付成功后,将订单号存入缓存,设置过期时间为防止缓存数据过多

jedis.setex(key, 3600, "processed");

}

}


分布式锁

在分布式系统中,可以使用分布式锁来确保同一时间只有一个线程能够处理某个请求。例如,使用 Redis 的 SETNX(SET if Not eXists)命令来实现分布式锁:

public class DistributedLock {

private Jedis jedis;

private String lockKey;

private String requestId;

private int expireTime;

public DistributedLock(Jedis jedis, String lockKey, int expireTime) {

this.jedis = jedis;

this.lockKey = lockKey;

this.requestId = UUID.randomUUID().toString();

this.expireTime = expireTime;

}

public boolean tryLock() {

String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);

return "OK".equals(result);

}

public void unlock() {

String script = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";

jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

}

}


在支付服务中使用分布式锁:

public class PaymentService {

private Jedis jedis;

public PaymentService() {

jedis = new Jedis("localhost", 6379);

}

public void processPayment() {

DistributedLock lock = new DistributedLock(jedis, "payment_lock", 10);

if (lock.tryLock()) {

try {

// 处理支付逻辑

//...

} finally {

lock.unlock();

}

} else {

System.out.println("操作正在进行,请稍后重试");

}

}

}


在 Java 高并发场景下,通过数据库层面和应用层面的多种防重手段,可以有效地避免重复操作带来的问题。开发者应根据具体业务场景和系统架构,选择合适的防重方案,以确保系统的高效、稳定运行。随着技术的不断发展,未来还会有更多创新的解决方案出现,我们需要持续关注并学习,以应对日益复杂的高并发挑战。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表