一、問(wèn)題
在一次管理后臺(tái)數(shù)據(jù)導(dǎo)入接口中,發(fā)現(xiàn)在大數(shù)量導(dǎo)入的情況下,數(shù)據(jù)會(huì)出現(xiàn)重復(fù)寫入的問(wèn)題。后經(jīng)調(diào)試發(fā)現(xiàn)導(dǎo)入接口實(shí)際上被調(diào)用了兩次。初步猜測(cè)可能是Feign或Ribbon的重試機(jī)制導(dǎo)致的。也就是管理后臺(tái)服務(wù)調(diào)用業(yè)務(wù)服務(wù),由于業(yè)務(wù)服務(wù)數(shù)據(jù)導(dǎo)入執(zhí)行耗時(shí)較長(zhǎng)導(dǎo)致超時(shí),從而后臺(tái)服務(wù)進(jìn)行了重試導(dǎo)致。
后臺(tái)服務(wù)Ribbon配置如下:
#Ribbon配置
#Ribbon更新服務(wù)注冊(cè)列表的頻率
ribbon.ServerListRefreshInterval=2000
#請(qǐng)求連接的超時(shí)時(shí)間
ribbon.ConnectTimeout=3000
#請(qǐng)求處理的超時(shí)時(shí)間
ribbon.ReadTimeout=10000
二、分析
跟蹤源碼,在FeignLoadBalancer中配置了重試相關(guān)的策略,如果ribbon.OkToRetryOnAllOperations配置為true,則任何請(qǐng)求方法都進(jìn)行重試,ribbon.OkToRetryOnAllOperations配置為false時(shí),GET請(qǐng)求方式也會(huì)進(jìn)行重試,非GET方法只有在連接異常時(shí)才會(huì)進(jìn)行重試。
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler (
RibbonRequest request, IClientConfig requestConfig){
// 如果OkToRetryOnAllOperations配置為true,則任何請(qǐng)求方法/任何異常的情況都進(jìn)行重試
if (this.ribbon.isOkToRetryOnAllOperations()) {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
// OkToRetryOnAllOperations配置為false時(shí)(默認(rèn)為false)
// 非GET請(qǐng)求,只有連接異常時(shí)才進(jìn)行重試
if (!request.toRequest().method().equals("GET")) {
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
requestConfig);
// GET請(qǐng)求任何情況/任何異常都重試
} else {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
}
通過(guò)上面的分析,我們可以知道并不是配置了ribbon.OkToRetryOnAllOperations=false就不會(huì)進(jìn)行重試,對(duì)于GET請(qǐng)求Ribbon還是會(huì)進(jìn)行重試的,而在我們的系統(tǒng)中并沒(méi)有對(duì)Ribbon的重試機(jī)制做特殊的配置,也就是用的默認(rèn)值。Ribbon重試機(jī)制默認(rèn)配置如下:
#同一實(shí)例最大重試次數(shù),不包括首次調(diào)用。默認(rèn)值為0
ribbon.MaxAutoRetries = 0
#同一個(gè)服務(wù)其他實(shí)例的最大重試次數(shù),不包括第一次調(diào)用的實(shí)例。默認(rèn)值為1
ribbon.MaxAutoRetriesNextServer = 1
#是否所有操作都允許重試。默認(rèn)值為false
ribbon.OkToRetryOnAllOperations = false
由于MaxAutoRetriesNextServer配置默認(rèn)值為1,而我們的導(dǎo)入接口恰巧又是GET請(qǐng)求,在業(yè)務(wù)服務(wù)接口數(shù)據(jù)處理超時(shí)的情況下,所以Ribbon會(huì)自動(dòng)重試一次。
三、解決方案
首先GET請(qǐng)求是用于數(shù)據(jù)查詢類接口的請(qǐng)求方式,像涉及到數(shù)據(jù)插入/更新/刪除等操作接口不應(yīng)該用GET請(qǐng)求方式,在我們的數(shù)據(jù)導(dǎo)入接口中使用的是GET請(qǐng)求方式,所以此處是存在問(wèn)題的。
像在一般的系統(tǒng)中,建議關(guān)閉Ribbon的重試機(jī)制,如果非得開(kāi)啟重試,那么系統(tǒng)的各個(gè)接口一定要保證接口的冪等性,否則可能會(huì)導(dǎo)致接口邏輯被執(zhí)行多次的情況,在一些重要數(shù)據(jù)的場(chǎng)景帶來(lái)的影響將是災(zāi)難性的。
要關(guān)閉Ribbon的重試將上面的MaxAutoRetriesNextServer配置為0即可,后調(diào)整Ribbon的完整配置如下:
#Ribbon配置
#Ribbon更新服務(wù)注冊(cè)列表的頻率
ribbon.ServerListRefreshInterval=2000
#請(qǐng)求連接的超時(shí)時(shí)間
ribbon.ConnectTimeout=3000
#請(qǐng)求處理的超時(shí)時(shí)間
ribbon.ReadTimeout=10000
#同一實(shí)例最大重試次數(shù),不包括首次調(diào)用。默認(rèn)值為0
ribbon.MaxAutoRetries = 0
#同一個(gè)服務(wù)其他實(shí)例的最大重試次數(shù),不包括第一次調(diào)用的實(shí)例。默認(rèn)值為1
ribbon.MaxAutoRetriesNextServer = 0
#是否所有操作都允許重試。默認(rèn)值為false
ribbon.OkToRetryOnAllOperations = false
如果文章對(duì)你有幫助的話,給文章點(diǎn)個(gè)贊吧。
如果有寫得不正確的地方,歡迎指出。
文章首發(fā)公眾號(hào):會(huì)跳舞的機(jī)器人,歡迎掃碼關(guān)注。
