關於 es 資料同步的一次效能優化實踐
緣由
開發同學有個資料同步的需求,正好我這邊有閒,就接過來做了,
主要是將mysql的幾張表,同步至elasticsearch,全量同步
資料量
測試環境:4600條
生產環境:3000萬條
大套路
首先,es叢集環境搭建
然後,編寫java程式碼,完成同步功能
程式碼設計套路
因為當前幾張表都是基於accountId的關聯的,而accountId是自增的
主要分以下幾步:
- 查出庫表中最小的id主鍵
- 查出庫表中最大的id主鍵
- 按每頁1000條,算出總頁數
- 按批查詢mysql,一批10頁
- 將查到的資料轉化為es物件,使用執行緒池將資料插入到es
叢集環境搭建
這裡借鑑了搜尋和其他業務線的開發同學搭建的方式,完成搭建,可另寫帖子完成
第一版 60秒
@Override
public void memberAccountInfoSync() {
int pageSize = 1000;
long channelMinId = queryChannelMinId();
long channelMaxId = queryChannelMaxId();
int totalCount = queryTotalCount(channelMinId, channelMaxId);
PageTaskUtil.handlePageTaskForBizInvoke(new PageTaskUtil.PageTaskForBizInvoke<ESMemberAccountInfo>() {
@Override
public List queryPageData(int pageNo, int pageSize) {
long pageStartId = getPageStartId(channelMinId, pageSize, pageNo);
long pageEndId = getPageEndId(pageStartId, pageSize);
List<CpsBaseAccountInfo> cpsBaseAccountInfoList = cpsBaseAccountService.listAccountIdsWithInitialValue((byte) CURR_CHANNEL.getCode(), pageStartId, pageEndId);
List<Long> accountIdList = cpsBaseAccountInfoList.stream().map(CpsBaseAccountInfo::getAccountId).collect(Collectors.toList());
// 效能優化,如果accountIdList為空,則進入下一次迴圈
if (CollectionUtils.isEmpty(accountIdList)) {
return new ArrayList(0);
}
// 撈出其他表資料
List<ESMemberAccountInfo> esMemberAccountInfoList = new ArrayList<>();
cpsBaseAccountInfoList.forEach(one -> {
long accountId = one.getAccountId();
ESMemberAccountInfo e = new ESMemberAccountInfo();
// 合併資料到es物件
esMemberAccountInfoList.add(e);
});
return esMemberAccountInfoList;
}
@Override
public void pageTask(List<ESMemberAccountInfo> pageList, Object res) {
// 如果集合為空,直接返回
if (CollectionUtils.isEmpty(pageList)) {
return;
}
CountDownLatch countDownLatch = new CountDownLatch(pageList.size());
for (ESMemberAccountInfo esMemberAccountInfo : pageList) {
final Long accountId = esMemberAccountInfo.getAccountId();
esSyncThreadPoolTaskExecutor.execute(new Runnable() {
@Override
public void run() {
try {
// 查詢es中是否存在該使用者
if (!isExistAccountInES(esMemberAccountInfo)) {
insertAccountInfoToES(esMemberAccountInfo);
}
} catch (Exception e) {
log.error("es accountInfo sync error, accountId: {}", accountId);
} finally {
countDownLatch.countDown();
}
}
});
}
try {
countDownLatch.await();
} catch (Exception e) {
log.error("es sync error" + e.getMessage());
}
}
}, totalCount, pageSize, 10, "esSync", null);
}
第一版,因為不知道es有批量插入的功能,是一條一條的往es中插入資料的,然後
在插入操作之前還查了一下es中是否存在該資料,
不存在則,整個功能完成後,
測試環境用時近60秒
第二版 30秒
private void insertAccountInfoToESBatch(List<ESMemberAccountInfo> esMemberAccountInfoList) throws Exception {
BulkRequest bulkRequest = new BulkRequest();
for (ESMemberAccountInfo esMemberAccountInfo: esMemberAccountInfoList) {
UpdateRequest updateRequest = new UpdateRequest(ESIndexEnum.ES_INDEX_BASE_ACCOUNT_INFO.getIndex(), ESIndexEnum.ES_INDEX_BASE_ACCOUNT_INFO.getType(),
esMemberAccountInfo.getAccountId().toString() + esMemberAccountInfo.getBizChannel().toString());
updateRequest.doc(JSON.parseObject(JSON.toJSONString(esMemberAccountInfo)));
updateRequest.docAsUpsert(true);
bulkRequest.add(updateRequest);
}
client.getRhlClient().bulk(bulkRequest, RequestOptions.DEFAULT);
}
第二版,改為批量插入es,並且移除查詢請求,測試環境用時近30秒
第三版 6秒
@Override
public void memberAccountInfoSync() {
int pageSize = 1000;
long channelMinId = queryChannelMinId();
long channelMaxId = queryChannelMaxId();
log.info("es全量同步任務,channelMinId is: {}, channelMaxId is: {}", channelMinId, channelMaxId);
int totalCount = queryTotalCount(channelMinId, channelMaxId);
int pageCount = totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1;
log.info("es全量同步任務,pageCount is: {}", pageCount);
// druid資料庫連線池的大小
final int dbConnection = 10;
final int repeatTimes = pageCount % dbConnection == 0 ? pageCount / dbConnection : pageCount / dbConnection + 1;
log.info("es全量同步任務,repeatTimes is: {}", repeatTimes);
for (int i=1; i<repeatTimes+1; i++) {
CountDownLatch countDownLatch = new CountDownLatch(dbConnection);
for (int j=1; j<dbConnection+1; j++) {
log.info("es全量同步任務,channelMinId is: {}", channelMinId);
Runnable r = syncTask(channelMinId, pageSize, j + (i-1)*dbConnection, countDownLatch);
esSyncThreadPoolTaskExecutor.execute(r);
}
try {
countDownLatch.await();
} catch (Exception e) {
log.error("es全量同步任務 countDownLatch.await() error" + e.getMessage());
}
}
}
在第二版的基礎上,對查詢資料的動作也進行非同步批量處理,所以整個不使用開發提供的幫助類了,自己寫,但保留了批量查詢處理的思想,測試環境用時近6秒
第四版 3秒
@Override
public void memberAccountInfoSync() {
int pageSize = 2000;
long channelMinId = queryChannelMinId();
long channelMaxId = queryChannelMaxId();
int totalCount = queryTotalCount(channelMinId, channelMaxId);
int pageCount = totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1;
log.info("es全量同步任務,pageCount: {}", pageCount);
CountDownLatch c = new CountDownLatch(pageCount);
// druid資料庫連線池的大小,不要超過最大值40
esSyncThreadPoolTaskExecutor.setCorePoolSize(30);
esSyncThreadPoolTaskExecutor.setMaxPoolSize(30);
for (int i=0; i<pageCount; i++) {
try {
esSyncThreadPoolTaskExecutor.execute(syncTask(CURR_CHANNEL, channelMinId, channelMaxId, pageSize, i, c));
} catch (Exception e) {
e.printStackTrace();
}
}
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
因為第三版仍然使用了批量處理的思想,在同一批任務中,是以最後一個任務跑完了時間為準的,所以會導致一些效能浪費,
在這裡直接通過執行緒池本身提供的佇列功能,通過引數控制執行緒池大小,避免將資料庫連線池耗盡
總結
主要使用批量操作,執行緒池非同步操作完成了單機優化
問題
- 假如要再一步提升,使用分散式任務,如何處理
相關文章
- 關於效能優化的一些實踐優化
- 基於 PageSpeed 的效能優化實踐優化
- 一次效能提升300%的優化實踐優化
- 記一次關於Laravel model查詢返回大量資料的效能優化Laravel優化
- 記十億級Es資料遷移mongodb成本節省及效能優化實踐MongoDB優化
- 【轉】關於Oracle資料庫的效能優化心得Oracle資料庫優化
- 優化邏輯Standby的資料同步效能優化
- 記一次介面效能優化實踐總結:優化介面效能的八個建議優化
- gprof的效能優化實踐優化
- Canvas 動畫的效能優化實踐Canvas動畫優化
- 資料庫優化的最佳實踐資料庫優化
- 效能優化,實踐淺談優化
- SAP ABAP 效能優化實踐優化
- 記MySQL一次關於In的優化MySql優化
- hadoop JOB的效能優化實踐Hadoop優化
- 資料庫效能優化-索引與sql相關優化資料庫優化索引SQL
- 關於sap效能優化的問題優化
- ES寫入效能優化優化
- MySQL資料庫優化的最佳實踐MySql資料庫優化
- TiDB 效能分析&效能調優&優化實踐大全TiDB優化
- 基於 MySQL Binlog 的 Elasticsearch 資料同步實踐MySqlElasticsearch
- ⚠️Flutter 效能優化實踐 總結⚠️Flutter優化
- FlutterWeb效能優化探索與實踐FlutterWeb優化
- 前端效能優化原理與實踐前端優化
- 關於主資料的實踐和思考
- 前端感官效能的衡量和優化實踐前端優化
- 【效能優化】ORACLE資料庫效能優化概述優化Oracle資料庫
- Tree-Shaking效能優化實踐 - 實踐篇優化
- 記某百億級mongodb叢集資料過期效能優化實踐MongoDB優化
- 關於資料字典的查詢效率優化優化
- 一次效能壓測及分析調優實踐
- 讀小程式效能優優化實踐-筆記優化筆記
- Vue 專案效能優化 — 實踐指南Vue優化
- HBase最佳實踐-讀效能優化策略優化
- Sqlserver關於tempdb臨時資料庫最優檔案個數的最優實踐SQLServer資料庫
- 效能優化之關於畫素管道及優化(二)優化
- 小程式效能優化的幾點實踐技巧優化
- 聊聊關於效能優化和其他(一)優化