1.背景
本人所在的部門是主要負(fù)責(zé)的職責(zé)就是根據(jù)活動的玩法來發(fā)相關(guān)的禮品杀狡,我們常見的禮品有:xxx券抒抬,券碼,以及一些其他合作方的券
對于這些禮品的發(fā)放記錄我這邊數(shù)據(jù)量是比較大的挤安,一天預(yù)估的是500w的量挡篓,那么一年下來就會達(dá)到18億的數(shù)據(jù)量,這些記錄保留一年恬砂,所以說峰值最高會有18億的數(shù)據(jù)量咧纠,這些數(shù)據(jù)存放可以用Mysql分庫分表。
但是現(xiàn)在有個問題是僅僅存放是完全可以的泻骤,但是如果要查詢呢漆羔?
對于簡單的查詢根據(jù)分片id能查,但是對于一些復(fù)雜的多維度查詢瞪讼,mysql分庫分表是不方便的钧椰,實(shí)現(xiàn)了性能也會很差。所以我們這邊就打算把數(shù)據(jù)存放在ES中符欠。下面是目前對于禮品記錄寫和查的方案
寫
采用雙寫方案先寫DB嫡霞,寫成功后再異步發(fā)kafka,寫ES
查詢
要求實(shí)時性高希柿、查詢簡單的就走DB诊沪,復(fù)雜多維度的就走ES
2.現(xiàn)狀問題
在方案的設(shè)計之初,對這個方案的考慮不夠全面曾撤,導(dǎo)致出現(xiàn)下面的問題:
單ES索引數(shù)據(jù)量巨大:單個索引占用110G端姚,不符合es索引推薦20-50G的范圍
mapping設(shè)計不合理:存在不需要分詞但是仍然分詞的、以及多余的字段
沒有清理數(shù)據(jù)任務(wù):歷史廢數(shù)據(jù)一致存在挤悉,隨著時間推移會越來越大渐裸,影響性能
針對上面的問題,這次打算用合理的方案把它優(yōu)化掉
3.新架構(gòu)
1.架構(gòu)描述
舊的整體架構(gòu)是在左邊装悲,這個架構(gòu)也就會存在上述的數(shù)據(jù)量大等因?yàn)?strong>數(shù)據(jù)都存在一個索引的問題昏鹃。
那么經(jīng)過架構(gòu)的設(shè)計升級,最新的禮品發(fā)放記錄讀寫架構(gòu)變成了右圖诀诊,主要改動點(diǎn):
-
mapping設(shè)計:重新設(shè)計mapping
減少不必要的字段
針對不需要分詞的字段改成keyword類型
重新設(shè)置shard數(shù)量
讀:es用別名的方式洞渤,多個分片索引對應(yīng)同一個別名(user_award_index_all),這樣查的時候都統(tǒng)一查user_award_index_all的索引即可
寫:根據(jù)索引月份分片后属瓣,每次寫入當(dāng)前月對應(yīng)的的索引分片载迄,例如現(xiàn)在是2023年12月,那么就會寫入到
user_award_index_202312
索引通過定時任務(wù)抡蛙,定期清理過期的分片索引
2.創(chuàng)建索引和刪除過期索引定時任務(wù)
這個定時任務(wù)每周跑一次护昧,會創(chuàng)建這個月和下個月的索引,以及刪除上一年本月-1過期索引溜畅。當(dāng)然如果當(dāng)前需要創(chuàng)建的索引已經(jīng)存在就不會再創(chuàng)建捏卓,這樣每周跑一次,保證了創(chuàng)建當(dāng)前月和下個月的索引,以及刪除舊索引能重試3次怠晴。
例如:當(dāng)前是23年12月遥金,那么就會創(chuàng)建user_awrad_index_2312,和user_awrad_index_2401的索引蒜田,
同時會刪除上一年本月-1過期索引(user_awrad_index_2211)稿械,也就是保留一年的數(shù)據(jù)。
3.ES Alias(別名)介紹
作為本篇文章中最重要的知識點(diǎn)之一冲粤,對于es的別名機(jī)制這里簡單說明一下:
別名可以看作是索引的一個指向美莫,它提供了一種靈活而可靠的方式來管理和操作索引。下面是ES別名機(jī)制的幾個優(yōu)勢:
簡化索引操作:通過使用別名梯捕,您可以將復(fù)雜的索引操作(如創(chuàng)建厢呵、刪除、重命名等)進(jìn)行簡化傀顾。例如襟铭,當(dāng)需要更換底層索引時,只需更新別名指向新索引即可短曾,而不需要更改應(yīng)用程序代碼或配置文件寒砖。
實(shí)現(xiàn)無縫切換:由于別名提供了指向具體索引的邏輯名稱,因此可以輕松地在不影響應(yīng)用程序和用戶訪問的情況下切換底層索引嫉拐。這對于數(shù)據(jù)遷移哩都、版本升級或故障恢復(fù)等場景非常有用。
支持滾動升級:當(dāng)您需要對大型索引進(jìn)行結(jié)構(gòu)變更或調(diào)整時婉徘,別名機(jī)制使得滾動升級成為可能漠嵌。您可以先創(chuàng)建一個新版本的索引,并將別名指向該新版本盖呼。然后逐步將舊版本中的數(shù)據(jù)導(dǎo)入到新版本中献雅,在此過程中保持服務(wù)連續(xù)可用。
多租戶支持:通過使用別名塌计,您可以為每個租戶或用戶創(chuàng)建獨(dú)立的視圖,并將其映射到特定的索引侯谁。這樣可以實(shí)現(xiàn)安全性和隔離性锌仅,并方便地控制每個租戶或用戶所能訪問和操作的數(shù)據(jù)范圍。
3.優(yōu)勢
每個分片索引的大小能控制在20-50G內(nèi)墙贱,符合ES的規(guī)范標(biāo)準(zhǔn)热芹,提高讀寫性能
刪除數(shù)據(jù)的時候,只需要將別名移除惨撇,再把過期索引整個刪除即可伊脓,不需要在同一個大索引操作,減少引起ES性能問題的可能
由于之前的禮品發(fā)放記錄采用的是舊的方案魁衙,那么需要轉(zhuǎn)變成新的方案报腔,就涉及到數(shù)據(jù)的遷移株搔,那么怎么遷移呢?
4.遷移方案
1.方案評估
在做數(shù)據(jù)遷移方案的時候纯蛾,有以下這些考慮的點(diǎn):
1.是否停機(jī)遷移纤房?
2.如何把舊遷移遷移到新數(shù)據(jù)?
3.數(shù)據(jù)的新增翻诉、更新如何應(yīng)對炮姨?
4.出問題了如何回滾?
5.如何校對數(shù)據(jù)遷移的準(zhǔn)確性碰煌?
上面這些考慮點(diǎn)也是做數(shù)據(jù)遷移繞不過的考慮的點(diǎn)舒岸,那么針對本人的業(yè)務(wù)場景一一分析:
1.是否停機(jī)?
我們的業(yè)務(wù)是不允許停機(jī)來做數(shù)據(jù)遷移的芦圾,目前發(fā)獎峰值QPS在500蛾派,并且發(fā)獎的這個業(yè)務(wù)如果停機(jī)對于C端的業(yè)務(wù)是不能接受的
2.如何遷移舊數(shù)據(jù)到新數(shù)據(jù)?
遷移數(shù)據(jù)就是需要從老的user_award_index
遷移到分片的user_award_index_2023xx
堕扶,那么遷移方式有2種:
1.采用定時任務(wù)碍脏,深度分頁查詢es數(shù)據(jù),通過scroll分頁的查詢稍算,將數(shù)據(jù)同步過去
2.使用es的自帶遷移命令:reindex典尾,這個原理是通過異步任務(wù),也是和方式1的scroll深度分頁查詢一樣糊探,深 度分頁查詢出來后钾埂,同步到目標(biāo)索引
這里使用es的reindex
會更加方便,支持的功能也比較豐富科平,同時它是異步分頁處理任務(wù)褥紫,不影響先上的讀寫。而且es有getTask
命令瞪慧,用于查詢reindex相關(guān)的任務(wù)髓考,這種可視化命令的方式也提供了不少便利。
3.數(shù)據(jù)的新增弃酌、更新如何應(yīng)對?
我們的場景都是寫操作氨菇,有記錄了就往ES里面寫,沒有更新的數(shù)據(jù)妓湘,所以這里遷移數(shù)據(jù)處理起來會更簡單查蓉,直接覆蓋即可。
4.出問題了如何回滾榜贴?
我們的方案采用雙寫豌研,然后切讀,再關(guān)舊索引的寫,最后刪除老的索引鹃共,去掉用于切換雙寫等開關(guān)代碼
所以在切讀以后如果出問題了鬼佣,那么可以立馬把讀切回讀老的索引。
5.如何校對數(shù)據(jù)遷移的準(zhǔn)確性及汉?
條數(shù)對比:使用es的命令查詢新舊索引的條數(shù)沮趣,然后對比,這里需要注意不能直接比對實(shí)時全量的數(shù)據(jù)坷随,因?yàn)樾吕纤饕龑慐S的速度不一樣房铭,執(zhí)行統(tǒng)計索引數(shù)量的時間也不一致,完全實(shí)時的數(shù)量是不一定一致的温眉。所以我們這邊是對比某個時間點(diǎn)之前的全量數(shù)據(jù)條數(shù)缸匪。
數(shù)據(jù)性對比:禮品發(fā)放記錄的索引數(shù)據(jù)是相對簡潔,沒有太復(fù)雜的邏輯类溢,只需要抽樣一些數(shù)據(jù)對比是否有差異
2.遷移流程
1.遷移流程圖
2.上線雙寫
這一步上線寫新老索引的代碼凌蔬,同時加上寫新老索引的開關(guān),方便后續(xù)出問題后切換
// 寫老的es
String oldUserAwardEsWriteSwitch = sysConfigHelper.getByKeyWithDefaultVal(CommDefConstants.OLD_USER_AWARD_ES_WRITE_SWITCH, "1");
if (Objects.equals("1", oldUserAwardEsWriteSwitch)) {
esUtil.batchAddListData(CommDefConstants.EsIndex.USER_AWARD_RECORD_INDEX_OLD, esBatchList);
}
// 寫新的es
try {
String newUserAwardEsWriteSwitch = sysConfigHelper.getByKeyWithDefaultVal(CommDefConstants.NEW_USER_AWARD_ES_WRITE_SWITCH, "0");
if (Objects.equals("1", newUserAwardEsWriteSwitch)) {
esUtil.batchAddListData(getSliceUserAwardIndexName(YearMonth.now()), esBatchList);
}
} catch (Exception e) {
log.error(SfJsonUtil.toJsonStr(esBatchList), e);
}
OLD_USER_AWARD_ES_WRITE_SWITCH
為寫老es索引的開關(guān)
NEW_USER_AWARD_ES_WRITE_SWITCH
為寫新es索引的開關(guān)闯冷,這里try-catch
住了砂心,防止寫新索引報錯影響正常的業(yè)務(wù)
3.遷移數(shù)據(jù):reindex
下面是遷移的命令:
curl -u xxx:xxx -X POST 'xxxxxx:9200/_reindex' \
-H 'Content-Type: application/json' \
--d '{
"source":{
"index":"user_award_record_index",
"size":5000,
"_source":[
"amount",
"channel",
"createTm",
"detail",
"userId",
"errorMsg",
"errorCode"
],
"query": {
"range": {
"createTm": {
"gte": "2022-12-07T18:00:00.000",
"lte": "2023-12-08T00:00:00.000"
}
}
}
},
"dest":{
"index":"slice_of_user_award_index_202312"
},
}'
souce: 表示源索引,也就是數(shù)據(jù)的來源
dest: 表示目標(biāo)索引蛇耀,表示需要遷移到的目標(biāo)索引
size:表示每次批量處理的數(shù)據(jù)量大小
query: 查詢需要遷移的數(shù)據(jù)
es的reindex還有其他很多參數(shù)辩诞,例如下面:
當(dāng)涉及到 Elasticsearch 的 reindex API 參數(shù)時,下面是詳細(xì)介紹:
"script"
:允許在重新索引期間對文檔進(jìn)行轉(zhuǎn)換或修改的腳本纺涤∫朐荩可以使用不同的編程語言如 Painless、Groovy 等編寫自定義腳本來處理文檔內(nèi)容撩炊。"size"
:用于分批處理數(shù)據(jù)時指定每個批處理操作中要復(fù)制的文檔數(shù)量外永。默認(rèn)為 1000。"refresh"
:控制在每次重新索引操作后是否自動刷新目標(biāo)索引以使更改立即生效拧咳〔ィ可以設(shè)置為true
表示自動刷新,或者false
表示禁用刷新骆膝。"wait_for_completion"
:決定是否等待重新索引過程完成后再返回響應(yīng)砾淌。默認(rèn)為true
,表示等待完成谭网;而設(shè)置為false
會立即返回響應(yīng)并在后臺執(zhí)行重建操作。"requests_per_second"
:限制重新索引操作每秒允許執(zhí)行的請求數(shù)量赃春。這有助于平衡性能和資源消耗愉择。例如,"requests_per_second": 1000
將限制每秒不超過 1000 個請求。"conflicts"
:定義如何解決可能出現(xiàn)的沖突情況(當(dāng)源和目標(biāo)之間存在相同 ID 的文檔)锥涕≈愿辏可以選擇忽略沖突、替換目標(biāo)文檔或更新目標(biāo)文檔层坠。
我目前的場景對于沖突用的是默認(rèn)更新殖妇,其他的參數(shù)就暫時用不少了,對于復(fù)雜的遷移可能會用上破花。
這里也提供一下查詢reindex的任務(wù)命令:
這樣就能看出當(dāng)前的reindex任務(wù)執(zhí)行進(jìn)度谦趣,會包含需要遷移的數(shù)據(jù)量、當(dāng)前遷移的數(shù)量座每、創(chuàng)建時間等前鹅。
4.驗(yàn)證數(shù)據(jù)
驗(yàn)證數(shù)據(jù)就是查詢新老索引的數(shù)量進(jìn)行對比,然后抽查一下數(shù)據(jù)的情況峭梳。
-H 'Content-Type: application/json' \
-d '{
"query": {
"range": {
"createTm": {
"gte": "2022-12-09T18:00:00.000",
"lte": "2023-12-11T10:40:00.000"
}
}
}
}'
5.切讀
String readFlag = sysConfigHelper.getByKeyWithDefaultVal(CommDefConstants.USER_AWARD_ES_READ_FLAG, "old");
BoolQueryBuilder boolBuilder;
boolean old = "old".equals(readFlag);
if (old) {
boolBuilder = genBoolQueryBuilder(req);
} else {
boolBuilder = boolQueryBuilder(req);
}
切讀就是通過USER_AWARD_ES_READ_FLAG
開關(guān)來控制讀新索引還是老索引舰绘,這里比較簡單
6.停止舊索引寫入
觀察幾天,當(dāng)線上流量讀的流量驗(yàn)證讀無問題后葱椭,那么就說明新的索引運(yùn)行穩(wěn)定了捂寿,就可以把寫舊索引關(guān)了。這里直接把OLD_USER_AWARD_ES_WRITE_SWITCH
開關(guān)改成0即可孵运,同時把老的索引刪除秦陋。
5.成果
平滑遷移0故障、0BUG: 此次遷移未造成任何生產(chǎn)影響或者問題
存儲占用減少:由于重新設(shè)計過了mapping掐松,不需要分詞的數(shù)據(jù)去掉了分詞踱侣,也減少了不必要字段,整體的索引容量減少了一倍大磺。2億2千條數(shù)據(jù)從之前的110G到了56G
性能提升:查詢性能相較之前提升了30%抡句,平均耗時從125ms下降到93ms,這里是由于es集群本身沒優(yōu)化好杠愧,導(dǎo)致新老索引的整體耗時偏高