1. 程式人生 > >spring-cloud服務閘道器中的Timeout設定

spring-cloud服務閘道器中的Timeout設定

大家在初次使用spring-cloud的gateway的時候,肯定會被裡面各種的Timeout搞得暈頭轉向。hytrix有設定,ribbon也有。我們一開始也是亂設一桶,Github上各種專案裡也沒幾個設定正確的。對Timeout的研究源於一次log中的warning

The Hystrix timeout of 60000 ms for the command “foo” is set lower than the combination of the Ribbon read and connect timeout, 200000ms.

hytrix超時時間

log出自AbstractRibbonCommand.java

,那麼索性研究一下原始碼。

假設:

  • 這裡gateway會請求一個serviceName=foo的服務
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected static int getHystrixTimeout(IClientConfig config, String commandKey) {
	int ribbonTimeout = getRibbonTimeout(config, commandKey);
	DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();
	
	// 獲取預設的hytrix超時時間
	int defaultHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds",
		0).get();
	// 獲取具體服務的hytrix超時時間,這裡應該是hystrix.command.foo.execution.isolation.thread.timeoutInMilliseconds
	int commandHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command." + commandKey + ".execution.isolation.thread.timeoutInMilliseconds",
		0).get();
	int hystrixTimeout;
	// hystrixTimeout的優先順序是 具體服務的hytrix超時時間 > 預設的hytrix超時時間 > ribbon超時時間
	if(commandHystrixTimeout > 0) {
		hystrixTimeout = commandHystrixTimeout;
	}
	else if(defaultHystrixTimeout > 0) {
		hystrixTimeout = defaultHystrixTimeout;
	} else {
		hystrixTimeout = ribbonTimeout;
	}
	// 如果預設的或者具體服務的hytrix超時時間小於ribbon超時時間就會警告
	if(hystrixTimeout < ribbonTimeout) {
		LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey +
			" is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms.");
	}
	return hystrixTimeout;
}

緊接著,看一下我們的配置是什麼

1
2
3
4
5
6
7
8
9
10
11
12
13
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 60000
            
ribbon:
  ReadTimeout: 50000
  ConnectTimeout: 50000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 1

ribbon超時時間

這裡ribbon的超時時間是50000ms,那麼為什麼log中寫的ribbon時間是200000ms?

繼續分析原始碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected static int getRibbonTimeout(IClientConfig config, String commandKey) {
	int ribbonTimeout;
	// 這是比較異常的情況,不說
	if (config == null) {
		ribbonTimeout = RibbonClientConfiguration.DEFAULT_READ_TIMEOUT + RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT;
	} else {
	   // 這裡獲取了四個引數,ReadTimeout,ConnectTimeout,MaxAutoRetries, MaxAutoRetriesNextServer
		int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout",
			IClientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.DEFAULT_READ_TIMEOUT);
		int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout",
			IClientConfigKey.Keys.ConnectTimeout, RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT);
		int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries",
			IClientConfigKey.Keys.MaxAutoRetries, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES);
		int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer",
			IClientConfigKey.Keys.MaxAutoRetriesNextServer, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER);
		// 原來ribbonTimeout的計算方法在這裡,以上文的設定為例
		// ribbonTimeout = (50000 + 50000) * (0 + 1) * (1 + 1) = 200000
		ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);
	}
	return ribbonTimeout;
}

可以看到ribbonTimeout是一個總時間,所以從邏輯上來講,作者希望hystrixTimeout要大於ribbonTimeout,否則hystrix熔斷了以後,ribbon的重試就都沒有意義了。

ribbon單服務設定

到這裡最前面的疑問已經解開了,但是hytrix可以分服務設定timeout,ribbon可不可以? 原始碼走起,這裡看的檔案是DefaultClientConfigImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 這是獲取配置的入口方法,如果是null,那麼用預設值
// 所有ribbon的預設值的都在該類中設定了,可以自己看一下
public <T> T get(IClientConfigKey<T> key, T defaultValue) {
    T value = get(key);
    if (value == null) {
        value = defaultValue;
    }
    return value;
}

// 這是核心方法   
protected Object getProperty(String key) {
    if (enableDynamicProperties) {
        String dynamicValue = null;
        DynamicStringProperty dynamicProperty = dynamicProperties.get(key);
        // dynamicProperties其實是一個快取,首次訪問foo服務的時候會載入
        if (dynamicProperty != null) {
            dynamicValue = dynamicProperty.get();
        }
        // 如果快取沒有,那麼就再獲取一次,注意這裡的getConfigKey(key)是生成key的方法
        if (dynamicValue == null) {
            dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString();
            // 如果還是沒有取預設值,getDefaultPropName(key)生成key的方法
            if (dynamicValue == null) {
                dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString();
            }
        }
        if (dynamicValue != null) {
            return dynamicValue;
        }
    }
    return properties.get(key);
}

以我們的服務為例:
getConfigKey(key) returns foo.ribbon.ReadTimeout
getDefaultPropName(key) returns ribbon.ReadTimeout

一目瞭然,{serviceName}.ribbon.{propertyName}就可以了。

小結

感覺ribbon和hytrix的配置獲取原始碼略微有點亂,所以也導致大家在設定的時候有些無所適從。spring-cloud的程式碼一直在迭代,無論github上還是文件可能都相對滯後,這時候閱讀原始碼並且動手debug一下是最能接近事實真相的了。