性能优化实践

95 阅读3分钟

背景

页面点击构建按钮,调用builder接口异步的构建三个层级(父子孙)的信息。 A是子类信息,BCD均是关联信息。这四个接口都是http调用外部接口,然后数据入库BCD需要关联A。 ABCD四个接口调用由例

调用A(1,2)返回
{
  "1": [
    {"id": 10,"age": 18},
    {"id": 11,"age": 18}
  ],
  "2": [
    {"id": 12,"age": 18},
    {"id": 13,"age": 18}
  ]
}

调用B,C,D(10,11,12,13)返回
{
  "data": 
  [
    {"aid":10,"date":2022},
    {"aid":11,"date":2022},
    {"aid":12,"date":2022},
    {"aid":13,"date":2022}
  ]
}

调用逻辑

@Autowired
private IRemoteService remoteService;

public void builder(ParamInfo paramInfo){
    //获取子类信息
    List<A> alist = remoteService.getA(paramInfo.getPersonIds());
    List<String> newPersonIds = new ArrayList<>();
    for (A a : alist) {
        newPersonIds.addAll(a.getChildList());
    }
    paramInfo.setPersonIds(newPersonIds);
    //B信息
    List<B> bList = remoteService.getB(paramInfo);
    //C信息
    List<C> cList = remoteService.getC(paramInfo);
    //D信息
    List<D> dList = remoteService.getD(paramInfo);
    // 数据入库(循环调用数据库)
    insertDB(alist,bList,cList,dList);
    //递归调用
    builder(paramInfo);
}

private void insertDB(List<A> alist, List<B> bList, List<C> cList, List<D> dList) {
    saveA(alist);
    // 保存A后返回数据库主键
    List<String> ids = alist.stream().map(A::getId).collect(Collectors.toList());
    // 关联Aid 插入数据库
    saveB(bList,ids);
    saveC(cList,ids);
    saveD(dList,ids);
}

第一次优化 处理数据库循环调用

  1. 循环调用的原因是B,C,D需要A保存后的数据库主键,插入数据库。也就是A,B,C,D的关联关系是通过A.dbId来维系的。处理方案是给A,B,C,D加上虚拟主键(UUID生成)

  2. 递归调用接口返回的数据量大小不一,我们先调用接口拿到所有的数据给A,B,C,D建立逻辑关系。 接口调用完成后,先保存A集合的数据返回数据库主键,B,C,D再根据虚拟主键找到A的数据库主键,然后使用线程池1000条一个线程调用提高处理能力

Map<Integer, B> bMap = bList.stream().collect(Collectors.toMap(B::getAid, Function.identity()));
Map<Integer, C> cMap = cList.stream().collect(Collectors.toMap(C::getAid, Function.identity()));
Map<Integer, D> dMap = dList.stream().collect(Collectors.toMap(D::getAid, Function.identity()));
String tempId;
for (A a : aList) {
    tempId = UUID.randomUUID().toString();
    a.setTempId(tempId);
    bMap.get(a.getId()).setTempId(tempId);
    cMap.get(a.getId()).setTempId(tempId);
    dMap.get(a.getId()).setTempId(tempId);
}

第二次优化 远程接口优化

经过第一次优化,数据入库的能力得到了提升。接口调用的瓶颈还是存在。 处理方案:

  1. 使用HttpClient的连接池
  2. 获取A,B,C,D的四个远程接口都是支持多参数传入的,我们要最大限度的利用可传入的参数数量。也就是我的每次接口请求都尽量把参数塞满(接口的参数有数量限制) 缓存请求参数,当请求的参数数量达到了接口参数限制的最大数量时立即发送请求,如果未达到最大数量等待500ms,500ms时间结束马上发送请求
/**
 * 数据填充满或达到发送时间发送数据
 * @param now 当前时间
 */
public boolean send(long now){
    return size > 50 || (now - sendTime) >= 500;
}

第三次优化 展示方案优化

经过数据和远程接口的优化后,在返回的子级和孙级数据量较大的情况下运行时间任然还是需要较长的处理时间。 将原来的异步处理改成同步+异步。先同步返回一级信息和二级节点的部分信息,其他相关的信息和三级节点的信息由原来的异步线程处理。新增一个查询接口给页面查询,在页面点击查询某二三级节点时将以处理的数据用一个Map保存下,异步线程跑任务时去重。数据库也做下幂等校验。 这样页面可以更快的查询到数据,用户体验提升。