ITPub博客

首页 > 数据库 > 数据库开发技术 > 你一定要知道电商业务中解决幂等场景的技术方案

你一定要知道电商业务中解决幂等场景的技术方案

原创 数据库开发技术 作者:服务端技术栈 时间:2024-09-10 23:31:34 0 删除 编辑

大家好,本博客致力于分享互联网领域的各种技术干货,欢迎关注我们一起交流一起学习哦!

一、背景

幂等性问题在电商系统里真的是常见的老大难问题,尤其是遇到高并发的场景时。我们需要保证某些操作不会因为重复执行而导致数据不一致,比如重复生成订单、多次发放优惠券等。今天我们就聊聊电商中的几个常见场景,以及如何通过一些设计技巧来保证幂等性。

二、电商业务中的幂等性场景

1、提交订单

场景描述:用户在下单时,可能会因为网络延迟或重复点击“提交订单”按钮而导致重复请求。如果没有处理好幂等性,系统可能会生成多个订单,进而影响库存、结算等一系列问题。

解决方案:

签名机制:为了保证幂等性,我们可以给每一次下单请求生成一个唯一的签名值。具体方法是,前端将请求中的关键参数(比如用户 ID、商品 ID、时间戳等)通过一定规则进行签名(如用 MD5、SHA256 等哈希算法),生成唯一的签名值 sign。这个签名值随请求一起发送到服务端。

服务端逻辑:当服务端接收到请求时,首先会根据签名值 sign 查询 Redis,看这个签名值是否已经存在。如果存在,说明订单已经处理过了,直接返回处理结果;如果不存在,才会继续创建订单,并把这个签名值存到 Redis 中,设置一个适当的过期时间,以防止重复请求处理。

防重复提交的幂等设计思路:

  1. 请求发起时,前端对用户请求参数生成签名值。
  2. 服务端检查 Redis 看签名值是否已处理,如果已处理则直接返回结果。
  3. 如果未处理,则生成订单并记录签名值,保证只处理一次。

具体代码示例:

复制代码
// 检查签名值是否已存在
if (redis.exists(sign)) {
    return "订单已生成"; // 重复请求,直接返回
} else {
    // 处理订单生成逻辑
    createOrder();
    redis.set(sign, orderId, 10 * 60); // 设置 10 分钟过期时间
    return "订单创建成功";
}
这种方案通过前端生成签名值,后端只认一次请求的方式,可以有效防止重复下单问题。

2、领券与发放权益

场景描述:用户在活动期间参加领券活动,比如每个用户每天只能领取一张券。然而,由于并发请求或恶意重复请求,可能导致用户领取多张券。我们需要确保每个用户在活动期间只能领取一次指定的优惠券。

解决方案:

记录表设计:创建一个“领券记录表”来记录用户领取券的历史。这个表包括 user_id(用户 ID)、activity_id(活动 ID)、coupon_id(券 ID)、以及 create_time(领取时间)等信息,并通过 user_id + activity_id + coupon_id 设定唯一索引,确保用户在每个活动中只能领取一张券。


CREATE TABLE coupon_records (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    activity_id BIGINT NOT NULL,
    coupon_id BIGINT NOT NULL,
    create_time DATETIME,
    UNIQUE KEY uniq_coupon (user_id, campaign_id, coupon_id)
);

分布式锁的引入:为了防止并发情况下用户多次领取相同的优惠券,可以使用Redis实现用户维度的分布式锁。每次用户发起领券请求时,首先在 Redis 中为该用户加锁,确保该用户的领券操作只能有一个请求进行。

具体实现步骤:

  1. 用户发起领券请求:

  • 服务端使用用户 ID 作为锁的 key,使用 Redis 的 SETNX(SET if Not eXists)命令为该用户加锁,锁的生存时间设定为一个合理值(如 10 秒)。
  • 检查领券记录:

    • 如果锁获取成功,服务端会查询 coupon_records 表,检查该用户是否已经领取了该优惠券。
    • 如果已经领取,返回“已领取”结果。
  • 处理领券逻辑:

    • 如果用户未领取,则生成领券记录,并返回“领取成功”。
  • 释放锁:

    • 无论领券成功与否,在处理完请求后,服务端都会主动释放 Redis 中的分布式锁。

    领券逻辑:

    public String claimCoupon(Long userId, Long activityId, Long couponId) {
        String lockKey = "lock:coupon:" + userId;
        boolean isLockAcquired = redis.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

        if (!isLockAcquired) {
            return "请求频繁,请稍后再试"; // 如果无法获取锁,说明用户在短时间内重复请求,直接返回
        }

        try {
            // 检查是否已经领取过该优惠券
            if (couponRecordsExist(userId, activityId, couponId)) {
                return "已领取";
            } else {
                // 创建领券记录
                createCouponRecord(userId, activityId, couponId);
                return "领取成功";
            }
        } finally {
            // 释放分布式锁
            redis.del(lockKey);
        }
    }

    通过这个分布式锁和唯一索引的设计,可以保证无论用户如何重复请求,都只能领取一张优惠券。

    3、订单消费与业务处理

    场景描述:在电商促销活动中,用户消费达到一定次数可能会触发某种奖励。系统需要确保消费记录只会被处理一次,避免因为重复处理导致奖励多发。

    解决方案:

    订单处理记录表设计:可以设计一个 order_processed 表,记录所有已处理过的订单 ID。表的字段包括 order_id(订单 ID)、user_id(用户 ID)、process_time(处理时间)等,并将 order_id 设为唯一索引,确保每个订单只会被处理一次。

    CREATE TABLE order_processed (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        order_id BIGINT NOT NULL,
        user_id BIGINT NOT NULL,
        process_time DATETIME,
        UNIQUE KEY uniq_order (order_id)
    );

    业务逻辑:每当接收到一笔订单时,系统首先检查 order_processed 表,看这笔订单是否已经处理过。如果已经处理过,则跳过处理逻辑;如果没处理过,则继续执行奖励或其他业务操作。订单处理逻辑:

    // 检查订单是否已经处理过
    if (orderProcessed(orderId)) {
        return "订单已处理";
    } else {
        // 先记录订单处理状态,防止重复处理
        recordOrderProcessed(orderId, userId);
        
        // 处理订单业务逻辑
        processOrder(orderId);
        
        return "处理成功";
    }

    通过这种方式,可以确保每个订单的业务处理只会执行一次,避免了重复操作带来的数据问题。

    三、总结

    幂等性虽然看上去只是防止重复操作,但其实在实际开发中会涉及到很多细节和设计。通过签名机制、数据库唯一索引以及订单处理记录表等手段,我们可以有效保证系统的幂等性,避免因为重复请求或处理导致的数据混乱。

    希望这些实际的场景和解决方案能帮你在开发中少踩坑,轻松应对幂等性问题!

    关注我们一起学习技术吧,坚持相信有输入一定要有输出,希望我们的技术能力越来越强大。



    原文作者:服务端技术栈;如有侵权,请联系管理员删除。

    请登录后发表评论 登录
    全部评论

    注册时间:2019-03-08

    • 博文量
      97
    • 访问量
      41428