高并发下如何防重?

本文讲述了在高并发环境下,系统批量复制商品时出现重复数据的问题及其解决方案。首先介绍了问题背景,包括需求、性能优化以及出现的并发问题。接着分析了多线程消费和顺序消费的策略,以及如何利用唯一索引、分布式锁和消息队列来避免数据重复。最后,提出了防重表的概念,作为解决有逻辑删除功能表的重复数据问题的一种有效方法。
摘要由CSDN通过智能技术生成

前言

最近测试给我提了一个 bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。

追查原因之后发现,这个事情没想象中简单,可以说一波多折。

1. 需求

产品有个需求:用户选择一些品牌,点击确定按钮之后,系统需要基于一份默认品牌的商品数据,复制出一批新的商品

拿到这个需求时觉得太简单了,三下五除二就搞定。

我提供了一个复制商品的基础接口,给商城系统调用。

当时的流程图如下:

如果每次复制的商品数量不多,使用同步接口调用的方案问题也不大。

2. 性能优化

但由于每次需要复制的商品数量比较多,可能有几千。如果每次都是用同步接口的方式复制商品,可能会有性能问题。因此,后来我把复制商品的逻辑改成使用 MQ 异步处理。

改造之后的流程图:

复制商品的结果还需要通知商城系统:

这个方案看起来,挺不错的。但后来出现问题了。

3. 出问题了

测试给我们提了一个 bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。

经过追查之后发现,商城系统为了性能考虑,也改成异步了。他们没有在接口中直接调用基础系统的复制商品接口,而是在 job 中调用的。

站在他们的视角流程图是这样的:

 

用户调用商城的接口,他们会往请求记录表中写入一条数据,然后在另外一个 job 中,异步调用基础系统的接口去复制商品。

实际情况是这样的:商城系统内部出现了 bug,在请求记录表中,同一条请求产生了重复的数据。这样导致的结果是,在 job 中调用基础系统复制商品接口时,发送了重复的请求

刚好,基础系统现在是使用 RocketMQ 异步处理的。由于商城的 job 一次会取一批数据(比如 20 条记录),在极短的时间内(其实就是在一个 for 循环中)多次调用接口,可能存在相同的请求参数连续调用复制商品接口情况。于是,出现了并发插入重复数据的问题。

为什么会出现这个问题呢?

4. 多线程消费

RocketMQ 的消费者,为了性能考虑,默认是用多线程并发消费的,最大支持 64 个线程。

例如:

@RocketMQMessageListener(topic = "${com.susan.topic:PRODUCT_TOPIC}",        consumerGroup = "${com.susan.group:PRODUCT_TOPIC_GROUP}")@Servicepublic class MessageReceiver implements RocketMQListener<MessageExt> {
  
    @Override    public void onMessage(MessageExt message) {
          String message = new String(message.getBody(), StandardCharsets.UTF_8);        doSamething(message);    }}

也就是说,如果在极短的时间内,连续发送重复的消息,就会被不同的线程消费。

即使在代码中有这样的判断:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾听铃的声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值