springCloud中最重要的就是微服務(wù)之間的調(diào)用放刨,因?yàn)榫W(wǎng)絡(luò)延遲或者調(diào)用超時會直接導(dǎo)致程序異常舀瓢,因此超時的配置及處理就至關(guān)重要躲叼。
在開發(fā)過程中被調(diào)用的微服務(wù)打斷點(diǎn)發(fā)現(xiàn)會又多次重試的情況则果,測試環(huán)境有的請求響應(yīng)時間過長也會出現(xiàn)多次請求,網(wǎng)上查詢了配置試了一下無果凭疮,決定自己看看源碼。
本人使用的SpringCloud版本是Camden.SR3串述。
微服務(wù)間調(diào)用其實(shí)走的是http請求执解,debug了一下默認(rèn)的ReadTimeout時間為5s,ConnectTimeout時間為2s纲酗,我使用的是Fegin進(jìn)行微服務(wù)間調(diào)用衰腌,底層用的還是Ribbon,網(wǎng)上提到的配置如下
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
超時時間是有效的但是重試的次數(shù)無效觅赊,如果直接使用ribbon應(yīng)該是有效的右蕊。
下面開始測試:
在微服務(wù)B中建立測試方法,sleep 8s 確保請求超時
public Integer testee(){
try {
Thread.sleep(8000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 9;
}
在微服務(wù)A中使用fegin調(diào)用此方法時看到有異常
看到在SynchronousMethodHandler中請求的方法
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
response.toBuilder().request(request).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
//出現(xiàn)異常后拋出RetryableException
throw errorExecuting(request, e);
}
出現(xiàn)異常后調(diào)用 throw errorExecuting(request, e) 拋出異常
在調(diào)用executeAndDecode的地方catch
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
//重試的地方
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
retryer.continueOrPropagate(e); 這句就是關(guān)鍵繼續(xù)跟進(jìn)
public void continueOrPropagate(RetryableException e) {
//maxAttempts是構(gòu)造方法傳進(jìn)來的大于重試次數(shù)拋出異常,否則繼續(xù)循環(huán)執(zhí)行請求
if (attempt++ >= maxAttempts) {
throw e;
}
....
默認(rèn)的Retryer構(gòu)造器
public Default() {
this(100, SECONDS.toMillis(1), 5);
}
第一個參數(shù)period是請求重試的間隔算法參數(shù)吮螺,第二個參數(shù)maxPeriod 是請求間隔最大時間饶囚,第三個參數(shù)是重試的次數(shù)。算法如下:
long interval = (long) (period * Math.pow(1.5, attempt - 1));
return interval > maxPeriod ? maxPeriod : interval;
}
我們能否改寫參數(shù)呢鸠补?我們再看看SpringCloud的文檔中關(guān)于Retry的配置
新建一個配置類
@Configuration
public class FeginConfig {
@Bean
public Retryer feginRetryer(){
Retryer retryer = new Retryer.Default(100, SECONDS.toMillis(10), 3);
return retryer;
}
}
在feginClient是加入configuration的配置
@FeignClient(value = "fund-server",fallback = FundClientHystrix.class,configuration = FeginConfig.class)
public interface FundClient
重啟重試萝风,只調(diào)用了一次,F(xiàn)egin重試次數(shù)解決紫岩。
我們再看看請求超時這里的參數(shù)
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
//請求參數(shù)
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
其中ReadTimeout 和 ConnectTimeout 讀取的就是ribbon的配置闹丐,再來看一眼
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
如果想覆蓋ribbon的超時設(shè)置可以在剛剛寫的FeginConfig里注入下面的bean
public Request.Options feginOption(){
Request.Options option = new Request.Options(7000,7000);
return option;
}
總結(jié):使用開源的東西在弄不清問題出在哪時最好能看看源碼,對原理的實(shí)現(xiàn)以及自己的編碼思路都有很大的提升被因。