求教一个高并发插入数据幂等的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
wym7223645
V2EX    程序员

求教一个高并发插入数据幂等的问题

  •  1
     
      wym7223645 2019-09-23 19:24:08 +08:00 3333 次点击
    这是一个创建于 2214 天前的主题,其中的信息可能已经有所发展或是发生改变。

    因为业务十分复杂,简化描述问题如下

    假设现在有一个 A 表,字段如下 ID 主键,自增 BILL_NO 单据编号 BILL_NAME 单据名称 BACKUP_COLUMN 备用列,用于存储外部系统传递的单据编号 TASK_TYPE 数据类型 …… 其余字段

    接口:

    外部系统传递 BACKUP_COLUMN,根据 BACKUP_COLUMN 从我们系统的 B、C 表中查询数据拼接并插入 A 表。 此类接口进入 A 表的时候 TASK_TYPE 为 002,且只有通过这个接口进来的是 002

    问题:

    此接口因为外部系统原因经常会在几毫秒甚至几纳秒内重发 N 次,导致 A 表出现重复数据

    目前解决思路

    方式 1:

    一开始打算 BACKUP_COLUMN 加唯一约束,但是因为这个表还存其他业务数据,其他数据中 BACKUP_COLUMN 的值允许重复(例如 TASK_TYPE 为 001 时 BACKUP_COLUMN 允许为空或者重复),改动代价太大,时间及人员上不允许,故此放弃增加唯一约束的方式

    方式 2:

    在插入之前做一次查询,如果存在则不插入,本地及测试环境简单测试发现拦截住了,就部署到了生产环境 上了生产环境之后发现,还是有重复数据,经过查询日志发现,对方系统多次重复请求在几毫秒甚至及纳秒之内(数据库日志只记录到秒,最后还是通过打印时间发现的时间这么短) 这么短的间隔导致了第一次请求的数据还没执行到 inser 之前第二个请求就进来了,此时第二个请求的查重还是没有数据,最终第一次、第二次都插入成功。 这种情况只适用于两次重发间隔不是很短的情况

    方式 3:

    基于方式 2 的拦截失败,我们在数据插入成功之后,根据 BACKUP_COLUMN、TASK_TYPE 进行一次查重,如果有多个,保留时间最早的一个,删除(业务回退)其余重复数据,暂时没发现问题

    ** **

    目前我们是通过方式 2 和方式 3 结合的方式保证数据不重复推送 因为现在方式 3 中的删除(回退)逻辑并不复杂,但是之后这里逻辑会变的超级复杂,或者出现复杂接口,个人个感觉方式 3 并不是很合适

    请问各位大佬是否还有其他方式保证接口的幂等性或者说让重复数据不入库

    11 条回复    2019-09-24 17:58:52 +08:00
    killergun
        1
    killergun  
       2019-09-23 22:14:29 +08:00
    感觉使用 Redis 锁最简单粗暴
    reus
        2
    reus  
       2019-09-23 22:33:11 +08:00
    如果数据库不是 MySQL,可以用条件性唯一索引,也就是符合 where 子句的行,才增加唯一索引。
    用 MySQL 的话,就没有这个功能了。
    hspeed18
        3
    hspeed18  
       2019-09-23 23:33:40 +08:00
    联合索引不行吗?
    intermole
        4
    intermole  
       2019-09-23 23:40:34 +08:00 via iPhone
    联合索引1
    Vegetable
        5
    Vegetable  
       2019-09-23 23:51:41 +08:00
    同意 1 楼,可以加一个中间层过滤数据.两次间隔时间这么近不要忘了检查是不是系统有 bug.感觉用单纯用数据库解决,对数据库压力比较大一点.插入的时候检查索引有一点点违和.如果量级不大倒是没什么.
    aliipay
        6
    aliipay  
       2019-09-24 01:12:44 +08:00
    BACKUP_COLUMN 如果能建索引的话,直接用悲观锁,两个事务同时执行时候有一个会触发 deadlock 退出事务,一个会成功写入, 避免其它逻辑导致死锁误判,可以在延时一小段时间后查询再决定是否重新写入数据库
    lihongjie0209
        7
    lihongjie0209  
       2019-09-24 08:58:29 +08:00
    把请求放入队列中,单线程读取,查询数据库, 插入
    wym7223645
        8
    wym7223645  
    OP
       2019-09-24 09:56:58 +08:00
    @killergun 我们这个项目的机房在北京,已经没有地方部署 Redis 了,想部署 Redis 需要部署到西安的机房,两边有延迟还挺厉害

    @reus 我们是 DB2 的库,简单搜了一下没发现条件性唯一索引,回头我再搜搜

    @Vegetable 的确可以增加一个中间表,数据线入中间表,然后再插入正式表。
    两次间隔这么近的确不正常,找了对端系统他们说使用了某消息中间件,中间件有问题导致,但是中间件在甲方手里维护,甲方不解决,对端系统也不想通过程序解决,所以只能我们来想办法解决了
    ivyxjc
        9
    ivyxjc  
       2019-09-24 10:30:36 +08:00 via Android
    最简单的方法就建一张有唯一索引的中间表,不需要额外的服务,也不用改框架。

    一定要先把数据重复和时序问题解决了再做业务,否则业务一复杂,就很难写,很难测了。
    justRua
        10
    justRua  
       2019-09-24 11:19:53 +08:00
    方式 2 出现的问题是 写偏差,用可序列化的事务级别可以避免。或加一张辅助表,也可以加一辅助列(值为 TASK_TYPE TASK_TYPE +BACKUP_COLUMN,使其有唯一性,然后加上唯一索引)。
    wym7223645
        11
    wym7223645  
    OP
       2019-09-24 17:58:52 +08:00
    @justRua 貌似只能加辅助表,我们是集群部署,事务单机可以,集群下负载到不同的集群就不行了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5204 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 09:15 PVG 17:15 LAX 02:15 JFK 05:15
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86