超发与超领问题

现有领券流程存在两个问题:
    现象:并发情况下,优惠券超发的问题,用户超领的问题
    原因:整个流程里存储一系列的校验,校验通过之后才能领券,涉及了多次校验与数据库操作,这些操作不是原子性
并发问题的通用解决方法:
    通常情况下,只要是非原子性的操作,在高并发情况下都会出现并发安全问题。一般会使用加锁的方式解决问题
    乐观锁:
        比较乐观看问题的一种思想,
            认为其它线程很少会修改我的数据,所以我不需要加排它性的、独占性的锁,因为会影响性能。
            但是为了防止真的有其它线程修改数据,导致出现问题:利用CAS线程在每次修改之前先对原数据进行校验
                如果数据没有被其它线程修改,就执行自己的修改操作
                如果数据被其它线程修改了,就放弃操作
        优点:性能好,安全性也好;缺点:如果写操作多,会出现比较高的失败率
        适合:读多写少的情况
        实现:对数据增加版本号,每次修改之前,先对比版本号,如果版本号不同,就说明被修改了
                 如果版本号相同,就执行修改操作,然后把版本号+1
    悲观锁:
        比较悲观看问题的一种思想
            总是认为数据会被其它线程做修改操作,为了避免其它线程的改数据影响自己,对数据加排它性、独占性锁
            自己对数据加锁,然后开始操作。在操作过程中,其它得不到锁,就需要等待
            等待自己释放锁,其它线程才可以抢锁、得到锁再进行操作
            把多线程并发变成了串行
        优点:安全性高;缺点:性能不好
        适合:写多读少的情况
        实现:synchronized、Lock对象

超发与超领问题的解决
    第一个问题,优惠券超发的问题:使用了优化后的乐观锁解决
        update coupon set issue_num = issue_num+1 where issue_num < total_num and id = 优惠券id
        只要已发放数量,没有达到总数量,就允许领取、修改数据
        避免了原始乐观锁的方式,存在大量失败的问题
    第二个问题,优惠券超领的问题:没办法使用乐观锁解决了
        原因:先统计当前用户已经领取了多少张;再进行校验;如果校验通过再修改发放数量、领取用户券
            涉及了不同表、不同数据的多次操作,不能使用乐观锁
        解决:使用了悲观锁,使用synchronized关键字,锁住用户id
    然后又出现了新的问题

### 设计方案:电商优惠 #### 数据库表结构设计 为了有效管理优惠并防止现象,在数据库层面需建立合理的数据模型。具体来说,可以构建两个主要的数据表来支持这一功能: - **优惠批次表 (`coupon_batch`)**:用于存储每一批次放的优惠基本信息,包括但不限于名称、关联规则ID以及总量限制等字段。 ```sql CREATE TABLE IF NOT EXISTS `coupon_batch` ( id INT AUTO_INCREMENT PRIMARY KEY, coupon_name VARCHAR(255), rule_id INT, total_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` - **已取记录表 (`redeemed_coupons`)** :追踪每位用户对于特定批次优惠的实际获取情况,确保不会出设定的数量上限。 ```sql CREATE TABLE IF NOT EXISTS `redeemed_coupons` ( user_id INT, batch_id INT, redeemed_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (batch_id) REFERENCES coupon_batch(id) ); ``` 通过上述两张表格之间的外键关系(`batch_id`),能够精确控制每个用户的行为,并且方便后续查询统计工作[^3]。 #### Redis辅助机制 考虑到高并场景下可能出现的竞争条件问题,仅依赖于SQL事务可能不足以完全杜绝现象的生。因此引入Redis作为额外的安全屏障是非常必要的。可以在Redis中维护一个基于用户ID的集合(set),每当有新的请求到来时先检查该集合内是否存在对应成员;如果不存在,则允许此次操作并将新元素加入其中;反之则拒绝处理当前申请。 ```python import redis def check_and_add_user_to_redis(user_id, batch_key): r = redis.Redis(host='localhost', port=6379, db=0) if not r.sismember(batch_key, str(user_id)): r.sadd(batch_key, str(user_id)) return True return False ``` 此方法不仅提高了系统的响应速度,同时也大大降低了由于网络延迟等因素引起的潜在风险[^4]。 #### MQ消息队列优化 当面对海量用户的同时访问压力时,直接同步写入MySQL可能会成为性能瓶颈所在。此时采用MQ(Message Queue)技术将原本即时的操作转变为异步执行模式不失为一种有效的解决方案之一。即每次成功验证完一位顾客是否有资格获得某张电子礼劵之后立即将相关信息推送到指定的消息队列里去等待进一步处理——比如更新最终的状态至持久化层当中。如此一来既保证了核心逻辑部分不受外界干扰而保持高效运转,又实现了良好的解耦合效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值