大家好,我是田螺。
相信很多后端開發(fā)的伙伴們,都做過刷數(shù)任務(wù)了吧。今天跟大家聊聊,做好一個(gè)刷數(shù)任務(wù),需要具備哪些后端思維。
我們做刷數(shù)任務(wù)的時(shí)候,首先要考慮的是,這些被刷的數(shù)據(jù)是否還要還原的。或者刷出問題時(shí),需要回滾的。如果是的話,我們就要做好備份。
如果你是把數(shù)據(jù)遷移到新的表,則有可能不需要備份,這個(gè)具體問題具體分析的哈。
通常,我們?cè)谝粋€(gè)事務(wù)內(nèi),先備份數(shù)據(jù),再操作刷數(shù)邏輯。
圖片
當(dāng)然,備份數(shù)據(jù)的方式有多種方式,可以數(shù)據(jù)庫備份,比如搞一個(gè)備份表。或者文件系統(tǒng)快照等,在需要的時(shí)候,就還原數(shù)據(jù)。
我們刷數(shù)的時(shí)候,先確認(rèn)下具體的業(yè)務(wù)需求和數(shù)據(jù)模型。然后需要確定刷數(shù)的維度是什么。
圖片
當(dāng)然,還有其他維度,比如產(chǎn)品維度等等都可以,就看業(yè)務(wù)需求和你們的數(shù)據(jù)模型。
確認(rèn)了刷數(shù)維度后,需要思考你的刷數(shù)是否支持灰度。比如確認(rèn)客戶維度刷數(shù)后,你實(shí)現(xiàn)的代碼,是否支持灰度刷數(shù),也就是說,是否支持先刷一部分客戶,確認(rèn)沒問題后,再根據(jù)配置刷全量客戶。
比如,你要給一個(gè)客戶相關(guān)的業(yè)務(wù)表刷數(shù),需要考慮并發(fā)場(chǎng)景,簡(jiǎn)單點(diǎn)說,就是你在刷數(shù)的過程中,客戶是否可能在做交易請(qǐng)求,這時(shí)候,是否可能產(chǎn)生臟數(shù)據(jù)。
一般情況下,可以考慮給客戶維度加分布式鎖,比如加鎖的key是customerId。但是我們加鎖的時(shí)候,肯定是不希望影響客戶太久的,因?yàn)榧渔i后,整個(gè)刷數(shù)的過程中,系統(tǒng)是處理不了這個(gè)用戶的交易請(qǐng)求的,也就是說影響交易了。
圖片
所以也一般刷數(shù)任務(wù),我們會(huì)選擇在夜深人靜的時(shí)候執(zhí)行,這時(shí)候用戶發(fā)生交易的概率很低,影響相對(duì)較小。
我們?cè)谒?shù)的時(shí)候,有可能會(huì)刷失敗。比如因?yàn)榫W(wǎng)絡(luò)問題、或者目標(biāo)表結(jié)構(gòu)等等。失敗后,我們有哪些措施保證呢:
圖片
如果刷數(shù)失敗的話,我們要確保數(shù)據(jù)的完整性和一致性,一般一個(gè)刷數(shù)維度,需要加事務(wù),確保失敗可以回滾,保證數(shù)據(jù)一致性。刷失敗之后,首先確認(rèn)分布式鎖要被移除(如果有加鎖的話),因?yàn)橐_認(rèn)即使失敗后,交易也是能正常進(jìn)行的。
還要考慮失敗支持重試。可以自動(dòng)重試或者手動(dòng)重試,比如通過xxl-job定時(shí)任務(wù)撈取,繼續(xù)重試。有時(shí)候,可以設(shè)置重試次數(shù)和重試間隔,確保任務(wù)在一段時(shí)間內(nèi)嘗試恢復(fù)。
我們第4小節(jié)提到,如果失敗了,需要保證數(shù)據(jù)的完整性和一致性。其實(shí),一般我們刷數(shù),就是通過加事務(wù)處理去保證的。
事務(wù)需要加到恰到好處,比如我們不能所有的刷數(shù)業(yè)務(wù)都放到一個(gè)事務(wù)內(nèi)。如果我們是按照客戶維度來刷數(shù)的,我們就一個(gè)事務(wù)把一個(gè)客戶所有的數(shù)據(jù)刷的邏輯放到一塊,當(dāng)然,有些查詢是可以放到事務(wù)外處理的,一些更新或者修改、刪除操作則放到事務(wù)內(nèi)。
圖片
如果你的數(shù)據(jù)是分庫處理的,則有可能刷數(shù)的時(shí)候要考慮分布式事務(wù)了。
如果我們的刷數(shù)任務(wù)數(shù)據(jù)量很大,執(zhí)行耗時(shí)比較久的話。就建議可以多線程并行執(zhí)行。
比如你是分庫分表的,是有30個(gè)表,你可以A線程執(zhí)行1-10的表,B線程執(zhí)行11-20的表,C線程執(zhí)行21-30的表。
圖片
我們執(zhí)行一個(gè)刷數(shù)任務(wù),一定要做好日志記錄。
我記得我們技術(shù)領(lǐng)導(dǎo)說過一句話,很有道理:評(píng)價(jià)你的日志是否打印得是否夠好。就是你根據(jù)控制臺(tái)打印出的日志,能知道你的復(fù)雜業(yè)務(wù)執(zhí)行到什么流程。如果異常中斷,你能根據(jù)日志快速知道什么異常,哪個(gè)業(yè)務(wù)數(shù)據(jù)有問題,那就夠了。
刷數(shù)日志打印,一般包括:
我們開發(fā)刷數(shù)邏輯的時(shí)候,如果某種返回不符合預(yù)期的時(shí)候,就需要告警上報(bào)監(jiān)控(比如插入數(shù)據(jù)庫返回跟預(yù)期插入條數(shù)不一致)。
又或者是你刷數(shù)失敗,需要包這個(gè)異常日志打印出來,并且上報(bào)監(jiān)控(比如普羅米修斯,和企業(yè)微信通知)。比如這塊代碼:
try{ flushService.flushDataCustomerLevel(customerNo);}catch(Exception e){ Logger.error("flush customer data fail: {}", customerNo, e); prometheusMonitor.report("刷數(shù)失敗",customerNo); notify(); weChatWorkSend();}
如果你刷的數(shù)據(jù)量很大的時(shí)候,最好做壓測(cè)。壓測(cè)通常包括模擬多種負(fù)載情況,以確保系統(tǒng)在不同條件下都能正常運(yùn)行。
做刷數(shù)任務(wù)壓測(cè),主要考慮這幾方面:
刷數(shù)壓測(cè)的好處:
圖片
有些時(shí)候,我們?nèi)绻麚?dān)心刷數(shù)任務(wù)跑太久,可能會(huì)影響交易,這時(shí)候我們可以搞個(gè)配置變量,比如apollo配置變量,控制刷數(shù)多長(zhǎng)時(shí)間后,可以停止。
我們做刷數(shù)任務(wù)的時(shí)候,經(jīng)常是分頁循環(huán)掃描某個(gè)客戶/用戶表,然后一批一批出來執(zhí)行刷數(shù)邏輯。比如偽代碼像這樣:
long minId while(true){ List<CustomerDo> customerList = customer.pageQueryAscID(pageSize,minId); flushCustomerData(customerList); if(customerList.size()< pageSize){ break; } minId = customerList.get(customerList.size()-1).getId(); }
這塊代碼,其實(shí)沒啥問題。有些時(shí)候,我們可能手抖寫錯(cuò)了,可能導(dǎo)致死循環(huán)。
其實(shí)為了保護(hù)我們的系統(tǒng),我們可以先確認(rèn)下客戶有多少,然后設(shè)置個(gè)循環(huán)次數(shù),當(dāng)超過最大循環(huán)次數(shù)之后,就告警排查確認(rèn)。
long minId; Integer maxCycleNum =1000; Integer cycleNum = 0; while(true ){ List<CustomerDo> customerList = customer.pageQueryAscID(pageSize,minId); flushCustomerData(customerList); if(customerList.size()< pageSize){ break; } minId = customerList.get(customerList.size()-1).getId(); if(cycleNum>= maxCycleNum){ //告警 } cycleNum ++; }
當(dāng)然這只是個(gè)一種后端思路哈。
大家如果使用過xxl-job作為定時(shí)任務(wù),應(yīng)該抖配置過它的路由規(guī)則吧。比如是分片的,還是第一個(gè)/最后一個(gè)等等。
如果是分片的,就是多個(gè)pod都可能執(zhí)行到你的業(yè)務(wù)邏輯。這時(shí)候你要考慮并發(fā)執(zhí)行,你的業(yè)務(wù)是否收影響。
我們做刷數(shù)任務(wù)的時(shí)候,很多時(shí)候,都要跟SQL打交道。
我們要確保查詢、更新、或者刪除的數(shù)據(jù)量大的表,都要有索引了。要確保沒有慢SQL。
常規(guī)的我們可以用explain分析SQL,我們還可以通過壓測(cè)分析出來。
如果你是按照客戶維度刷數(shù),加了客戶維度的分布式鎖,你要考慮鎖時(shí)間是多久,鎖時(shí)間是否可以配置(一般這種最好配置一下。)
如果你時(shí)間設(shè)置小,那這時(shí)候刷數(shù)還沒完成,鎖就超時(shí)釋放了,那不就有問題啦。
如果你時(shí)間設(shè)置過長(zhǎng)也不太好,當(dāng)然,你在刷完數(shù),finally執(zhí)行釋放鎖也可以。
finally { redisService.deleteKey(customerNoKey); }
我們?cè)谒?shù)的時(shí)候,為了保證數(shù)據(jù)的完整性和一致性,一般要求加事務(wù)的。
但是,切忌事務(wù)不要太大,我們可以把一些查詢放到事務(wù)外,把計(jì)算邏輯也放事務(wù)外,把數(shù)據(jù)庫的更新、新增、刪除操作放到事務(wù)內(nèi)就好。
就是把大事務(wù)拆分為小事務(wù)。
一般來說,做刷數(shù),盡量打印一下耗時(shí),這樣我們可以根據(jù)日志,觀察是否有哪些問題需要及時(shí)處理的。
比如打印刷一個(gè)客戶要多久。或者打印一批客戶要多久,等等。
有些時(shí)候,我們需要配置一定的灰度規(guī)則來支持灰度刷數(shù)。如果刷數(shù)流程是先掃描所有客戶,然后接著判斷客戶是否命中灰度。這樣每次任務(wù)執(zhí)行,都會(huì)掃描客戶表。
我么可以考慮加個(gè)配置,傳特定的客戶號(hào),根據(jù)客戶號(hào)列表,去查客戶列表,然后開始刷數(shù)邏輯,不用再全表掃描客戶表了。
有些時(shí)候,我們沒法確認(rèn)我們刷的邏輯是否正確,這時(shí)候,可以考慮是否加校驗(yàn)邏輯。
你可以異步進(jìn)行校驗(yàn),也可以同步校驗(yàn)(當(dāng)然,如果耗時(shí)不大的時(shí)候)
如果我們是按照一個(gè)客戶維度去刷數(shù)的,你要確認(rèn)A客戶刷失敗,是不是不能影響B(tài)客戶。這時(shí)候建議try...catch 包住可能的異常,這樣即有利于分析錯(cuò)誤原因,又可以不不因?yàn)槲粗惓?dǎo)致刷數(shù)中斷。
本文鏈接:http://www.www897cc.com/showinfo-26-76543-0.html實(shí)現(xiàn)一個(gè)刷數(shù)任務(wù),需要思考哪些維度?
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: 用 Python 優(yōu)雅地玩轉(zhuǎn) Elasticsearch:實(shí)用技巧與最佳實(shí)踐