因为业务十分复杂,简化描述问题如下
假设现在有一个 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 并不是很合适
请问各位大佬是否还有其他方式保证接口的幂等性或者说让重复数据不入库
1
killergun 2019-09-23 22:14:29 +08:00
感觉使用 Redis 锁最简单粗暴
|
2
reus 2019-09-23 22:33:11 +08:00
如果数据库不是 MySQL,可以用条件性唯一索引,也就是符合 where 子句的行,才增加唯一索引。
用 MySQL 的话,就没有这个功能了。 |
3
hspeed18 2019-09-23 23:33:40 +08:00
联合索引不行吗?
|
4
intermole 2019-09-23 23:40:34 +08:00 via iPhone
联合索引➕1
|
5
Vegetable 2019-09-23 23:51:41 +08:00
同意 1 楼,可以加一个中间层过滤数据.两次间隔时间这么近不要忘了检查是不是系统有 bug.感觉用单纯用数据库解决,对数据库压力比较大一点.插入的时候检查索引有一点点违和.如果量级不大倒是没什么.
|
6
aliipay 2019-09-24 01:12:44 +08:00
BACKUP_COLUMN 如果能建索引的话,直接用悲观锁,两个事务同时执行时候有一个会触发 deadlock 退出事务,一个会成功写入, 避免其它逻辑导致死锁误判,可以在延时一小段时间后查询再决定是否重新写入数据库
|
7
lihongjie0209 2019-09-24 08:58:29 +08:00
把请求放入队列中,单线程读取,查询数据库, 插入
|
8
wym7223645 OP |
9
ivyxjc 2019-09-24 10:30:36 +08:00 via Android
最简单的方法就建一张有唯一索引的中间表,不需要额外的服务,也不用改框架。
一定要先把数据重复和时序问题解决了再做业务,否则业务一复杂,就很难写,很难测了。 |
10
justRua 2019-09-24 11:19:53 +08:00
方式 2 出现的问题是 写偏差,用可序列化的事务级别可以避免。或加一张辅助表,也可以加一辅助列(值为 TASK_TYPE TASK_TYPE +BACKUP_COLUMN,使其有唯一性,然后加上唯一索引)。
|
11
wym7223645 OP @justRua 貌似只能加辅助表,我们是集群部署,事务单机可以,集群下负载到不同的集群就不行了
|