前情介绍
规则:
- 同步自己系统中的11417条数据到友军系统中
- 对方拒绝提供批量操作接口(必须一条一条通过网络进行传输 总共要进行11417个网络请求)
- 对方接口每秒请求不能超过200次(实际测试超过10次就开始404)
- 我方数据库要保证查库在2000次/秒这个范围内 防止操作先把自家数据库搞垮
首次操作
流程:
- 分页从库中读取11417条数据 读取6张关联表中的相关数据(每页200条 加上关联表总共7张表每次处理1400条数据)
- 封装7张表的数据 转换为友军需要的数据结构
- 进行网络请求 将数据发送给友军
- 将操作记录写入日志表中 方便结算
实操后发现的问题
可想而知的问题:
- 一条数据的流程走下来大概需要3秒中
- 11417条数据总耗时:34,251秒、570分钟、9.5小时
怀着复杂的心情 跑了十分钟后 直接停掉了任务 赶紧和对方商量批量发送的接口
无情被拒绝 疲软的产品更是别人说什么就是什么
我极力推荐加个批量操作接口 最后对方就是不改 交涉无果还被定了个工作态度不好的名声
但 一切也阻挡不了我这颗优化的心!
优化过程
并行、并行… 这个词无数次的在脑中回响
先计算下理想值:
- 对面接口限制200次每秒 11417/200 = 57秒 乘以单次耗时3秒 171秒完事
- 我方数据库限制2000次每秒 完全可以支持对面200次每秒的数据需求
初次优化
- 分页从库中读取11417条数据 读取6张关联表中的相关数据(每页200条 加上关联表总共7张表每次处理1400条数据)
- 封装7张表的数据 转换为友军需要的数据结构
- 因为接口限制 先开启200个线程 并行进行网络请求 将数据发送给友军
- 将操作记录写入日志表中 方便结算
// 开启多个线程 并行执行同步任务
ExecutorService executor = Executors.newFixedThreadPool(10);
List<CompletableFuture<HqzcPolicyInspectorLog>> futures =
list.stream().map(bean -> CompletableFuture.supplyAsync(() -> upLoadDataBean(bean.getInfo(), bean.getResult(), bean.getDataBean(), lastUpdateTime, type), executor)).collect(Collectors.toList());
inspectorLogs = futures.stream().map(s -> s.join()).collect(Collectors.toList());
// 关闭线程池
executor.shutdown();
怀着激动不已的心情开启了测试之路
感觉不太对 200个线程并没有真的发出200次请求 基本每秒请求数量在5-8次
怀疑是每次执行任务 网络请求等耗时3秒导致的
于是加线程到600 600/3 = 200 希望可以达到200次每秒的峰值
结果 对方开始疯狂404……
改低400个线程 还是404
最终测试 并发200个线程下耗时:58分钟
二次优化
- 计算出200条一页 总共有多少页数据
- 开启多个线程 并行从数据库获取数据 分页从库中读取11417条数据 读取6张关联表中的相关数据(每页200条 加上关联表总共7张表每次处理1400条数据)
- 封装7张表的数据 转换为友军需要的数据结构
- 因为接口限制 先开启200个线程 并行进行网络请求 将数据发送给友军
- 将操作记录写入日志表中 方便结算
此次方案优化为两次并行 并行从数据库中取数据 并行将取出来的数据发送给友军 最终耗时29分钟
测试三次的战绩:27分钟、28分钟、29分钟
多线程并行下 一定要注意安全问题 不用拖垮自家服务器、数据库、第三方接口…
long startTime = System.currentTimeMillis();
int pageSize = 200;
Integer count = mPolicyInfoDao.getCount();
int totalPage = (count / pageSize) + 1;
// 限制最大20个线程 防止友军接口404
int maxPoolSize = Math.min(totalPage, 10);
ArrayList<Integer> pages = new ArrayList<>();
for (int i = 1; i <= totalPage; i++) {
pages.add(i);
}
// 开启多个线程 并行执行同步任务
ExecutorService executor = Executors.newFixedThreadPool(maxPoolSize);
List<CompletableFuture<Integer>> futures =
pages.stream().map(bean -> CompletableFuture.supplyAsync(() -> {
// 切换到mysql数据源
DataSourceContext.setMysqlHqzcDataSource();
PageHelper.startPage(bean, pageSize);
List<HqzcPolicyInfo> infos = mPolicyInfoDao.selectAll();
// 同步数据到第三方平台
syncData2ThirdPlant(infos, lastUpdateTime, type);
return bean;
}, executor)).collect(Collectors.toList());
List<Integer> integers = futures.stream().map(s -> s.join()).collect(Collectors.toList());
// 关闭线程池
executor.shutdown();
long endTime = System.currentTimeMillis();
System.out.println("使用多线程进行同步耗时:" + ((endTime - startTime) / 60 / 1000) + "分钟");
流程图
初代:
二代:
最终流程
结语:
技术外 有种东西 叫做 —— power
一次产品经理的妥协 就是一段烂代码的开始~