批量数据请求问题

2023-10-17 19:32:30 +08:00
 u3f3o3333

背景

Java 小白,刚入门 SpringBoot 项目练手,现有一个需求要将本地数据库中的设备信息表批量发送给第三方 API 接口,本地设备信息表由用户导入,表结构如下:

id 为主键,device_ip 为设备 IP 地址信息,host_id 为第三方系统 API 返回的设备 ID 值,其他列为设备的相关属性参数。

id device_ip host_id device_attr_1 ……
1 x.x.x.x 1359652 aaaaaa ……
2 y.y.y.y 8496143 bbbbb ……

当用户点击“插入”操作后,需要将表中所有的数据,按照规定的 JSON 格式,将其发送给第三方系统的 API 接口。

如果执行成功,第三方系统 API 接口会返回设备 ID 值,将其保存到表中对应设备条目的 host_id 值中。

如果执行失败,第三方系统 API 接口会返回 error 消息,需要记录未成功添加的设备清单,供用户核查。

目前是写了一个异步方法,读取全表的设备信息为放入一个 List ,根据要求构造 JSON 数据,一条一条发送,每次 API 请求预计消耗 0.1-0.2s ,当数据量达到万级以上时,整个添加过程会达到半小时,时间太长。

现在想缩短整个添加过程的时长,目前在认知范围内考虑两种改进方向:

方案一

将这个 List 通过多线程的方式访问 API 接口传送数据,但因为对于多线程模式不是很了解,不知道会不会造成数据不一致的问题,因为需要记录 API 返回的 host_id 值。虽然初步查了一下资料,但是还是没有很清楚的概念具体应该如何实现。

方案二

该第三方 API 接口,允许一次传入多个设备的数据,但一次最多只能传 100 条左右的数据。考虑将整个 List 拆成 100 个一组,每组里面包含 100 个设备信息的 JSON 数据,将这 100 个数据给 API 发送一次。

但是这 100 条信息如果其中有任意一台添加失败,会导致所有这 100 台全都添加失败,API 只会返回添加失败信息,并不会指明哪一条数据添加异常,所以考虑此方法还需要在发生 API 请求失败时,切换成一条一条添加,这样就可以判断出哪一条数据添加异常,以便将无法添加的条目反馈供用户核验,这样也能保证无异常的设备都可以被添加进第三方系统中。这种方法时间起来感觉逻辑较复杂。

求助

因为新手没什么项目经验,不知道那种方案比较合适,或者是否有其他更优的解决方案,谢谢!

目前 Service 的部分逻辑代码如下:

import ...

@EnableAsync
@Service
public class MonitorServiceImpl implements IMonitorService
{
    @Autowired
    private MonitorInfoMapper monitorInfoMapper;
    
    @Async
    @Override
    public void startMonitor()
    {
        List<Equipment> equipmentList = monitorInfoMapper.selectEquipmentList();//获取数据表中所有设备清单
        for (Equipment equip : equipmentList) {
            //..............
            //处理数据,构造 JSON 数据格式,最后将一台设备信息放入 hostInfoJSON 变量
            //iApiService 写的第三方访问接口
            String creatHostResp = iApiService.insertHostList(authToken, hostInfoJSON);

            String hostid;
            if (creatHostResp.contains("error")) {
                hostid = "-1";
                JSONObject resposeJson = JSONObject.parseObject(creatHostResp);
                String errorString = resposeJson.getJSONObject("error").getString("data");
                log.info("添加失败!错误信息:" + errorString);
            } else {
                JSONObject resposeJson = JSONObject.parseObject(creatHostResp);
                List<String> hostIdList = new ArrayList<>();
                JSONArray resposeHostIdList = resposeJson.getJSONObject("result").getJSONArray("hostids").getString(0);
                log.info("添加成功,hostid:" + hostIdList);
            }
            equip.setHostId(hostid);
            monitorInfoMapper.updateEquipment(equip);
    }
    //......
}
1199 次点击
所在节点    Java
8 条回复
pddgoods
2023-10-17 19:39:25 +08:00
核心点,总共多少数据。
u3f3o3333
2023-10-17 19:40:11 +08:00
@pddgoods 一次需要插入数据量在万级左右
pocketz
2023-10-17 21:05:29 +08:00
你不是会写异步方法吗。。。
直接把 http 请求的部分拆出来走原生 executor 线程池
lsk569937453
2023-10-18 08:44:08 +08:00
1.最好还是按照批次发送,能节省不少网络请求,如果担心失败后需要重试的条数过多,可以把 100 个一组,改为 10 个一组。
2.可以 startMonitor 里面把 equipmentList 切分好(按照 10 个一组),然后把每组的数据丢到线程池去请求 API(前提对面没有并发数的限制)

其实这个需求还有几点需要明确:
1.是否允许用户在发送数据期间重复点击"插入"。如果允许的话,是不是需要存储每个批次的任务状态?
2.如果任务失败后需要重试,那么何为失败,网络请求失败?http 状态码非 200 ?失败后需要重试几次?
timtraveler
2023-10-18 15:11:42 +08:00
方案一 + 方案二,多线程+批量插入 100 条数据。
1 ,单独创建一张第三方调用结果表( A 表),保存所有设备数据 id 字段、插入成功或失败的状态字段、失败次数字段。
2 ,在第一轮插入之后,查询 A 表中的所有失败的设备 id ,关联到总表,根据失败次数,将插入数量改为 50 (第一次失败)、10 (第二次失败)、1 (第三次失败)重新进行插入,最后剩下的为无效数据,额外进行处理
xiaohang427
2023-10-18 15:43:55 +08:00
1 、使用游标查询,防止一下查询的数据太大吃内存
2 、开线程池处理第三方通讯,并且根据结果更新数据库
u3f3o3333
2023-10-18 16:39:17 +08:00
@lsk569937453 感谢大佬建议!关于需要明确的 2 点:1 、目前简单考虑是不允许用户在发送数据期间重复点击"插入",会提示数据正在处理。2 、除了常规网络问题可能导致的任务失败外,其他主要的一种原因是,第三方系统之前有垃圾数据没清理干净,会提示设备添加失败,比如之前在第三方系统有一个 192.168.1.1 的设备数据条目没有删除,而本次导入数据中也有一个 192.168.1.1 设备条目,此时第三方系统并不会覆盖,而是会返回报错添加设备失败。当发送批量数据时,若有一个数据出现冲突,它并不会明确提示哪个设备添加失败,只会提示本次导入任务失败。目前是遇到失败不重试,返回导入失败的条目,给用户核实后,下次再进行单独的添加操作。
u3f3o3333
2023-10-18 16:40:44 +08:00
@pocketz
@timtraveler
@xiaohang427
感谢诸位大佬建议和指导!我再思考研究下

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/982879

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX