背景
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);
}
//......
}
1
pddgoods 2023-10-17 19:39:25 +08:00
核心点,总共多少数据。
|
3
pocketz 2023-10-17 21:05:29 +08:00
你不是会写异步方法吗。。。
直接把 http 请求的部分拆出来走原生 executor 线程池 |
4
lsk569937453 2023-10-18 08:44:08 +08:00
1.最好还是按照批次发送,能节省不少网络请求,如果担心失败后需要重试的条数过多,可以把 100 个一组,改为 10 个一组。
2.可以 startMonitor 里面把 equipmentList 切分好(按照 10 个一组),然后把每组的数据丢到线程池去请求 API(前提对面没有并发数的限制) 其实这个需求还有几点需要明确: 1.是否允许用户在发送数据期间重复点击"插入"。如果允许的话,是不是需要存储每个批次的任务状态? 2.如果任务失败后需要重试,那么何为失败,网络请求失败?http 状态码非 200 ?失败后需要重试几次? |
5
timtraveler 2023-10-18 15:11:42 +08:00
方案一 + 方案二,多线程+批量插入 100 条数据。
1 ,单独创建一张第三方调用结果表( A 表),保存所有设备数据 id 字段、插入成功或失败的状态字段、失败次数字段。 2 ,在第一轮插入之后,查询 A 表中的所有失败的设备 id ,关联到总表,根据失败次数,将插入数量改为 50 (第一次失败)、10 (第二次失败)、1 (第三次失败)重新进行插入,最后剩下的为无效数据,额外进行处理 |
6
xiaohang427 2023-10-18 15:43:55 +08:00
1 、使用游标查询,防止一下查询的数据太大吃内存
2 、开线程池处理第三方通讯,并且根据结果更新数据库 |
7
u3f3o3333 OP @lsk569937453 感谢大佬建议!关于需要明确的 2 点:1 、目前简单考虑是不允许用户在发送数据期间重复点击"插入",会提示数据正在处理。2 、除了常规网络问题可能导致的任务失败外,其他主要的一种原因是,第三方系统之前有垃圾数据没清理干净,会提示设备添加失败,比如之前在第三方系统有一个 192.168.1.1 的设备数据条目没有删除,而本次导入数据中也有一个 192.168.1.1 设备条目,此时第三方系统并不会覆盖,而是会返回报错添加设备失败。当发送批量数据时,若有一个数据出现冲突,它并不会明确提示哪个设备添加失败,只会提示本次导入任务失败。目前是遇到失败不重试,返回导入失败的条目,给用户核实后,下次再进行单独的添加操作。
|
8
u3f3o3333 OP |