背景: 有 1 亿多的用户画像中数仓需要导入 ES. 大多数字段都是 sql 统计数据, 无法区分哪些发生了变化, 所以不能增量更新. 只能每天全量刷数据. 在刷数据的过程中出现了更新缓慢, 内存问题. 于是做了一些写入优化.
解决方案:
1. 读数据
首先要从数仓读取出数据到内存. 然后再组装对象去 ES 刷数据字段比较多而且都需要查询. 尝试了一下, 即使 limit 10, 也需要耗时 2 分钟. 所以第一步导数据不能直接查询. 采用的是数仓到分布式文件系统分片存储. 这一步已经有现成工具. 1 亿数据导入到分片耗时 3 分钟左右
2. 组装数据
将分片的数据读到 java 内存中. 再构造请求参数刷 ES
` 问题: 1. 刷数据 ES 报 413 错误. ES 建议每次 bulk5~15M 数据, 这里我每次批量提交 5000 条, bulk 的时候发生的 413 requets too large 错误, google 了一下, 说是索引的时候段合并内存不够. 于是调整 indices.breaker.fielddata.limit 为 60%, 增大堆内存, 结果没什么用; 也有说要调整 client_max_body_size 的, 但是我们的 es 是云服务, 没法改配置参数最终加大 es 的内存为 16G, 不再报这个错误.
2. 之前写业务代码数据量一般不是很大, 采用的是一次性把数据读取到内存中. 再做业务处理. 但是这次在数据塞到一半的数据, 先是系统响应变慢了, 后来测试环境的系统挂了. 通过过命令排查, 发现 List 对象占用了很多空间. 于是复查代码. 发现是 for 循环一直往 list 填对象导致的内存泄露. 于是限制了单个文件大小为 20M, 一个文件一个文件地处理. `
3. 提高 es 索引效率
刚开始刷数据预计需要 20 个小时. 今天的数据如果明天才更新完, 意义不大. 于是想办法提高索引效率. 网上都说 "refresh_interval": "-1"; 调整 number_of_replicas=0. 我调整了结果没什么变化. 于是采用多线程刷数据
问题: 1. 一开始使用 size 为 20 的无界队列, 导致耗尽资源, 任务线程占用的内存占用了 80+% 的内存, 其他任务可能被拖垮. 后来线程的核心线程数和最大线程数统一设置为 10. 并采用 future 模式, 一个任务完成后再去添加其他任务. 解决了线程耗尽资源和内存的问题.
用 htop 查看刷数据机器的性能
可以看到开启的 10 个线程占用 42% 内存. 主线程 CPU 偶尔接近 100%, 这不是 io 密集型吗? 怎么会耗 CPU.CPU 变高可能是复杂的技术或者死循环. 这里循环每次读取量有 50000 条, 并且组装对象的逻辑. 而且有 10 个线程, 猜想可能是这个原因.
ES 的索引速率
成果
最后原来需要 20 小时才能完成的刷数据任务, 只耗时约 100 分钟. 当然中间遇到的坑不止这些
来源: https://juejin.im/post/5c73d8ea518825622d74c138