幂等性(Idempotence)是指一个操作可以重复执行多次,但结果总是相同的。

在电商系统中,确保关键操作的幂等性是防止重复下单的重要手段。

其实现在的程序员大多参加工作之前接受培训就已经知道相关术语了,在我那个时候,很多程序员刚工作都不清楚什么叫幂等性。

甚至有很多程序员因为不在互联网公司工作,几年下来都不清楚。

假设我们在提交订单时,系统会生成一个唯一的订单号,并将订单状态保存到数据库中。

每次用户提交订单时,系统首先检查该订单号是否已经存在,如果存在则直接返回订单结果,避免重复处理。

public class OrderService {
    private Map<String, Order> orderDatabase = new ConcurrentHashMap<>();

    public synchronized String submitOrder(Order order) {
        String orderId = order.getOrderId();
        // 检查订单号是否已存在
        if (orderDatabase.containsKey(orderId)) {
            return "Order already processed";
        }
        // 保存订单到数据库
        orderDatabase.put(orderId, order);
        // 模拟订单处理
        processOrder(order);
        // 返回订单处理结果
        return "Order submitted successfully";
    }

    private void processOrder(Order order) {
        // 模拟订单处理逻辑
        System.out.println("Processing order: " + order.getOrderId());
    }
}

技术方案

方案一:前端控制——提交按钮置灰

前端控制是指在用户提交订单后,将提交按钮置灰,禁止用户再次点击。

这个方法简单直观,但只适用于低并发场景,无法完全解决问题。

// JavaScript 示例代码
document.getElementById('submitButton').addEventListener('click', function() {
    this.disabled = true;
    // 提交订单逻辑
    submitOrder();
});

function submitOrder() {
    // 模拟订单提交逻辑
    setTimeout(function() {
        alert('订单提交成功');
        document.getElementById('submitButton').disabled = false;
    }, 2000);
}

方案二:请求唯一ID与数据库索引

通过为每个订单请求生成唯一ID,并在数据库中建立唯一索引,确保每个请求只能处理一次。

public class OrderController {
    private Map<String, Boolean> requestDatabase = new ConcurrentHashMap<>();

    @PostMapping("/submitOrder")
    public ResponseEntity<String> submitOrder(@RequestBody OrderRequest request) {
        String requestId = request.getRequestId();
        // 检查请求ID是否已存在
        if (requestDatabase.containsKey(requestId)) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("Duplicate request");
        }
        // 将请求ID存储到数据库
        requestDatabase.put(requestId, true);
        // 处理订单
        processOrder(request);
        return ResponseEntity.ok("Order submitted successfully");
    }

    private void processOrder(OrderRequest request) {
        // 模拟订单处理逻辑
        System.out.println("Processing order: " + request.getOrderId());
    }
}

方案三:Redis分布式锁

Redis分布式锁是一个高效的并发控制手段,可以确保同一时间只有一个请求在处理。

截至目前为止,主流依然是Redission,历史文章也有关于这个的讲解,可以自己去看。

public class OrderService {
    private RedissonClient redissonClient;

    public OrderService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public String submitOrder(Order order) {
        // 获取Redis锁
        RLock lock = redissonClient.getLock("orderLock:" + order.getOrderId());
        try {
            // 尝试获取锁,等待时间为10秒
            if (lock.tryLock(10, TimeUnit.SECONDS)) {
                // 检查订单是否已存在
                if (orderExists(order.getOrderId())) {
                    return "Order already processed";
                }
                // 保存订单到数据库
                saveOrderToDatabase(order);
                return "Order submitted successfully";
            } else {
                return "Unable to acquire lock, please try again";
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return "Error occurred, please try again";
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    private boolean orderExists(String orderId) {
        // 模拟订单是否存在检查
        return false;
    }

    private void saveOrderToDatabase(Order order) {
        // 模拟保存订单到数据库
        System.out.println("Saving order to database: " + order.getOrderId());
    }
}

方案四:Token机制

Token机制通过在用户提交订单时生成唯一的Token,确保每个订单请求都有唯一标识。

这个其实一般会和Redis结合使用,效果更佳。

public class OrderController {
    private Map<String, Boolean> tokenDatabase = new ConcurrentHashMap<>();

    @PostMapping("/submitOrder")
    public ResponseEntity<String> submitOrder(@RequestBody OrderRequest request) {
        String token = request.getToken();
        // 检查Token是否已使用
        if (tokenDatabase.containsKey(token)) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("Duplicate request");
        }
        // 标记Token为已使用
        tokenDatabase.put(token, true);
        // 处理订单
        processOrder(request);
        return ResponseEntity.ok("Order submitted successfully");
    }

    private void processOrder(OrderRequest request) {
        // 模拟订单处理逻辑
        System.out.println("Processing order: " + request.getOrderId());
    }
}


如何1秒处理10万订单而不出错?_redis

实战案例

还是以电商平台在高并发场景下实施防重复下单策略作为案例,通过Redis分布式锁Token机制,降低重复下单率。

伪代码如下:

public class OrderService {
    private RedissonClient redissonClient;
    private Map<String, Boolean> tokenDatabase = new ConcurrentHashMap<>();

    public OrderService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public String submitOrder(Order order) {
        String token = order.getToken();
        // 获取Redis锁
        RLock lock = redissonClient.getLock("orderLock:" + token);
        try {
            // 尝试获取锁,等待时间为10秒
            if (lock.tryLock(10, TimeUnit.SECONDS)) {
                // 检查Token是否已使用
                if (tokenDatabase.containsKey(token)) {
                    return "Order already processed";
                }
                // 标记Token为已使用
                tokenDatabase.put(token, true);
                // 保存订单到数据库
                saveOrderToDatabase(order);
                return "Order submitted successfully";
            } else {
                return "Unable to acquire lock, please try again";
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return "Error occurred, please try again";
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    private void saveOrderToDatabase(Order order) {
        // 模拟保存订单到数据库
        System.out.println("Saving order to database: " + order.getOrderId());
    }
}

其实很简单,代码不是主要的,核心还是思想。

在高并发环境下,技术、产品和运营团队需要协同作战,共同解决这一问题。

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!