V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
u3f3o3333
V2EX  ›  Java

批量数据请求问题

  •  
  •   u3f3o3333 · 2023-10-17 19:32:30 +08:00 · 1200 次点击
    这是一个创建于 403 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景

    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);
        }
        //......
    }
    
    8 条回复    2023-10-18 16:40:44 +08:00
    pddgoods
        1
    pddgoods  
       2023-10-17 19:39:25 +08:00
    核心点,总共多少数据。
    u3f3o3333
        2
    u3f3o3333  
    OP
       2023-10-17 19:40:11 +08:00
    @pddgoods 一次需要插入数据量在万级左右
    pocketz
        3
    pocketz  
       2023-10-17 21:05:29 +08:00
    你不是会写异步方法吗。。。
    直接把 http 请求的部分拆出来走原生 executor 线程池
    lsk569937453
        4
    lsk569937453  
       2023-10-18 08:44:08 +08:00
    1.最好还是按照批次发送,能节省不少网络请求,如果担心失败后需要重试的条数过多,可以把 100 个一组,改为 10 个一组。
    2.可以 startMonitor 里面把 equipmentList 切分好(按照 10 个一组),然后把每组的数据丢到线程池去请求 API(前提对面没有并发数的限制)

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