SpringCloud 源码系列(4)—— 负载均衡 Ribbon(上)

SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)

五、Ribbon 核心接口

前面已经了解到 Ribbon 核心接口以及默认实现如何协作来查找要调用的一个实例,这节再来看下各个核心接口的一些特性及其它实现类。

1、客户端配置 — IClientConfig

IClientConfig 就是管理客户端配置的核心接口,它的默认实现类是 DefaultClientConfigImpl。可以看到在创建 IClientConfig 时,设置了 Ribbon 客户端默认的连接和读取超时时间为 1 秒,例如读取如果超过1秒,就会返回超时,这两个一般需要根据实际情况来调整。、

  1. 1 @Bean
  2. 2 @ConditionalOnMissingBean
  3. 3 public IClientConfig ribbonClientConfig() {
  4. 4 DefaultClientConfigImpl config = new DefaultClientConfigImpl();
  5. 5 // 加载配置
  6. 6 config.loadProperties(this.name);
  7. 7 // 连接超时默认 1 秒
  8. 8 config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
  9. 9 // 读取超时默认 1 秒
  10. 10 config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
  11. 11 config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
  12. 12 return config;
  13. 13 }

CommonClientConfigKey 这个类定义了 Ribbon 客户端相关的所有配置的键常量,可以通过这个类来看有哪些配置。

  1. 1 public abstract class CommonClientConfigKey<T> implements IClientConfigKey<T> {
  2. 2
  3. 3 public static final IClientConfigKey<String> AppName = new CommonClientConfigKey<String>("AppName"){};
  4. 4
  5. 5 public static final IClientConfigKey<String> Version = new CommonClientConfigKey<String>("Version"){};
  6. 6
  7. 7 public static final IClientConfigKey<Integer> Port = new CommonClientConfigKey<Integer>("Port"){};
  8. 8
  9. 9 public static final IClientConfigKey<Integer> SecurePort = new CommonClientConfigKey<Integer>("SecurePort"){};
  10. 10
  11. 11 public static final IClientConfigKey<String> VipAddress = new CommonClientConfigKey<String>("VipAddress"){};
  12. 12
  13. 13 public static final IClientConfigKey<Boolean> ForceClientPortConfiguration = new CommonClientConfigKey<Boolean>("ForceClientPortConfiguration"){}; // use client defined port regardless of server advert
  14. 14
  15. 15 public static final IClientConfigKey<String> DeploymentContextBasedVipAddresses = new CommonClientConfigKey<String>("DeploymentContextBasedVipAddresses"){};
  16. 16
  17. 17 public static final IClientConfigKey<Integer> MaxAutoRetries = new CommonClientConfigKey<Integer>("MaxAutoRetries"){};
  18. 18
  19. 19 public static final IClientConfigKey<Integer> MaxAutoRetriesNextServer = new CommonClientConfigKey<Integer>("MaxAutoRetriesNextServer"){};
  20. 20
  21. 21 public static final IClientConfigKey<Boolean> OkToRetryOnAllOperations = new CommonClientConfigKey<Boolean>("OkToRetryOnAllOperations"){};
  22. 22
  23. 23 public static final IClientConfigKey<Boolean> RequestSpecificRetryOn = new CommonClientConfigKey<Boolean>("RequestSpecificRetryOn"){};
  24. 24
  25. 25 public static final IClientConfigKey<Integer> ReceiveBufferSize = new CommonClientConfigKey<Integer>("ReceiveBufferSize"){};
  26. 26
  27. 27 public static final IClientConfigKey<Boolean> EnablePrimeConnections = new CommonClientConfigKey<Boolean>("EnablePrimeConnections"){};
  28. 28
  29. 29 public static final IClientConfigKey<String> PrimeConnectionsClassName = new CommonClientConfigKey<String>("PrimeConnectionsClassName"){};
  30. 30
  31. 31 public static final IClientConfigKey<Integer> MaxRetriesPerServerPrimeConnection = new CommonClientConfigKey<Integer>("MaxRetriesPerServerPrimeConnection"){};
  32. 32
  33. 33 public static final IClientConfigKey<Integer> MaxTotalTimeToPrimeConnections = new CommonClientConfigKey<Integer>("MaxTotalTimeToPrimeConnections"){};
  34. 34
  35. 35 public static final IClientConfigKey<Float> MinPrimeConnectionsRatio = new CommonClientConfigKey<Float>("MinPrimeConnectionsRatio"){};
  36. 36
  37. 37 public static final IClientConfigKey<String> PrimeConnectionsURI = new CommonClientConfigKey<String>("PrimeConnectionsURI"){};
  38. 38
  39. 39 public static final IClientConfigKey<Integer> PoolMaxThreads = new CommonClientConfigKey<Integer>("PoolMaxThreads"){};
  40. 40
  41. 41 public static final IClientConfigKey<Integer> PoolMinThreads = new CommonClientConfigKey<Integer>("PoolMinThreads"){};
  42. 42
  43. 43 public static final IClientConfigKey<Integer> PoolKeepAliveTime = new CommonClientConfigKey<Integer>("PoolKeepAliveTime"){};
  44. 44
  45. 45 public static final IClientConfigKey<String> PoolKeepAliveTimeUnits = new CommonClientConfigKey<String>("PoolKeepAliveTimeUnits"){};
  46. 46
  47. 47 public static final IClientConfigKey<Boolean> EnableConnectionPool = new CommonClientConfigKey<Boolean>("EnableConnectionPool") {};
  48. 48
  49. 49 /**
  50. 50 * Use {@link #MaxConnectionsPerHost}
  51. 51 */
  52. 52 @Deprecated
  53. 53 public static final IClientConfigKey<Integer> MaxHttpConnectionsPerHost = new CommonClientConfigKey<Integer>("MaxHttpConnectionsPerHost"){};
  54. 54
  55. 55 /**
  56. 56 * Use {@link #MaxTotalConnections}
  57. 57 */
  58. 58 @Deprecated
  59. 59 public static final IClientConfigKey<Integer> MaxTotalHttpConnections = new CommonClientConfigKey<Integer>("MaxTotalHttpConnections"){};
  60. 60
  61. 61 public static final IClientConfigKey<Integer> MaxConnectionsPerHost = new CommonClientConfigKey<Integer>("MaxConnectionsPerHost"){};
  62. 62
  63. 63 public static final IClientConfigKey<Integer> MaxTotalConnections = new CommonClientConfigKey<Integer>("MaxTotalConnections"){};
  64. 64
  65. 65 public static final IClientConfigKey<Boolean> IsSecure = new CommonClientConfigKey<Boolean>("IsSecure"){};
  66. 66
  67. 67 public static final IClientConfigKey<Boolean> GZipPayload = new CommonClientConfigKey<Boolean>("GZipPayload"){};
  68. 68
  69. 69 public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout"){};
  70. 70
  71. 71 public static final IClientConfigKey<Integer> BackoffInterval = new CommonClientConfigKey<Integer>("BackoffTimeout"){};
  72. 72
  73. 73 public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout"){};
  74. 74
  75. 75 public static final IClientConfigKey<Integer> SendBufferSize = new CommonClientConfigKey<Integer>("SendBufferSize"){};
  76. 76
  77. 77 public static final IClientConfigKey<Boolean> StaleCheckingEnabled = new CommonClientConfigKey<Boolean>("StaleCheckingEnabled"){};
  78. 78
  79. 79 public static final IClientConfigKey<Integer> Linger = new CommonClientConfigKey<Integer>("Linger"){};
  80. 80
  81. 81 public static final IClientConfigKey<Integer> ConnectionManagerTimeout = new CommonClientConfigKey<Integer>("ConnectionManagerTimeout"){};
  82. 82
  83. 83 public static final IClientConfigKey<Boolean> FollowRedirects = new CommonClientConfigKey<Boolean>("FollowRedirects"){};
  84. 84
  85. 85 public static final IClientConfigKey<Boolean> ConnectionPoolCleanerTaskEnabled = new CommonClientConfigKey<Boolean>("ConnectionPoolCleanerTaskEnabled"){};
  86. 86
  87. 87 public static final IClientConfigKey<Integer> ConnIdleEvictTimeMilliSeconds = new CommonClientConfigKey<Integer>("ConnIdleEvictTimeMilliSeconds"){};
  88. 88
  89. 89 public static final IClientConfigKey<Integer> ConnectionCleanerRepeatInterval = new CommonClientConfigKey<Integer>("ConnectionCleanerRepeatInterval"){};
  90. 90
  91. 91 public static final IClientConfigKey<Boolean> EnableGZIPContentEncodingFilter = new CommonClientConfigKey<Boolean>("EnableGZIPContentEncodingFilter"){};
  92. 92
  93. 93 public static final IClientConfigKey<String> ProxyHost = new CommonClientConfigKey<String>("ProxyHost"){};
  94. 94
  95. 95 public static final IClientConfigKey<Integer> ProxyPort = new CommonClientConfigKey<Integer>("ProxyPort"){};
  96. 96
  97. 97 public static final IClientConfigKey<String> KeyStore = new CommonClientConfigKey<String>("KeyStore"){};
  98. 98
  99. 99 public static final IClientConfigKey<String> KeyStorePassword = new CommonClientConfigKey<String>("KeyStorePassword"){};
  100. 100
  101. 101 public static final IClientConfigKey<String> TrustStore = new CommonClientConfigKey<String>("TrustStore"){};
  102. 102
  103. 103 public static final IClientConfigKey<String> TrustStorePassword = new CommonClientConfigKey<String>("TrustStorePassword"){};
  104. 104
  105. 105 // if this is a secure rest client, must we use client auth too?
  106. 106 public static final IClientConfigKey<Boolean> IsClientAuthRequired = new CommonClientConfigKey<Boolean>("IsClientAuthRequired"){};
  107. 107
  108. 108 public static final IClientConfigKey<String> CustomSSLSocketFactoryClassName = new CommonClientConfigKey<String>("CustomSSLSocketFactoryClassName"){};
  109. 109 // must host name match name in certificate?
  110. 110 public static final IClientConfigKey<Boolean> IsHostnameValidationRequired = new CommonClientConfigKey<Boolean>("IsHostnameValidationRequired"){};
  111. 111
  112. 112 // see also http://hc.apache.org/httpcomponents-client-ga/tutorial/html/advanced.html
  113. 113 public static final IClientConfigKey<Boolean> IgnoreUserTokenInConnectionPoolForSecureClient = new CommonClientConfigKey<Boolean>("IgnoreUserTokenInConnectionPoolForSecureClient"){};
  114. 114
  115. 115 // Client implementation
  116. 116 public static final IClientConfigKey<String> ClientClassName = new CommonClientConfigKey<String>("ClientClassName"){};
  117. 117
  118. 118 //LoadBalancer Related
  119. 119 public static final IClientConfigKey<Boolean> InitializeNFLoadBalancer = new CommonClientConfigKey<Boolean>("InitializeNFLoadBalancer"){};
  120. 120
  121. 121 public static final IClientConfigKey<String> NFLoadBalancerClassName = new CommonClientConfigKey<String>("NFLoadBalancerClassName"){};
  122. 122
  123. 123 public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName"){};
  124. 124
  125. 125 public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName"){};
  126. 126
  127. 127 public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval"){};
  128. 128
  129. 129 public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime"){};
  130. 130
  131. 131 public static final IClientConfigKey<String> NFLoadBalancerStatsClassName = new CommonClientConfigKey<String>("NFLoadBalancerStatsClassName"){};
  132. 132
  133. 133 public static final IClientConfigKey<String> NIWSServerListClassName = new CommonClientConfigKey<String>("NIWSServerListClassName"){};
  134. 134
  135. 135 public static final IClientConfigKey<String> ServerListUpdaterClassName = new CommonClientConfigKey<String>("ServerListUpdaterClassName"){};
  136. 136
  137. 137 public static final IClientConfigKey<String> NIWSServerListFilterClassName = new CommonClientConfigKey<String>("NIWSServerListFilterClassName"){};
  138. 138
  139. 139 public static final IClientConfigKey<Integer> ServerListRefreshInterval = new CommonClientConfigKey<Integer>("ServerListRefreshInterval"){};
  140. 140
  141. 141 public static final IClientConfigKey<Boolean> EnableMarkingServerDownOnReachingFailureLimit = new CommonClientConfigKey<Boolean>("EnableMarkingServerDownOnReachingFailureLimit"){};
  142. 142
  143. 143 public static final IClientConfigKey<Integer> ServerDownFailureLimit = new CommonClientConfigKey<Integer>("ServerDownFailureLimit"){};
  144. 144
  145. 145 public static final IClientConfigKey<Integer> ServerDownStatWindowInMillis = new CommonClientConfigKey<Integer>("ServerDownStatWindowInMillis"){};
  146. 146
  147. 147 public static final IClientConfigKey<Boolean> EnableZoneAffinity = new CommonClientConfigKey<Boolean>("EnableZoneAffinity"){};
  148. 148
  149. 149 public static final IClientConfigKey<Boolean> EnableZoneExclusivity = new CommonClientConfigKey<Boolean>("EnableZoneExclusivity"){};
  150. 150
  151. 151 public static final IClientConfigKey<Boolean> PrioritizeVipAddressBasedServers = new CommonClientConfigKey<Boolean>("PrioritizeVipAddressBasedServers"){};
  152. 152
  153. 153 public static final IClientConfigKey<String> VipAddressResolverClassName = new CommonClientConfigKey<String>("VipAddressResolverClassName"){};
  154. 154
  155. 155 public static final IClientConfigKey<String> TargetRegion = new CommonClientConfigKey<String>("TargetRegion"){};
  156. 156
  157. 157 public static final IClientConfigKey<String> RulePredicateClasses = new CommonClientConfigKey<String>("RulePredicateClasses"){};
  158. 158
  159. 159 public static final IClientConfigKey<String> RequestIdHeaderName = new CommonClientConfigKey<String>("RequestIdHeaderName") {};
  160. 160
  161. 161 public static final IClientConfigKey<Boolean> UseIPAddrForServer = new CommonClientConfigKey<Boolean>("UseIPAddrForServer") {};
  162. 162
  163. 163 public static final IClientConfigKey<String> ListOfServers = new CommonClientConfigKey<String>("listOfServers") {};
  164. 164
  165. 165 private static final Set<IClientConfigKey> keys = new HashSet<IClientConfigKey>();
  166. 166
  167. 167 // ...
  168. 168 }

进入到 DefaultClientConfigImpl,可以看到 CommonClientConfigKey 中的每个配置都对应了一个默认值。在加载配置的时候,如果用户没有定制配置,就会使用默认的配置。

  1. 1 public class DefaultClientConfigImpl implements IClientConfig {
  2. 2
  3. 3 public static final Boolean DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS = Boolean.TRUE;
  4. 4
  5. 5 public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing"; // DummyPing.class.getName();
  6. 6
  7. 7 public static final String DEFAULT_NFLOADBALANCER_RULE_CLASSNAME = "com.netflix.loadbalancer.AvailabilityFilteringRule";
  8. 8
  9. 9 public static final String DEFAULT_NFLOADBALANCER_CLASSNAME = "com.netflix.loadbalancer.ZoneAwareLoadBalancer";
  10. 10
  11. 11 public static final boolean DEFAULT_USEIPADDRESS_FOR_SERVER = Boolean.FALSE;
  12. 12
  13. 13 public static final String DEFAULT_CLIENT_CLASSNAME = "com.netflix.niws.client.http.RestClient";
  14. 14
  15. 15 public static final String DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME = "com.netflix.client.SimpleVipAddressResolver";
  16. 16
  17. 17 public static final String DEFAULT_PRIME_CONNECTIONS_URI = "/";
  18. 18
  19. 19 public static final int DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS = 30000;
  20. 20
  21. 21 public static final int DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION = 9;
  22. 22
  23. 23 public static final Boolean DEFAULT_ENABLE_PRIME_CONNECTIONS = Boolean.FALSE;
  24. 24
  25. 25 public static final int DEFAULT_MAX_REQUESTS_ALLOWED_PER_WINDOW = Integer.MAX_VALUE;
  26. 26
  27. 27 public static final int DEFAULT_REQUEST_THROTTLING_WINDOW_IN_MILLIS = 60000;
  28. 28
  29. 29 public static final Boolean DEFAULT_ENABLE_REQUEST_THROTTLING = Boolean.FALSE;
  30. 30
  31. 31 public static final Boolean DEFAULT_ENABLE_GZIP_CONTENT_ENCODING_FILTER = Boolean.FALSE;
  32. 32
  33. 33 public static final Boolean DEFAULT_CONNECTION_POOL_CLEANER_TASK_ENABLED = Boolean.TRUE;
  34. 34
  35. 35 public static final Boolean DEFAULT_FOLLOW_REDIRECTS = Boolean.FALSE;
  36. 36
  37. 37 public static final float DEFAULT_PERCENTAGE_NIWS_EVENT_LOGGED = 0.0f;
  38. 38
  39. 39 public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1;
  40. 40
  41. 41 public static final int DEFAULT_MAX_AUTO_RETRIES = 0;
  42. 42
  43. 43 public static final int DEFAULT_BACKOFF_INTERVAL = 0;
  44. 44
  45. 45 public static final int DEFAULT_READ_TIMEOUT = 5000;
  46. 46
  47. 47 public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;
  48. 48
  49. 49 public static final int DEFAULT_CONNECT_TIMEOUT = 2000;
  50. 50
  51. 51 public static final Boolean DEFAULT_ENABLE_CONNECTION_POOL = Boolean.TRUE;
  52. 52
  53. 53 @Deprecated
  54. 54 public static final int DEFAULT_MAX_HTTP_CONNECTIONS_PER_HOST = 50;
  55. 55
  56. 56 @Deprecated
  57. 57 public static final int DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS = 200;
  58. 58
  59. 59 public static final int DEFAULT_MAX_CONNECTIONS_PER_HOST = 50;
  60. 60
  61. 61 public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 200;
  62. 62
  63. 63 public static final float DEFAULT_MIN_PRIME_CONNECTIONS_RATIO = 1.0f;
  64. 64
  65. 65 public static final String DEFAULT_PRIME_CONNECTIONS_CLASS = "com.netflix.niws.client.http.HttpPrimeConnection";
  66. 66
  67. 67 public static final String DEFAULT_SEVER_LIST_CLASS = "com.netflix.loadbalancer.ConfigurationBasedServerList";
  68. 68
  69. 69 public static final String DEFAULT_SERVER_LIST_UPDATER_CLASS = "com.netflix.loadbalancer.PollingServerListUpdater";
  70. 70
  71. 71 public static final int DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS = 30000; // every half minute (30 secs)
  72. 72
  73. 73 public static final int DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS = 30000; // all connections idle for 30 secs
  74. 74
  75. 75 protected volatile Map<String, Object> properties = new ConcurrentHashMap<String, Object>();
  76. 76
  77. 77 protected Map<IClientConfigKey<?>, Object> typedProperties = new ConcurrentHashMap<IClientConfigKey<?>, Object>();
  78. 78
  79. 79 private static final Logger LOG = LoggerFactory.getLogger(DefaultClientConfigImpl.class);
  80. 80
  81. 81 private String clientName = null;
  82. 82
  83. 83 private VipAddressResolver resolver = null;
  84. 84
  85. 85 private boolean enableDynamicProperties = true;
  86. 86 /**
  87. 87 * Defaults for the parameters for the thread pool used by batchParallel
  88. 88 * calls
  89. 89 */
  90. 90 public static final int DEFAULT_POOL_MAX_THREADS = DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS;
  91. 91 public static final int DEFAULT_POOL_MIN_THREADS = 1;
  92. 92 public static final long DEFAULT_POOL_KEEP_ALIVE_TIME = 15 * 60L;
  93. 93 public static final TimeUnit DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS = TimeUnit.SECONDS;
  94. 94 public static final Boolean DEFAULT_ENABLE_ZONE_AFFINITY = Boolean.FALSE;
  95. 95 public static final Boolean DEFAULT_ENABLE_ZONE_EXCLUSIVITY = Boolean.FALSE;
  96. 96 public static final int DEFAULT_PORT = 7001;
  97. 97 public static final Boolean DEFAULT_ENABLE_LOADBALANCER = Boolean.TRUE;
  98. 98
  99. 99 public static final String DEFAULT_PROPERTY_NAME_SPACE = "ribbon";
  100. 100
  101. 101 private String propertyNameSpace = DEFAULT_PROPERTY_NAME_SPACE;
  102. 102
  103. 103 public static final Boolean DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS = Boolean.FALSE;
  104. 104
  105. 105 public static final Boolean DEFAULT_ENABLE_NIWS_EVENT_LOGGING = Boolean.TRUE;
  106. 106
  107. 107 public static final Boolean DEFAULT_IS_CLIENT_AUTH_REQUIRED = Boolean.FALSE;
  108. 108
  109. 109 private final Map<String, DynamicStringProperty> dynamicProperties = new ConcurrentHashMap<String, DynamicStringProperty>();
  110. 110
  111. 111 public Boolean getDefaultPrioritizeVipAddressBasedServers() {
  112. 112 return DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS;
  113. 113 }
  114. 114
  115. 115 public String getDefaultNfloadbalancerPingClassname() {
  116. 116 return DEFAULT_NFLOADBALANCER_PING_CLASSNAME;
  117. 117 }
  118. 118
  119. 119 public String getDefaultNfloadbalancerRuleClassname() {
  120. 120 return DEFAULT_NFLOADBALANCER_RULE_CLASSNAME;
  121. 121 }
  122. 122
  123. 123 public String getDefaultNfloadbalancerClassname() {
  124. 124 return DEFAULT_NFLOADBALANCER_CLASSNAME;
  125. 125 }
  126. 126
  127. 127 public boolean getDefaultUseIpAddressForServer() {
  128. 128 return DEFAULT_USEIPADDRESS_FOR_SERVER;
  129. 129 }
  130. 130
  131. 131 public String getDefaultClientClassname() {
  132. 132 return DEFAULT_CLIENT_CLASSNAME;
  133. 133 }
  134. 134
  135. 135 public String getDefaultVipaddressResolverClassname() {
  136. 136 return DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME;
  137. 137 }
  138. 138
  139. 139 public String getDefaultPrimeConnectionsUri() {
  140. 140 return DEFAULT_PRIME_CONNECTIONS_URI;
  141. 141 }
  142. 142
  143. 143 public int getDefaultMaxTotalTimeToPrimeConnections() {
  144. 144 return DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS;
  145. 145 }
  146. 146
  147. 147 public int getDefaultMaxRetriesPerServerPrimeConnection() {
  148. 148 return DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION;
  149. 149 }
  150. 150
  151. 151 public Boolean getDefaultEnablePrimeConnections() {
  152. 152 return DEFAULT_ENABLE_PRIME_CONNECTIONS;
  153. 153 }
  154. 154
  155. 155 public int getDefaultMaxRequestsAllowedPerWindow() {
  156. 156 return DEFAULT_MAX_REQUESTS_ALLOWED_PER_WINDOW;
  157. 157 }
  158. 158
  159. 159 public int getDefaultRequestThrottlingWindowInMillis() {
  160. 160 return DEFAULT_REQUEST_THROTTLING_WINDOW_IN_MILLIS;
  161. 161 }
  162. 162
  163. 163 public Boolean getDefaultEnableRequestThrottling() {
  164. 164 return DEFAULT_ENABLE_REQUEST_THROTTLING;
  165. 165 }
  166. 166
  167. 167 public Boolean getDefaultEnableGzipContentEncodingFilter() {
  168. 168 return DEFAULT_ENABLE_GZIP_CONTENT_ENCODING_FILTER;
  169. 169 }
  170. 170
  171. 171 public Boolean getDefaultConnectionPoolCleanerTaskEnabled() {
  172. 172 return DEFAULT_CONNECTION_POOL_CLEANER_TASK_ENABLED;
  173. 173 }
  174. 174
  175. 175 public Boolean getDefaultFollowRedirects() {
  176. 176 return DEFAULT_FOLLOW_REDIRECTS;
  177. 177 }
  178. 178
  179. 179 public float getDefaultPercentageNiwsEventLogged() {
  180. 180 return DEFAULT_PERCENTAGE_NIWS_EVENT_LOGGED;
  181. 181 }
  182. 182
  183. 183 public int getDefaultMaxAutoRetriesNextServer() {
  184. 184 return DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER;
  185. 185 }
  186. 186
  187. 187 public int getDefaultMaxAutoRetries() {
  188. 188 return DEFAULT_MAX_AUTO_RETRIES;
  189. 189 }
  190. 190
  191. 191 public int getDefaultReadTimeout() {
  192. 192 return DEFAULT_READ_TIMEOUT;
  193. 193 }
  194. 194
  195. 195 public int getDefaultConnectionManagerTimeout() {
  196. 196 return DEFAULT_CONNECTION_MANAGER_TIMEOUT;
  197. 197 }
  198. 198
  199. 199 public int getDefaultConnectTimeout() {
  200. 200 return DEFAULT_CONNECT_TIMEOUT;
  201. 201 }
  202. 202
  203. 203 @Deprecated
  204. 204 public int getDefaultMaxHttpConnectionsPerHost() {
  205. 205 return DEFAULT_MAX_HTTP_CONNECTIONS_PER_HOST;
  206. 206 }
  207. 207
  208. 208 @Deprecated
  209. 209 public int getDefaultMaxTotalHttpConnections() {
  210. 210 return DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS;
  211. 211 }
  212. 212
  213. 213 public int getDefaultMaxConnectionsPerHost() {
  214. 214 return DEFAULT_MAX_CONNECTIONS_PER_HOST;
  215. 215 }
  216. 216
  217. 217 public int getDefaultMaxTotalConnections() {
  218. 218 return DEFAULT_MAX_TOTAL_CONNECTIONS;
  219. 219 }
  220. 220
  221. 221 public float getDefaultMinPrimeConnectionsRatio() {
  222. 222 return DEFAULT_MIN_PRIME_CONNECTIONS_RATIO;
  223. 223 }
  224. 224
  225. 225 public String getDefaultPrimeConnectionsClass() {
  226. 226 return DEFAULT_PRIME_CONNECTIONS_CLASS;
  227. 227 }
  228. 228
  229. 229 public String getDefaultSeverListClass() {
  230. 230 return DEFAULT_SEVER_LIST_CLASS;
  231. 231 }
  232. 232
  233. 233 public int getDefaultConnectionIdleTimertaskRepeatInMsecs() {
  234. 234 return DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS;
  235. 235 }
  236. 236
  237. 237 public int getDefaultConnectionidleTimeInMsecs() {
  238. 238 return DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS;
  239. 239 }
  240. 240
  241. 241 public VipAddressResolver getResolver() {
  242. 242 return resolver;
  243. 243 }
  244. 244
  245. 245 public boolean isEnableDynamicProperties() {
  246. 246 return enableDynamicProperties;
  247. 247 }
  248. 248
  249. 249 public int getDefaultPoolMaxThreads() {
  250. 250 return DEFAULT_POOL_MAX_THREADS;
  251. 251 }
  252. 252
  253. 253 public int getDefaultPoolMinThreads() {
  254. 254 return DEFAULT_POOL_MIN_THREADS;
  255. 255 }
  256. 256
  257. 257 public long getDefaultPoolKeepAliveTime() {
  258. 258 return DEFAULT_POOL_KEEP_ALIVE_TIME;
  259. 259 }
  260. 260
  261. 261 public TimeUnit getDefaultPoolKeepAliveTimeUnits() {
  262. 262 return DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS;
  263. 263 }
  264. 264
  265. 265 public Boolean getDefaultEnableZoneAffinity() {
  266. 266 return DEFAULT_ENABLE_ZONE_AFFINITY;
  267. 267 }
  268. 268
  269. 269 public Boolean getDefaultEnableZoneExclusivity() {
  270. 270 return DEFAULT_ENABLE_ZONE_EXCLUSIVITY;
  271. 271 }
  272. 272
  273. 273 public int getDefaultPort() {
  274. 274 return DEFAULT_PORT;
  275. 275 }
  276. 276
  277. 277 public Boolean getDefaultEnableLoadbalancer() {
  278. 278 return DEFAULT_ENABLE_LOADBALANCER;
  279. 279 }
  280. 280
  281. 281
  282. 282 public Boolean getDefaultOkToRetryOnAllOperations() {
  283. 283 return DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS;
  284. 284 }
  285. 285
  286. 286 public Boolean getDefaultIsClientAuthRequired(){
  287. 287 return DEFAULT_IS_CLIENT_AUTH_REQUIRED;
  288. 288 }
  289. 289
  290. 290
  291. 291 /**
  292. 292 * Create instance with no properties in default name space {@link #DEFAULT_PROPERTY_NAME_SPACE}
  293. 293 */
  294. 294 public DefaultClientConfigImpl() {
  295. 295 this.dynamicProperties.clear();
  296. 296 this.enableDynamicProperties = false;
  297. 297 }
  298. 298
  299. 299 /**
  300. 300 * Create instance with no properties in the specified name space
  301. 301 */
  302. 302 public DefaultClientConfigImpl(String nameSpace) {
  303. 303 this();
  304. 304 this.propertyNameSpace = nameSpace;
  305. 305 }
  306. 306
  307. 307 public void loadDefaultValues() {
  308. 308 putDefaultIntegerProperty(CommonClientConfigKey.MaxHttpConnectionsPerHost, getDefaultMaxHttpConnectionsPerHost());
  309. 309 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalHttpConnections, getDefaultMaxTotalHttpConnections());
  310. 310 putDefaultBooleanProperty(CommonClientConfigKey.EnableConnectionPool, getDefaultEnableConnectionPool());
  311. 311 putDefaultIntegerProperty(CommonClientConfigKey.MaxConnectionsPerHost, getDefaultMaxConnectionsPerHost());
  312. 312 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalConnections, getDefaultMaxTotalConnections());
  313. 313 putDefaultIntegerProperty(CommonClientConfigKey.ConnectTimeout, getDefaultConnectTimeout());
  314. 314 putDefaultIntegerProperty(CommonClientConfigKey.ConnectionManagerTimeout, getDefaultConnectionManagerTimeout());
  315. 315 putDefaultIntegerProperty(CommonClientConfigKey.ReadTimeout, getDefaultReadTimeout());
  316. 316 putDefaultIntegerProperty(CommonClientConfigKey.MaxAutoRetries, getDefaultMaxAutoRetries());
  317. 317 putDefaultIntegerProperty(CommonClientConfigKey.MaxAutoRetriesNextServer, getDefaultMaxAutoRetriesNextServer());
  318. 318 putDefaultBooleanProperty(CommonClientConfigKey.OkToRetryOnAllOperations, getDefaultOkToRetryOnAllOperations());
  319. 319 putDefaultBooleanProperty(CommonClientConfigKey.FollowRedirects, getDefaultFollowRedirects());
  320. 320 putDefaultBooleanProperty(CommonClientConfigKey.ConnectionPoolCleanerTaskEnabled, getDefaultConnectionPoolCleanerTaskEnabled());
  321. 321 putDefaultIntegerProperty(CommonClientConfigKey.ConnIdleEvictTimeMilliSeconds, getDefaultConnectionidleTimeInMsecs());
  322. 322 putDefaultIntegerProperty(CommonClientConfigKey.ConnectionCleanerRepeatInterval, getDefaultConnectionIdleTimertaskRepeatInMsecs());
  323. 323 putDefaultBooleanProperty(CommonClientConfigKey.EnableGZIPContentEncodingFilter, getDefaultEnableGzipContentEncodingFilter());
  324. 324 String proxyHost = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(CommonClientConfigKey.ProxyHost.key()));
  325. 325 if (proxyHost != null && proxyHost.length() > 0) {
  326. 326 setProperty(CommonClientConfigKey.ProxyHost, proxyHost);
  327. 327 }
  328. 328 Integer proxyPort = ConfigurationManager
  329. 329 .getConfigInstance()
  330. 330 .getInteger(
  331. 331 getDefaultPropName(CommonClientConfigKey.ProxyPort),
  332. 332 (Integer.MIN_VALUE + 1)); // + 1 just to avoid potential clash with user setting
  333. 333 if (proxyPort != (Integer.MIN_VALUE + 1)) {
  334. 334 setProperty(CommonClientConfigKey.ProxyPort, proxyPort);
  335. 335 }
  336. 336 putDefaultIntegerProperty(CommonClientConfigKey.Port, getDefaultPort());
  337. 337 putDefaultBooleanProperty(CommonClientConfigKey.EnablePrimeConnections, getDefaultEnablePrimeConnections());
  338. 338 putDefaultIntegerProperty(CommonClientConfigKey.MaxRetriesPerServerPrimeConnection, getDefaultMaxRetriesPerServerPrimeConnection());
  339. 339 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalTimeToPrimeConnections, getDefaultMaxTotalTimeToPrimeConnections());
  340. 340 putDefaultStringProperty(CommonClientConfigKey.PrimeConnectionsURI, getDefaultPrimeConnectionsUri());
  341. 341 putDefaultIntegerProperty(CommonClientConfigKey.PoolMinThreads, getDefaultPoolMinThreads());
  342. 342 putDefaultIntegerProperty(CommonClientConfigKey.PoolMaxThreads, getDefaultPoolMaxThreads());
  343. 343 putDefaultLongProperty(CommonClientConfigKey.PoolKeepAliveTime, getDefaultPoolKeepAliveTime());
  344. 344 putDefaultTimeUnitProperty(CommonClientConfigKey.PoolKeepAliveTimeUnits, getDefaultPoolKeepAliveTimeUnits());
  345. 345 putDefaultBooleanProperty(CommonClientConfigKey.EnableZoneAffinity, getDefaultEnableZoneAffinity());
  346. 346 putDefaultBooleanProperty(CommonClientConfigKey.EnableZoneExclusivity, getDefaultEnableZoneExclusivity());
  347. 347 putDefaultStringProperty(CommonClientConfigKey.ClientClassName, getDefaultClientClassname());
  348. 348 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerClassName, getDefaultNfloadbalancerClassname());
  349. 349 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, getDefaultNfloadbalancerRuleClassname());
  350. 350 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerPingClassName, getDefaultNfloadbalancerPingClassname());
  351. 351 putDefaultBooleanProperty(CommonClientConfigKey.PrioritizeVipAddressBasedServers, getDefaultPrioritizeVipAddressBasedServers());
  352. 352 putDefaultFloatProperty(CommonClientConfigKey.MinPrimeConnectionsRatio, getDefaultMinPrimeConnectionsRatio());
  353. 353 putDefaultStringProperty(CommonClientConfigKey.PrimeConnectionsClassName, getDefaultPrimeConnectionsClass());
  354. 354 putDefaultStringProperty(CommonClientConfigKey.NIWSServerListClassName, getDefaultSeverListClass());
  355. 355 putDefaultStringProperty(CommonClientConfigKey.VipAddressResolverClassName, getDefaultVipaddressResolverClassname());
  356. 356 putDefaultBooleanProperty(CommonClientConfigKey.IsClientAuthRequired, getDefaultIsClientAuthRequired());
  357. 357 // putDefaultStringProperty(CommonClientConfigKey.RequestIdHeaderName, getDefaultRequestIdHeaderName());
  358. 358 putDefaultBooleanProperty(CommonClientConfigKey.UseIPAddrForServer, getDefaultUseIpAddressForServer());
  359. 359 putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
  360. 360 }
  361. 361 }

也可以在配置文件中定制配置,例如配置超时和重试:

  1. 1 # 全局配置
  2. 2 ribbon:
  3. 3 # 客户端读取超时时间
  4. 4 ReadTimeout: 3000
  5. 5 # 客户端连接超时时间
  6. 6 ConnectTimeout: 3000
  7. 7 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  8. 8 OkToRetryOnAllOperations: false
  9. 9 # 重试次数
  10. 10 MaxAutoRetries: 1
  11. 11 # 最多重试几个实例
  12. 12 MaxAutoRetriesNextServer: 1
  13. 13
  14. 14 # 只针对 demo-producer 客户端
  15. 15 demo-producer:
  16. 16 ribbon:
  17. 17 # 客户端读取超时时间
  18. 18 ReadTimeout: 5000
  19. 19 # 客户端连接超时时间
  20. 20 ConnectTimeout: 3000

2、均衡策略 — IRule

IRule 是最终选择 Server 的策略规则类,核心的接口就是 choose。

  1. 1 public interface IRule{
  2. 2
  3. 3 // 选择 Server
  4. 4 public Server choose(Object key);
  5. 5
  6. 6 // 设置 ILoadBalancer
  7. 7 public void setLoadBalancer(ILoadBalancer lb);
  8. 8
  9. 9 // 获取 ILoadBalancer
  10. 10 public ILoadBalancer getLoadBalancer();
  11. 11 }

Ribbon 提供了丰富的负载均衡策略,我们也可以通过配置指定使用某个均衡策略。下面是整个Ribbon提供的 IRule 均衡策略。

3、服务检查 — IPing

IPing 是用于定期检查 Server 的可用性的,它只提供了一个接口,用来判断 Server 是否存活:

  1. 1 public interface IPing {
  2. 2
  3. 3 public boolean isAlive(Server server);
  4. 4 }

IPing 也提供了多种策略可选,下面是整个 IPing 体系结构:

4、获取服务列表 — ServerList

ServerList 提供了两个接口,一个是第一次获取 Server 列表,一个是更新 Server 列表,其中 getUpdatedListOfServers 会每被 Loadbalancer 隔 30 秒调一次来更新 allServerList。

  1. 1 public interface ServerList<T extends Server> {
  2. 2
  3. 3 public List<T> getInitialListOfServers();
  4. 4
  5. 5 /**
  6. 6 * Return updated list of servers. This is called say every 30 secs
  7. 7 * (configurable) by the Loadbalancer's Ping cycle
  8. 8 */
  9. 9 public List<T> getUpdatedListOfServers();
  10. 10 }

ServerList 也提供了多种实现,ServerList 体系结构如下:

5、过滤服务 — ServerListFilter

ServerListFilter 提供了一个接口用来过滤出可用的 Server。

  1. 1 public interface ServerListFilter<T extends Server> {
  2. 2
  3. 3 public List<T> getFilteredListOfServers(List<T> servers);
  4. 4 }

ServerListFilter 体系结构如下:

6、服务列表更新 — ServerListUpdater

ServerListUpdater 有多个接口,最核心的就是 start 开启定时任务调用 updateAction 来更新 allServerList。

  1. 1 public interface ServerListUpdater {
  2. 2
  3. 3 /**
  4. 4 * an interface for the updateAction that actually executes a server list update
  5. 5 */
  6. 6 public interface UpdateAction {
  7. 7 void doUpdate();
  8. 8 }
  9. 9
  10. 10 /**
  11. 11 * start the serverList updater with the given update action
  12. 12 * This call should be idempotent.
  13. 13 */
  14. 14 void start(UpdateAction updateAction);
  15. 15 }

默认有两个实现类:

7、负载均衡器 — ILoadBalancer

ILoadBalancer 是负载均衡选择服务的核心接口,主要提供了如下的获取Server列表和根据客户端名称选择Server的接口。

  1. 1 public interface ILoadBalancer {
  2. 2
  3. 3 // 添加Server
  4. 4 public void addServers(List<Server> newServers);
  5. 5
  6. 6 // 根据key选择一个Server
  7. 7 public Server chooseServer(Object key);
  8. 8
  9. 9 // 获取存活的Server列表,返回 upServerList
  10. 10 public List<Server> getReachableServers();
  11. 11
  12. 12 // 获取所有Server列表,返回 allServerList
  13. 13 public List<Server> getAllServers();
  14. 14 }

ILoadBalancer 的体系结构如下:

8、Ribbon 相关配置类

从前面一直看下来,可以发现有很多与 Ribbon 相关的配置类,这里总结下与 Ribbon 相关的配置类,看每个配置类的配置顺序,以及都主要配置了哪些东西。

① 首先是Eureka客户端配置类 EurekaClientAutoConfiguration,这个自动化配置类主要配置了 Ribbon 所需的 EurekaClient。

  1. 1 @Configuration(proxyBeanMethods = false)
  2. 2 @EnableConfigurationProperties
  3. 3 @ConditionalOnClass(EurekaClientConfig.class)
  4. 4 @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
  5. 5 @ConditionalOnDiscoveryEnabled
  6. 6 @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
  7. 7 CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
  8. 8 @AutoConfigureAfter(name = {
  9. 9 "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
  10. 10 "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
  11. 11 "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
  12. 12 "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
  13. 13 public class EurekaClientAutoConfiguration {
  14. 14 // ....
  15. 15 }

② 接着是Ribbon自动化配置类 RibbonAutoConfiguration,这个类主要配置了如下类:

  • SpringClientFactory:管理 Ribbon 客户端上下文。
  • LoadBalancerClient:负载均衡客户端,默认实现类为 RibbonLoadBalancerClient(实际是在 RibbonClientConfiguration 中配置的)。
  • PropertiesFactory:用于判断配置文件中是否自定义了核心接口的实现类,如 NFLoadBalancerClassName、NFLoadBalancerPingClassName 等。
  • RibbonApplicationContextInitializer:开启饥饿配置的时候,用这个类来在启动时初始化 Ribbon 客户端上下文。

  1. 1 package org.springframework.cloud.netflix.ribbon;
  2. 2
  3. 3 @Configuration
  4. 4 @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
  5. 5 @RibbonClients
  6. 6 // 在 EurekaClientAutoConfiguration 之后配置
  7. 7 @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
  8. 8 // 在 LoadBalancerAutoConfiguration、AsyncLoadBalancerAutoConfiguration 之前配置
  9. 9 @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })
  10. 10 @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })
  11. 11 public class RibbonAutoConfiguration {
  12. 12
  13. 13 @Autowired(required = false)
  14. 14 private List<RibbonClientSpecification> configurations = new ArrayList<>();
  15. 15
  16. 16 @Autowired
  17. 17 private RibbonEagerLoadProperties ribbonEagerLoadProperties;
  18. 18
  19. 19 @Bean
  20. 20 public HasFeatures ribbonFeature() {
  21. 21 return HasFeatures.namedFeature("Ribbon", Ribbon.class);
  22. 22 }
  23. 23
  24. 24 @Bean
  25. 25 @ConditionalOnMissingBean
  26. 26 public SpringClientFactory springClientFactory() {
  27. 27 SpringClientFactory factory = new SpringClientFactory();
  28. 28 factory.setConfigurations(this.configurations);
  29. 29 return factory;
  30. 30 }
  31. 31
  32. 32 @Bean
  33. 33 @ConditionalOnMissingBean(LoadBalancerClient.class)
  34. 34 public LoadBalancerClient loadBalancerClient() {
  35. 35 return new RibbonLoadBalancerClient(springClientFactory());
  36. 36 }
  37. 37
  38. 38 @Bean
  39. 39 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
  40. 40 @ConditionalOnMissingBean
  41. 41 public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
  42. 42 return new RibbonLoadBalancedRetryFactory(clientFactory);
  43. 43 }
  44. 44
  45. 45 @Bean
  46. 46 @ConditionalOnMissingBean
  47. 47 public PropertiesFactory propertiesFactory() {
  48. 48 return new PropertiesFactory();
  49. 49 }
  50. 50
  51. 51 @Bean
  52. 52 @ConditionalOnProperty("ribbon.eager-load.enabled")
  53. 53 public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
  54. 54 return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients());
  55. 55 }
  56. 56 }

③ 接着是负载均衡器配置类 LoadBalancerAutoConfiguration,这个类主要是创建了负载均衡拦截器 LoadBalancerInterceptor,并添加到 RestTemplae 的拦截器中。

  1. 1 package org.springframework.cloud.client.loadbalancer;
  2. 2
  3. 3 @Configuration(proxyBeanMethods = false)
  4. 4 @ConditionalOnClass(RestTemplate.class)
  5. 5 @ConditionalOnBean(LoadBalancerClient.class)
  6. 6 @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
  7. 7 public class LoadBalancerAutoConfiguration {
  8. 8
  9. 9 @LoadBalanced
  10. 10 @Autowired(required = false)
  11. 11 private List<RestTemplate> restTemplates = Collections.emptyList();
  12. 12
  13. 13 @Autowired(required = false)
  14. 14 private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
  15. 15
  16. 16 // 对 RestTemplate 定制化
  17. 17 @Bean
  18. 18 public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
  19. 19 final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
  20. 20 return () -> restTemplateCustomizers.ifAvailable(customizers -> {
  21. 21 for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
  22. 22 for (RestTemplateCustomizer customizer : customizers) {
  23. 23 customizer.customize(restTemplate);
  24. 24 }
  25. 25 }
  26. 26 });
  27. 27 }
  28. 28
  29. 29 @Bean
  30. 30 @ConditionalOnMissingBean
  31. 31 public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
  32. 32 return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
  33. 33 }
  34. 34
  35. 35 @Configuration(proxyBeanMethods = false)
  36. 36 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  37. 37 static class LoadBalancerInterceptorConfig {
  38. 38
  39. 39 // 创建 RestTemplate 拦截器
  40. 40 @Bean
  41. 41 public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient,
  42. 42 LoadBalancerRequestFactory requestFactory) {
  43. 43 return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
  44. 44 }
  45. 45
  46. 46 @Bean
  47. 47 @ConditionalOnMissingBean
  48. 48 public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
  49. 49 return restTemplate -> {
  50. 50 List<ClientHttpRequestInterceptor> list = new ArrayList<>(
  51. 51 restTemplate.getInterceptors());
  52. 52 list.add(loadBalancerInterceptor);
  53. 53 restTemplate.setInterceptors(list);
  54. 54 };
  55. 55 }
  56. 56
  57. 57 }
  58. 58 }

④ 之后是默认的 Ribbon 客户端配置类 RibbonClientConfiguration,这个类主要配置了 Ribbon 核心接口的默认实现。

  • IClientConfig:Ribbon 客户端配置类,默认实现是 DefaultClientConfigImpl。
  • IRule:负载均衡策略规则组件,默认实现是 ZoneAvoidanceRule。
  • IPing:判断 Server 是否存活,默认实现是 DummyPing,永远都是返回 true。
  • ServerList:获取 Server 的组件,默认实现类为 ConfigurationBasedServerList,从配置文件获取。
  • ServerListUpdater:Server 列表更新组件,默认实现类为 PollingServerListUpdater。
  • ServerListFilter:过滤可用的 Server 列表,默认实现类为 ZonePreferenceServerListFilter。
  • RibbonLoadBalancerContext:负载均衡客户端。
  • RetryHandler:重试处理器,默认实现类为 DefaultLoadBalancerRetryHandler。

  1. 1 package org.springframework.cloud.netflix.ribbon;
  2. 2
  3. 3 @SuppressWarnings("deprecation")
  4. 4 @Configuration(proxyBeanMethods = false)
  5. 5 @EnableConfigurationProperties
  6. 6 @Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
  7. 7 RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
  8. 8 public class RibbonClientConfiguration {
  9. 9 public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
  10. 10 public static final int DEFAULT_READ_TIMEOUT = 1000;
  11. 11 public static final boolean DEFAULT_GZIP_PAYLOAD = true;
  12. 12
  13. 13 @RibbonClientName
  14. 14 private String name = "client";
  15. 15
  16. 16 @Autowired
  17. 17 private PropertiesFactory propertiesFactory;
  18. 18
  19. 19 @Bean
  20. 20 @ConditionalOnMissingBean
  21. 21 public IClientConfig ribbonClientConfig() {
  22. 22 DefaultClientConfigImpl config = new DefaultClientConfigImpl();
  23. 23 config.loadProperties(this.name);
  24. 24 config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
  25. 25 config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
  26. 26 config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
  27. 27 return config;
  28. 28 }
  29. 29
  30. 30 @Bean
  31. 31 @ConditionalOnMissingBean
  32. 32 public IRule ribbonRule(IClientConfig config) {
  33. 33 if (this.propertiesFactory.isSet(IRule.class, name)) {
  34. 34 return this.propertiesFactory.get(IRule.class, config, name);
  35. 35 }
  36. 36 ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
  37. 37 rule.initWithNiwsConfig(config);
  38. 38 return rule;
  39. 39 }
  40. 40
  41. 41 @Bean
  42. 42 @ConditionalOnMissingBean
  43. 43 public IPing ribbonPing(IClientConfig config) {
  44. 44 if (this.propertiesFactory.isSet(IPing.class, name)) {
  45. 45 return this.propertiesFactory.get(IPing.class, config, name);
  46. 46 }
  47. 47 return new DummyPing();
  48. 48 }
  49. 49
  50. 50 @Bean
  51. 51 @ConditionalOnMissingBean
  52. 52 @SuppressWarnings("unchecked")
  53. 53 public ServerList<Server> ribbonServerList(IClientConfig config) {
  54. 54 if (this.propertiesFactory.isSet(ServerList.class, name)) {
  55. 55 return this.propertiesFactory.get(ServerList.class, config, name);
  56. 56 }
  57. 57 ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
  58. 58 serverList.initWithNiwsConfig(config);
  59. 59 return serverList;
  60. 60 }
  61. 61
  62. 62 @Bean
  63. 63 @ConditionalOnMissingBean
  64. 64 public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
  65. 65 return new PollingServerListUpdater(config);
  66. 66 }
  67. 67
  68. 68 @Bean
  69. 69 @ConditionalOnMissingBean
  70. 70 public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
  71. 71 ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
  72. 72 IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
  73. 73 if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
  74. 74 return this.propertiesFactory.get(ILoadBalancer.class, config, name);
  75. 75 }
  76. 76 return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
  77. 77 serverListFilter, serverListUpdater);
  78. 78 }
  79. 79
  80. 80 @Bean
  81. 81 @ConditionalOnMissingBean
  82. 82 @SuppressWarnings("unchecked")
  83. 83 public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
  84. 84 if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
  85. 85 return this.propertiesFactory.get(ServerListFilter.class, config, name);
  86. 86 }
  87. 87 ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
  88. 88 filter.initWithNiwsConfig(config);
  89. 89 return filter;
  90. 90 }
  91. 91
  92. 92 @Bean
  93. 93 @ConditionalOnMissingBean
  94. 94 public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
  95. 95 IClientConfig config, RetryHandler retryHandler) {
  96. 96 return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
  97. 97 }
  98. 98
  99. 99 @Bean
  100. 100 @ConditionalOnMissingBean
  101. 101 public RetryHandler retryHandler(IClientConfig config) {
  102. 102 return new DefaultLoadBalancerRetryHandler(config);
  103. 103 }
  104. 104
  105. 105 @Bean
  106. 106 @ConditionalOnMissingBean
  107. 107 public ServerIntrospector serverIntrospector() {
  108. 108 return new DefaultServerIntrospector();
  109. 109 }
  110. 110 }

⑤ Ribbon Eureka 自动化配置类 RibbonEurekaAutoConfiguration,判断是否启用 Ribbon Eureka,并触发 EurekaRibbonClientConfiguration 配置类。

  1. 1 package org.springframework.cloud.netflix.ribbon.eureka;
  2. 2
  3. 3 @Configuration(proxyBeanMethods = false)
  4. 4 @EnableConfigurationProperties
  5. 5 @ConditionalOnRibbonAndEurekaEnabled
  6. 6 @AutoConfigureAfter(RibbonAutoConfiguration.class)
  7. 7 @RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
  8. 8 public class RibbonEurekaAutoConfiguration {
  9. 9
  10. 10 }

⑥ 默认启用 Ribbon Eureka 的情况下,会使用 Ribbon Eureka 客户端配置类 EurekaRibbonClientConfiguration:

  • IPing:替换了默认实现类 DummyPing,改为 NIWSDiscoveryPing,通过判断 InstanceInfo 的状态是否为 UP 来判断 Server 是否存活。
  • ServerList:替换了默认的实现类 ConfigurationBasedServerList,改为 DomainExtractingServerList,实际是 DiscoveryEnabledNIWSServerList,从 EurekaClient 获取 Server 列表。

  1. 1 package org.springframework.cloud.netflix.ribbon.eureka;
  2. 2
  3. 3 @Configuration(proxyBeanMethods = false)
  4. 4 public class EurekaRibbonClientConfiguration {
  5. 5
  6. 6 private static final Log log = LogFactory.getLog(EurekaRibbonClientConfiguration.class);
  7. 7
  8. 8 @Value("${ribbon.eureka.approximateZoneFromHostname:false}")
  9. 9 private boolean approximateZoneFromHostname = false;
  10. 10 @RibbonClientName
  11. 11 private String serviceId = "client";
  12. 12 @Autowired(required = false)
  13. 13 private EurekaClientConfig clientConfig;
  14. 14 @Autowired(required = false)
  15. 15 private EurekaInstanceConfig eurekaConfig;
  16. 16 @Autowired
  17. 17 private PropertiesFactory propertiesFactory;
  18. 18
  19. 19 public EurekaRibbonClientConfiguration() {
  20. 20 }
  21. 21
  22. 22 public EurekaRibbonClientConfiguration(EurekaClientConfig clientConfig,
  23. 23 String serviceId, EurekaInstanceConfig eurekaConfig,
  24. 24 boolean approximateZoneFromHostname) {
  25. 25 this.clientConfig = clientConfig;
  26. 26 this.serviceId = serviceId;
  27. 27 this.eurekaConfig = eurekaConfig;
  28. 28 this.approximateZoneFromHostname = approximateZoneFromHostname;
  29. 29 }
  30. 30
  31. 31 @Bean
  32. 32 @ConditionalOnMissingBean
  33. 33 public IPing ribbonPing(IClientConfig config) {
  34. 34 if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
  35. 35 return this.propertiesFactory.get(IPing.class, config, serviceId);
  36. 36 }
  37. 37 NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
  38. 38 ping.initWithNiwsConfig(config);
  39. 39 return ping;
  40. 40 }
  41. 41
  42. 42 @Bean
  43. 43 @ConditionalOnMissingBean
  44. 44 public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
  45. 45 if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
  46. 46 return this.propertiesFactory.get(ServerList.class, config, serviceId);
  47. 47 }
  48. 48 DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
  49. 49 config, eurekaClientProvider);
  50. 50 DomainExtractingServerList serverList = new DomainExtractingServerList(
  51. 51 discoveryServerList, config, this.approximateZoneFromHostname);
  52. 52 return serverList;
  53. 53 }
  54. 54 }

⑦ 各个配置类所属模块

spring-cloud-netflix-eureka-client:

  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
  • org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration
  • org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration

spring-cloud-netflix-ribbon:

  • org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
  • org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration

spring-cloud-commons:

  • org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

六、Ribbon HTTP客户端组件

1、Java HTTP 组件库

① HTTP 组件库

首先简单了解下常用的 Java HTTP 组件库,Ribbon 中通过不同的配置便可以启用某个 HTTP 组件来进行服务间的通信。

Java 中的 HTTP 组件库,大体可以分为三类:

  • JDK 自带的标准库 HttpURLConnection
  • Apache HttpComponents HttpClient
  • OkHttp

HttpURLConnection 发起 HTTP 请求最大的优点是不需要引入额外的依赖,但是 HttpURLConnection 封装层次太低,使用起来非常繁琐。支持的特性太少,缺乏连接池管理、域名机械控制,无法支持 HTTP/2等。

Apache HttpComponents HttpClient 和 OkHttp 都支持连接池管理、超时、空闲连接数控制等特性。OkHttp 接口设计更友好,且支持 HTTP/2,Android 开发中用得更多。

② 超时重试配置

先给 demo-consumer 中添加如下默认配置,即读取、连接超时时间设置为 1 秒,这也是默认值。然后重试次数为1,重试一个Server。后面基于这些配置来验证Ribbon HTTP客户端的超时和重试。

  1. 1 ribbon:
  2. 2 # 客户端读取超时时间
  3. 3 ReadTimeout: 1000
  4. 4 # 客户端连接超时时间
  5. 5 ConnectTimeout: 1000
  6. 6 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  7. 7 OkToRetryOnAllOperations: false
  8. 8 # 重试次数
  9. 9 MaxAutoRetries: 1
  10. 10 # 最多重试几个实例
  11. 11 MaxAutoRetriesNextServer: 1

然后 demo-producer 的接口休眠3秒,造成网络延迟的现象,并且 demo-producer 启两个实例。

  1. 1 @GetMapping("/v1/uuid")
  2. 2 public ResponseEntity<String> getUUID() throws InterruptedException {
  3. 3 String uuid = UUID.randomUUID().toString();
  4. 4 logger.info("generate uuid: {}", uuid);
  5. 5 Thread.sleep(3000);
  6. 6 return ResponseEntity.ok(uuid);
  7. 7 }

2、Ribbon 默认使用 HttpURLConnection

① Ribbon 默认的 HTTP 组件

在不添加其它配置的情况下,我们来看下 Ribbon 默认使用的 HTTP 组件是什么。

首先通过之前的分析可以知道,默认情况下,LoadBalancerAutoConfiguration 配置类会向 RestTemplate 添加 LoadBalancerInterceptor 拦截器。然后在 RestTemplate 调用时,即在 doExecute 方法中,创建 ClientHttpRequest 时,因为配置了拦截器,所以 ClientHttpRequestFactory 就是 InterceptingClientHttpRequestFactory,而且创建 InterceptingClientHttpRequestFactory 传入的 ClientHttpRequestFactory 默认是父类的 SimpleClientHttpRequestFactory。

  1. 1 protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
  2. 2 @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
  3. 3 //...
  4. 4 ClientHttpResponse response = null;
  5. 5 try {
  6. 6 // 创建 ClientHttpRequest
  7. 7 ClientHttpRequest request = createRequest(url, method);
  8. 8 if (requestCallback != null) {
  9. 9 requestCallback.doWithRequest(request);
  10. 10 }
  11. 11 // ClientHttpRequest 发起请求
  12. 12 response = request.execute();
  13. 13 handleResponse(url, method, response);
  14. 14 return (responseExtractor != null ? responseExtractor.extractData(response) : null);
  15. 15 }
  16. 16 catch (IOException ex) {
  17. 17 // ...
  18. 18 }
  19. 19 finally {
  20. 20 if (response != null) {
  21. 21 response.close();
  22. 22 }
  23. 23 }
  24. 24 }
  25. 25
  26. 26 protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
  27. 27 // getRequestFactory 获取 ClientHttpRequestFactory
  28. 28 ClientHttpRequest request = getRequestFactory().createRequest(url, method);
  29. 29 initialize(request);
  30. 30 if (logger.isDebugEnabled()) {
  31. 31 logger.debug("HTTP " + method.name() + " " + url);
  32. 32 }
  33. 33 return request;
  34. 34 }
  35. 35
  36. 36 public ClientHttpRequestFactory getRequestFactory() {
  37. 37 List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
  38. 38 if (!CollectionUtils.isEmpty(interceptors)) {
  39. 39 ClientHttpRequestFactory factory = this.interceptingRequestFactory;
  40. 40 if (factory == null) {
  41. 41 // 有拦截器的情况,super.getRequestFactory() 默认返回的是 SimpleClientHttpRequestFactory
  42. 42 factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
  43. 43 this.interceptingRequestFactory = factory;
  44. 44 }
  45. 45 return factory;
  46. 46 }
  47. 47 else {
  48. 48 // 无拦截器的情况
  49. 49 return super.getRequestFactory();
  50. 50 }
  51. 51 }

InterceptingClientHttpRequestFactory 这个工厂类创建的 ClientHttpRequest 类型是 InterceptingClientHttpRequest。最终 RestTemplate 的 doExecute 方法中调用 ClientHttpRequest 的 execute 方法时,就调用到了 InterceptingClientHttpRequest 中的内部类 InterceptingRequestExecution 中。

在 InterceptingRequestExecution 的 execute 方法中,首先是遍历所有拦截器对 RestTemplate 定制化,最后则通过 requestFactory 创建 ClientHttpRequest 来发起最终的 HTTP 调用。从这里可以看出,无论有没有拦截器,其实最终都会使用 requestFactory 来创建 ClientHttpRequest。

  1. 1 private class InterceptingRequestExecution implements ClientHttpRequestExecution {
  2. 2 private final Iterator<ClientHttpRequestInterceptor> iterator;
  3. 3
  4. 4 @Override
  5. 5 public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
  6. 6 if (this.iterator.hasNext()) {
  7. 7 // 拦截器定制化 RestTemplate
  8. 8 ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
  9. 9 return nextInterceptor.intercept(request, body, this);
  10. 10 }
  11. 11 else {
  12. 12 HttpMethod method = request.getMethod();
  13. 13 // delegate => SimpleBufferingClientHttpRequest
  14. 14 ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
  15. 15 //...
  16. 16 return delegate.execute();
  17. 17 }
  18. 18 }
  19. 19 }

这里的 requestFactory 就是前面传进来的 SimpleClientHttpRequestFactory,从它的 createRequest 方法可以看出,默认情况下,就是用的 JDK 标准 HTTP 库组件 HttpURLConnection 来进行服务间的请求通信。

  1. 1 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
  2. 2 // JDK 标准HTTP库 HttpURLConnection
  3. 3 HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
  4. 4 prepareConnection(connection, httpMethod.name());
  5. 5
  6. 6 if (this.bufferRequestBody) {
  7. 7 return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
  8. 8 }
  9. 9 else {
  10. 10 return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
  11. 11 }
  12. 12 }

总结:

从前面的源码分析可以看出,Ribbon 默认的 HTTP 客户端是 HttpURLConnection。

在前面默认的超时配置下,可以验证出超时配置并未生效,一直阻塞3秒后才返回了结果,说明 Ribbon 默认情况下就不支持超时重试。

而且 HttpURLConnection 每次都是新创建的,请求返回来之后就关闭连接,没有连接池管理机制,网络连接的建立和关闭本身就会损耗一定的性能,所以正式环境下,最好不要使用默认的配置。

② HttpClient 配置类

另外,我们从 RibbonClientConfiguration 配置类的定义可以看到,其导入了 HttpClientConfiguration、OkHttpRibbonConfiguration、RestClientRibbonConfiguration、HttpClientRibbonConfiguration 四个 HttpClient 的配置类,通过注释也可以了解到,最后一个是默认配置类,前面三个在某些配置启用的情况下才会生效。

  1. 1 @Configuration(proxyBeanMethods = false)
  2. 2 @EnableConfigurationProperties
  3. 3 // Order is important here, last should be the default, first should be optional
  4. 4 @Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
  5. 5 RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
  6. 6 public class RibbonClientConfiguration {
  7. 7
  8. 8 }

进入 HttpClientRibbonConfiguration,这个配置类在 ribbon.httpclient.enabled=true 时才生效,而且默认为 true。在从 SpringClientFactory 中获取 ILoadBalancer 时,会通过这个配置类初始化 HttpClient,按先后顺序会初始化 HttpClientConnectionManager、CloseableHttpClient、RibbonLoadBalancingHttpClient。CloseableHttpClient 是 Apache HttpComponents HttpClient 中的组件,也就是说默认情况下应该是使用 apache HttpComponents 作为 HTTP 组件库。

但经过前面源码的分析,以及测试发现,最终其实走的的 HttpURLConnection,并没有用到 CloseableHttpClient。把 ribbon.httpclient.enabled 设置为 false,也没有什么影响,还是默认走 HttpURLConnection。我们后面再来分析这个问题。

  1. 1 @Configuration(proxyBeanMethods = false)
  2. 2 @ConditionalOnClass(name = "org.apache.http.client.HttpClient")
  3. 3 // ribbon.httpclient.enabled more文为 true
  4. 4 @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true)
  5. 5 public class HttpClientRibbonConfiguration {
  6. 6
  7. 7 @RibbonClientName
  8. 8 private String name = "client";
  9. 9
  10. 10 // RibbonLoadBalancingHttpClient
  11. 11 @Bean
  12. 12 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
  13. 13 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  14. 14 public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient(
  15. 15 IClientConfig config, ServerIntrospector serverIntrospector,
  16. 16 ILoadBalancer loadBalancer, RetryHandler retryHandler,
  17. 17 CloseableHttpClient httpClient) {
  18. 18 RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(
  19. 19 httpClient, config, serverIntrospector);
  20. 20 client.setLoadBalancer(loadBalancer);
  21. 21 client.setRetryHandler(retryHandler);
  22. 22 Monitors.registerObject("Client_" + this.name, client);
  23. 23 return client;
  24. 24 }
  25. 25
  26. 26 // 在引入了 spring-retry 时,即可以重试的 RetryTemplate 时,就创建 RetryableRibbonLoadBalancingHttpClient
  27. 27 @Bean
  28. 28 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
  29. 29 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
  30. 30 public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient(
  31. 31 IClientConfig config, ServerIntrospector serverIntrospector,
  32. 32 ILoadBalancer loadBalancer, RetryHandler retryHandler,
  33. 33 LoadBalancedRetryFactory loadBalancedRetryFactory,
  34. 34 CloseableHttpClient httpClient,
  35. 35 RibbonLoadBalancerContext ribbonLoadBalancerContext) {
  36. 36 RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(
  37. 37 httpClient, config, serverIntrospector, loadBalancedRetryFactory);
  38. 38 client.setLoadBalancer(loadBalancer);
  39. 39 client.setRetryHandler(retryHandler);
  40. 40 client.setRibbonLoadBalancerContext(ribbonLoadBalancerContext);
  41. 41 Monitors.registerObject("Client_" + this.name, client);
  42. 42 return client;
  43. 43 }
  44. 44
  45. 45 @Configuration(proxyBeanMethods = false)
  46. 46 protected static class ApacheHttpClientConfiguration {
  47. 47
  48. 48 private final Timer connectionManagerTimer = new Timer(
  49. 49 "RibbonApacheHttpClientConfiguration.connectionManagerTimer", true);
  50. 50
  51. 51 private CloseableHttpClient httpClient;
  52. 52
  53. 53 @Autowired(required = false)
  54. 54 private RegistryBuilder registryBuilder;
  55. 55
  56. 56 // HttpClient 连接池管理器
  57. 57 @Bean
  58. 58 @ConditionalOnMissingBean(HttpClientConnectionManager.class)
  59. 59 public HttpClientConnectionManager httpClientConnectionManager(
  60. 60 IClientConfig config,
  61. 61 ApacheHttpClientConnectionManagerFactory connectionManagerFactory) {
  62. 62 RibbonProperties ribbon = RibbonProperties.from(config);
  63. 63 int maxTotalConnections = ribbon.maxTotalConnections();
  64. 64 int maxConnectionsPerHost = ribbon.maxConnectionsPerHost();
  65. 65 int timerRepeat = ribbon.connectionCleanerRepeatInterval();
  66. 66 long timeToLive = ribbon.poolKeepAliveTime();
  67. 67 TimeUnit ttlUnit = ribbon.getPoolKeepAliveTimeUnits();
  68. 68 final HttpClientConnectionManager connectionManager = connectionManagerFactory
  69. 69 .newConnectionManager(false, maxTotalConnections,
  70. 70 maxConnectionsPerHost, timeToLive, ttlUnit, registryBuilder);
  71. 71 this.connectionManagerTimer.schedule(new TimerTask() {
  72. 72 @Override
  73. 73 public void run() {
  74. 74 connectionManager.closeExpiredConnections();
  75. 75 }
  76. 76 }, 30000, timerRepeat);
  77. 77 return connectionManager;
  78. 78 }
  79. 79
  80. 80 // HttpClient => CloseableHttpClient
  81. 81 @Bean
  82. 82 @ConditionalOnMissingBean(CloseableHttpClient.class)
  83. 83 public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
  84. 84 HttpClientConnectionManager connectionManager, IClientConfig config) {
  85. 85 RibbonProperties ribbon = RibbonProperties.from(config);
  86. 86 Boolean followRedirects = ribbon.isFollowRedirects();
  87. 87 Integer connectTimeout = ribbon.connectTimeout();
  88. 88 RequestConfig defaultRequestConfig = RequestConfig.custom()
  89. 89 .setConnectTimeout(connectTimeout)
  90. 90 .setRedirectsEnabled(followRedirects).build();
  91. 91 this.httpClient = httpClientFactory.createBuilder()
  92. 92 .setDefaultRequestConfig(defaultRequestConfig)
  93. 93 .setConnectionManager(connectionManager).build();
  94. 94 return httpClient;
  95. 95 }
  96. 96 }
  97. 97 }

③ 默认配置下的 RestTemplate 的调用过程大致可以用下图来表示。

3、启用 RestClient

① 启用 RestClient

可以添加如下配置启用 RestClient:

  1. 1 ribbon:
  2. 2 # 关闭 httpclient
  3. 3 httpclient:
  4. 4 enabled: false
  5. 5 # 启用 RestClient
  6. 6 restclient:
  7. 7 enabled: true
  8. 8 # 启用 RestClient
  9. 9 http:
  10. 10 client:
  11. 11 enabled: true

进入 RestClientRibbonConfiguration  可以看到,只要 ribbon.http.client.enabled、ribbon.restclient.enabled 其中一个配置了启用,就可以启用 RestClient。

  1. 1 @SuppressWarnings("deprecation")
  2. 2 @Configuration(proxyBeanMethods = false)
  3. 3 // 启用条件 ConditionalOnRibbonRestClient
  4. 4 @RibbonAutoConfiguration.ConditionalOnRibbonRestClient
  5. 5 class RestClientRibbonConfiguration {
  6. 6 @RibbonClientName
  7. 7 private String name = "client";
  8. 8
  9. 9 // RestClient 已过期
  10. 10 @Bean
  11. 11 @Lazy
  12. 12 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
  13. 13 public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer,
  14. 14 ServerIntrospector serverIntrospector, RetryHandler retryHandler) {
  15. 15 RestClient client = new RibbonClientConfiguration.OverrideRestClient(config, serverIntrospector);
  16. 16 client.setLoadBalancer(loadBalancer);
  17. 17 client.setRetryHandler(retryHandler);
  18. 18 return client;
  19. 19 }
  20. 20
  21. 21 }
  22. 22
  23. 23 @Target({ ElementType.TYPE, ElementType.METHOD })
  24. 24 @Retention(RetentionPolicy.RUNTIME)
  25. 25 @Documented
  26. 26 @Conditional(OnRibbonRestClientCondition.class)
  27. 27 @interface ConditionalOnRibbonRestClient {
  28. 28 }
  29. 29
  30. 30 private static class OnRibbonRestClientCondition extends AnyNestedCondition {
  31. 31 @Deprecated // remove in Edgware"
  32. 32 @ConditionalOnProperty("ribbon.http.client.enabled")
  33. 33 static class ZuulProperty {
  34. 34 }
  35. 35
  36. 36 @ConditionalOnProperty("ribbon.restclient.enabled")
  37. 37 static class RibbonProperty {
  38. 38 }
  39. 39 }

RestClient 继承自 AbstractLoadBalancerAwareClient。需要注意的是,RestClient 已经过期,所以生产环境中我们就不要启用 RestTemplate 了。

  1. 1 @Deprecated
  2. 2 public class RestClient extends AbstractLoadBalancerAwareClient<HttpRequest, HttpResponse> {
  3. 3
  4. 4 }

② LoadBalancerContext 类体系结构

负载均衡上下文 LoadBalancerContext 体系的类结构如下。可以看出,Ribbon 是支持 Feign、OkHttp、HttpClient、RestClient 的。默认配置下使用的实现类是 RibbonLoadBalancerContext。

③ RestTemplate 的 ClientHttpRequest 工厂类配置

接着看 RibbonAutoConfiguration 中有如下的配置,跟前面 RestClientRibbonConfiguration 也是一样,满足 @ConditionalOnRibbonRestClient 的条件。

可以看到,它会创建 RibbonClientHttpRequestFactory 并设置到 RestTemplate 中,也就是说,这时 RestTemplate 中的 requestFactory 就不是默认的 SimpleClientHttpRequestFactory 了,而是 RibbonClientHttpRequestFactory。

  1. 1 @Configuration(proxyBeanMethods = false)
  2. 2 @ConditionalOnClass(HttpRequest.class)
  3. 3 @ConditionalOnRibbonRestClient
  4. 4 protected static class RibbonClientHttpRequestFactoryConfiguration {
  5. 5 @Autowired
  6. 6 private SpringClientFactory springClientFactory;
  7. 7
  8. 8 @Bean
  9. 9 public RestTemplateCustomizer restTemplateCustomizer(
  10. 10 final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
  11. 11 // RestTemplate 设置 requestFactory 为 RibbonClientHttpRequestFactory
  12. 12 return restTemplate -> restTemplate
  13. 13 .setRequestFactory(ribbonClientHttpRequestFactory);
  14. 14 }
  15. 15
  16. 16 // ClientHttpRequest 工厂类 => RibbonClientHttpRequestFactory
  17. 17 @Bean
  18. 18 public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
  19. 19 return new RibbonClientHttpRequestFactory(this.springClientFactory);
  20. 20 }
  21. 21 }

而且,由于这里配置了 RestTemplateCustomizer,原本默认配置下,在 LoadBalancerAutoConfiguration 中创建 RestTemplateCustomizer 的方法就不会生效了。

LoadBalancerAutoConfiguration 中的 RestTemplateCustomizer 是向 RestTemplate 中添加 LoadBalancerInterceptor 拦截器,所以在启用了 RestClient 的情况下,原本的 LoadBalancerInterceptor 就不会生效了。

  1. 1 @Configuration(proxyBeanMethods = false)
  2. 2 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  3. 3 static class LoadBalancerInterceptorConfig {
  4. 4
  5. 5 @Bean
  6. 6 public LoadBalancerInterceptor ribbonInterceptor(
  7. 7 LoadBalancerClient loadBalancerClient,
  8. 8 LoadBalancerRequestFactory requestFactory) {
  9. 9 return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
  10. 10 }
  11. 11
  12. 12 @Bean
  13. 13 @ConditionalOnMissingBean
  14. 14 public RestTemplateCustomizer restTemplateCustomizer(
  15. 15 final LoadBalancerInterceptor loadBalancerInterceptor) {
  16. 16 return restTemplate -> {
  17. 17 List<ClientHttpRequestInterceptor> list = new ArrayList<>(
  18. 18 restTemplate.getInterceptors());
  19. 19 list.add(loadBalancerInterceptor);
  20. 20 restTemplate.setInterceptors(list);
  21. 21 };
  22. 22 }
  23. 23 }

那么 RestTemplate 的 doExecute 方法中,在调用 createRequest 方法创建 ClientHttpRequest 时,就会用 RibbonClientHttpRequestFactory 来创建,进去可以看到 ClientHttpRequest 的实际类型就是 RibbonHttpRequest。

  1. 1 public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException {
  2. 2 String serviceId = originalUri.getHost();
  3. 3 if (serviceId == null) {
  4. 4 throw new IOException("Invalid hostname in the URI [" + originalUri.toASCIIString() + "]");
  5. 5 }
  6. 6 IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
  7. 7 RestClient client = this.clientFactory.getClient(serviceId, RestClient.class);
  8. 8 HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());
  9. 9
  10. 10 return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
  11. 11 }

调用 RibbonHttpRequest 的 execute 方法,实际组中是调用了它的 executeInternal 方法,然后最后是使用 RestClient 来发起负载均衡的调用。

  1. 1 protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
  2. 2 try {
  3. 3 // ...
  4. 4 HttpRequest request = builder.build();
  5. 5 // client => RestClient
  6. 6 HttpResponse response = client.executeWithLoadBalancer(request, config);
  7. 7 return new RibbonHttpResponse(response);
  8. 8 }
  9. 9 catch (Exception e) {
  10. 10 throw new IOException(e);
  11. 11 }
  12. 12 }

④ RestClient HTTP 调用

RestClient 的 executeWithLoadBalancer 实际是进入到父类 AbstractLoadBalancerAwareClient 的 executeWithLoadBalancer  方法中。

从这个方法可以知道,主要的负载均衡请求是在 LoadBalancerCommand 中的,LoadBalancerCommand 必定会通过负载均衡器 ILoadBalancer 得到一个 Server,然后通过 submit 的这个 ServerOperation 对原始URI进行重构,重构之后调用 RestClient 的 execute 发起HTTP请求。

  1. 1 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
  2. 2 // 负载均衡命令
  3. 3 LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
  4. 4
  5. 5 try {
  6. 6 // 发起负载均衡请求
  7. 7 return command.submit(
  8. 8 new ServerOperation<T>() {
  9. 9 @Override
  10. 10 public Observable<T> call(Server server) {
  11. 11 // 重构 URI,将服务名用 Server 的 IP 和端口替换
  12. 12 URI finalUri = reconstructURIWithServer(server, request.getUri());
  13. 13 S requestForServer = (S) request.replaceUri(finalUri);
  14. 14 try {
  15. 15 // execute 发起调用,实际调用的是 RestClient 中的 execute
  16. 16 return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
  17. 17 }
  18. 18 catch (Exception e) {
  19. 19 return Observable.error(e);
  20. 20 }
  21. 21 }
  22. 22 })
  23. 23 .toBlocking()
  24. 24 .single();
  25. 25 } catch (Exception e) {
  26. 26 //....
  27. 27 }
  28. 28 }

再看 RestClient 的 execute 方法,最终可以发现,RestClient 其实是使用基于 jersey 的 WebResource 来发起 HTTP 请求的。

  1. 1 private HttpResponse execute(HttpRequest.Verb verb, URI uri,
  2. 2 Map<String, Collection<String>> headers, Map<String, Collection<String>> params,
  3. 3 IClientConfig overriddenClientConfig, Object requestEntity) throws Exception {
  4. 4 // ...
  5. 5 // WebResource 是基于 jersey 封装的 HTTP 客户端组件
  6. 6 WebResource xResource = restClient.resource(uri.toString());
  7. 7 ClientResponse jerseyResponse;
  8. 8 Builder b = xResource.getRequestBuilder();
  9. 9 Object entity = requestEntity;
  10. 10
  11. 11 switch (verb) {
  12. 12 case GET:
  13. 13 jerseyResponse = b.get(ClientResponse.class);
  14. 14 break;
  15. 15 case POST:
  16. 16 jerseyResponse = b.post(ClientResponse.class, entity);
  17. 17 break;
  18. 18 case PUT:
  19. 19 jerseyResponse = b.put(ClientResponse.class, entity);
  20. 20 break;
  21. 21 case DELETE:
  22. 22 jerseyResponse = b.delete(ClientResponse.class);
  23. 23 break;
  24. 24 case HEAD:
  25. 25 jerseyResponse = b.head();
  26. 26 break;
  27. 27 case OPTIONS:
  28. 28 jerseyResponse = b.options(ClientResponse.class);
  29. 29 break;
  30. 30 default:
  31. 31 throw new ClientException(
  32. 32 ClientException.ErrorType.GENERAL,
  33. 33 "You have to one of the REST verbs such as GET, POST etc.");
  34. 34 }
  35. 35
  36. 36 thisResponse = new HttpClientResponse(jerseyResponse, uri, overriddenClientConfig);
  37. 37 if (thisResponse.getStatus() == 503){
  38. 38 thisResponse.close();
  39. 39 throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED);
  40. 40 }
  41. 41 return thisResponse;
  42. 42 }

⑤ 最后,RestTemplate 基于 RestClient 的请求流程可以用下图来做个总结。

4、Apache HttpClient 或 OkHttp 对 RestTemplate 不生效(BUG?)

① Apache HttpClient

默认情况下,ribbon.httpclient.enabled=true,在 HttpClientRibbonConfiguration 中会初始化 apache httpcomponents 相关的组件,前已经分析过了,但是在 RestTemplate 中并未使用相关的组件。

也就是说,默认情况下启用了 apache httpcomponents,但是 RestTemplate 最后是使用 HttpURLConnection 来发起 HTTP 请求的,而不是配置的 CloseableHttpClient。

② OkHttp

首先需要加入 OkHttp 的依赖:

  1. 1 <dependency>
  2. 2 <groupId>com.squareup.okhttp3</groupId>
  3. 3 <artifactId>okhttp</artifactId>
  4. 4 </dependency>

然后添加如下配置就可以启用 OkHttp:

  1. 1 ribbon:
  2. 2 httpclient:
  3. 3 enabled: false
  4. 4 # 启用 okhttp
  5. 5 okhttp:
  6. 6 enabled: true

配置 ribbon.okhttp.enabled=true 后,在 OkHttpRibbonConfiguration 中会初始化 OkHttp 相关的组件。

但是调试之后会发现,其实它还是走的默认的流程,就是最终用 HttpURLConnection 发起 HTTP 请求,跟 httpcomponents 是一样的效果。

  1. 1 @Configuration(proxyBeanMethods = false)
  2. 2 @ConditionalOnProperty("ribbon.okhttp.enabled")
  3. 3 @ConditionalOnClass(name = "okhttp3.OkHttpClient")
  4. 4 public class OkHttpRibbonConfiguration {
  5. 5
  6. 6 @RibbonClientName
  7. 7 private String name = "client";
  8. 8
  9. 9 @Bean
  10. 10 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
  11. 11 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
  12. 12 public RetryableOkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(
  13. 13 IClientConfig config, ServerIntrospector serverIntrospector,
  14. 14 ILoadBalancer loadBalancer, RetryHandler retryHandler,
  15. 15 LoadBalancedRetryFactory loadBalancedRetryFactory, OkHttpClient delegate,
  16. 16 RibbonLoadBalancerContext ribbonLoadBalancerContext) {
  17. 17 RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(
  18. 18 delegate, config, serverIntrospector, loadBalancedRetryFactory);
  19. 19 client.setLoadBalancer(loadBalancer);
  20. 20 client.setRetryHandler(retryHandler);
  21. 21 client.setRibbonLoadBalancerContext(ribbonLoadBalancerContext);
  22. 22 Monitors.registerObject("Client_" + this.name, client);
  23. 23 return client;
  24. 24 }
  25. 25
  26. 26 @Bean
  27. 27 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
  28. 28 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  29. 29 public OkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config,
  30. 30 ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer,
  31. 31 RetryHandler retryHandler, OkHttpClient delegate) {
  32. 32 OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(delegate, config,
  33. 33 serverIntrospector);
  34. 34 client.setLoadBalancer(loadBalancer);
  35. 35 client.setRetryHandler(retryHandler);
  36. 36 Monitors.registerObject("Client_" + this.name, client);
  37. 37 return client;
  38. 38 }
  39. 39
  40. 40 @Configuration(proxyBeanMethods = false)
  41. 41 protected static class OkHttpClientConfiguration {
  42. 42
  43. 43 private OkHttpClient httpClient;
  44. 44
  45. 45 @Bean
  46. 46 @ConditionalOnMissingBean(ConnectionPool.class)
  47. 47 public ConnectionPool httpClientConnectionPool(IClientConfig config,
  48. 48 OkHttpClientConnectionPoolFactory connectionPoolFactory) {
  49. 49 RibbonProperties ribbon = RibbonProperties.from(config);
  50. 50 int maxTotalConnections = ribbon.maxTotalConnections();
  51. 51 long timeToLive = ribbon.poolKeepAliveTime();
  52. 52 TimeUnit ttlUnit = ribbon.getPoolKeepAliveTimeUnits();
  53. 53 return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
  54. 54 }
  55. 55
  56. 56 @Bean
  57. 57 @ConditionalOnMissingBean(OkHttpClient.class)
  58. 58 public OkHttpClient client(OkHttpClientFactory httpClientFactory,
  59. 59 ConnectionPool connectionPool, IClientConfig config) {
  60. 60 RibbonProperties ribbon = RibbonProperties.from(config);
  61. 61 this.httpClient = httpClientFactory.createBuilder(false)
  62. 62 .connectTimeout(ribbon.connectTimeout(), TimeUnit.MILLISECONDS)
  63. 63 .readTimeout(ribbon.readTimeout(), TimeUnit.MILLISECONDS)
  64. 64 .followRedirects(ribbon.isFollowRedirects())
  65. 65 .connectionPool(connectionPool).build();
  66. 66 return this.httpClient;
  67. 67 }
  68. 68
  69. 69 @PreDestroy
  70. 70 public void destroy() {
  71. 71 if (httpClient != null) {
  72. 72 httpClient.dispatcher().executorService().shutdown();
  73. 73 httpClient.connectionPool().evictAll();
  74. 74 }
  75. 75 }
  76. 76
  77. 77 }
  78. 78
  79. 79 }

View Cod

③ 启用 HttpClient 或 OkHttp 不生效的原因

经过前面的分析,可以知道启用 apache httpcomponents 或者 OkHttp,对 RestTemplate 都没有起作用,最终还是用 HttpURLConnection 发起 HTTP 请求。那为什么为出现这种情况呢?我们可以看下 RestTemplate  的 setRequestFactory 方法。

通过 RestTemplate 的 setRequestFactory 方法的注释也可以了解到,默认的 requestFactory 是 SimpleClientHttpRequestFactory,它是基于 JDK 标准 HTTP 库的 HttpURLConnection。

默认的 HttpURLConnection 不支持 PATCH,如果想支持,需设置为 Apache HttpComponents 或 OkHttp 的 request factory。

  1. 1 /**
  2. 2 * Set the request factory that this accessor uses for obtaining client request handles.
  3. 3 * <p>The default is a {@link SimpleClientHttpRequestFactory} based on the JDK's own
  4. 4 * HTTP libraries ({@link java.net.HttpURLConnection}).
  5. 5 * <p><b>Note that the standard JDK HTTP library does not support the HTTP PATCH method.
  6. 6 * Configure the Apache HttpComponents or OkHttp request factory to enable PATCH.</b>
  7. 7 * @see #createRequest(URI, HttpMethod)
  8. 8 * @see SimpleClientHttpRequestFactory
  9. 9 * @see org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory
  10. 10 * @see org.springframework.http.client.OkHttp3ClientHttpRequestFactory
  11. 11 */
  12. 12 public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
  13. 13 Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
  14. 14 this.requestFactory = requestFactory;
  15. 15 }

ClientHttpRequestFactory 体系类结构如下:

那 RestClient 又是如何生效的呢?通过上一节的分析可以知道,在 RibbonAutoConfiguration 中有如下的配置,这个 RibbonClientHttpRequestFactoryConfiguration 通过自定义 RestTemplateCustomizer 向 RestTemplate 设置了 requestFactory 为 RibbonClientHttpRequestFactory。

  1. 1 @Configuration(proxyBeanMethods = false)
  2. 2 @ConditionalOnClass(HttpRequest.class)
  3. 3 @ConditionalOnRibbonRestClient
  4. 4 protected static class RibbonClientHttpRequestFactoryConfiguration {
  5. 5 @Autowired
  6. 6 private SpringClientFactory springClientFactory;
  7. 7
  8. 8 @Bean
  9. 9 public RestTemplateCustomizer restTemplateCustomizer(final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
  10. 10 return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
  11. 11 }
  12. 12
  13. 13 @Bean
  14. 14 public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
  15. 15 return new RibbonClientHttpRequestFactory(this.springClientFactory);
  16. 16 }
  17. 17 }

RibbonClientHttpRequestFactory 是对应 RestClient 的,也就是说要启用 OkHttp 或 HttpClient,还需自己创建对应的 ClientHttpRequestFactory,并设置给 RestTemplate。从上面的类结构可以看出,是提供了 HttpComponentsClientHttpRequestFactory 和 OkHttp3ClientHttpRequestFactory 工厂类了的。

这里其实也比较奇怪,既然启用了 apache httpcomponents 或者 OkHttp,却没有创建默认的 ClientHttpRequestFactory 实现类设置给 RestTemplate,感觉这是 spring-cloud-netflix-ribbon 的一个 BUG。

5、定制 RestTemplate 使用 Apache httpcomponents

如果想让 RestTemplate 使用 httpcomponents  的组件,就需要自己创建一个 ClientHttpRequestFactory,并设置给 RestTemplate。下面我们一步步来看看如何修复这个问题。

① 设置 HttpComponentsClientHttpRequestFactory

httpcomponents  中提供的 ClientHttpRequestFactory 实现类是 HttpComponentsClientHttpRequestFactory,但是并不能直接使用这个工厂类,因为它创建的 HttpComponentsClientHttpRequest 不具备重试的能力,它直接使用 CloseableHttpClient 执行请求,虽然有超时的功能,但不能重试。而且,它本质上也没有负载均衡的能力,需要借助 LoadBalancerInterceptor 拦截器来重构 URI。

  1. 1 final class HttpComponentsClientHttpRequest extends AbstractBufferingClientHttpRequest {
  2. 2 private final HttpClient httpClient;
  3. 3 private final HttpUriRequest httpRequest;
  4. 4 private final HttpContext httpContext;
  5. 5
  6. 6 @Override
  7. 7 protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
  8. 8 // ...
  9. 9 // httpClient => CloseableHttpClient
  10. 10 HttpResponse httpResponse = this.httpClient.execute(this.httpRequest, this.httpContext);
  11. 11 return new HttpComponentsClientHttpResponse(httpResponse);
  12. 12 }
  13. 13 }

所以,如果不需要重试的功能,可以直接创建一个 HttpComponentsClientHttpRequest,并设置给 RestTemplate 即可。这样就会使用 LoadBalancerInterceptor 来做负载均衡,重构 URI,然后用 HttpComponentsClientHttpRequest 来执行请求。

  1. 1 @Bean
  2. 2 @LoadBalanced
  3. 3 public RestTemplate restTemplate() {
  4. 4 HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
  5. 5 RestTemplate restTemplate = new RestTemplate();
  6. 6 restTemplate.setRequestFactory(requestFactory);
  7. 7 return restTemplate;
  8. 8 }

② 定制 apache ClientHttpRequestFactory

如果想让 RestTemplate 即有负载均衡的能力,又能使用 apache HttpComponents 组件,且具备重试的功能,我们就需要自己定制 ClientHttpRequestFactory 了。关于重试后面再单独来讲。

对比 RestClient 可以发现,RibbonClientHttpRequestFactory 创建的 RibbonHttpRequest 其实是使用 RestClient 执行请求,而 RestClient  内部使用 LoadBalancerCommand 来进行重试。

类似的,我们至少要用上已经配置好的 RibbonLoadBalancingHttpClient 来执行请求,所以需要自定义一个类似的 RibbonHttpRequest 。

1)定制 apache ClientHttpRequest

创建 ApacheClientHttpRequest 继承自 RibbonHttpRequest,核心的点在于要注入 RibbonLoadBalancingHttpClient,如果要支持重试,需注入 RetryableRibbonLoadBalancingHttpClient。RetryableRibbonLoadBalancingHttpClient 在引入 spring-retry 后才会创建,这个后面分析重试时再看。

然后在 executeInternal 根据 retryable 判断,如果要重试,就调用 execute 方法,看 RetryableRibbonLoadBalancingHttpClient 的源码可以发现,它本身是支持负载均衡的,会自动选择 Server。

如果不需要重试,就需要调用 executeWithLoadBalancer,它是利用 LoadBalancerCommand 来提交请求,就跟 RestClient 是一样的了。但是不一样的地方是 RibbonLoadBalancingHttpClient 的 executeWithLoadBalancer 是不会进行重试的,这个也放到后面分析。

  1. 1 package com.lyyzoo.sunny.register.ribbon.apache;
  2. 2
  3. 3 import java.io.IOException;
  4. 4 import java.net.URI;
  5. 5 import java.util.ArrayList;
  6. 6
  7. 7 import com.netflix.client.config.IClientConfig;
  8. 8 import com.netflix.client.http.HttpResponse;
  9. 9 import org.springframework.cloud.netflix.ribbon.RibbonHttpRequest;
  10. 10 import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse;
  11. 11 import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient;
  12. 12 import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpRequest;
  13. 13 import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
  14. 14 import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext;
  15. 15 import org.springframework.http.HttpHeaders;
  16. 16 import org.springframework.http.HttpMethod;
  17. 17 import org.springframework.http.client.ClientHttpResponse;
  18. 18 import org.springframework.util.LinkedMultiValueMap;
  19. 19
  20. 20 /**
  21. 21 * Apache ClientHttpRequest
  22. 22 *
  23. 23 * @author bojiangzhou
  24. 24 */
  25. 25 public class ApacheClientHttpRequest extends RibbonHttpRequest {
  26. 26
  27. 27 private final URI uri;
  28. 28
  29. 29 private final HttpMethod httpMethod;
  30. 30
  31. 31 private final String serviceId;
  32. 32
  33. 33 private final RibbonLoadBalancingHttpClient client;
  34. 34
  35. 35 private final IClientConfig config;
  36. 36 /**
  37. 37 * 是否重试
  38. 38 */
  39. 39 private final boolean retryable;
  40. 40
  41. 41 public ApacheClientHttpRequest(URI uri,
  42. 42 HttpMethod httpMethod,
  43. 43 String serviceId,
  44. 44 RibbonLoadBalancingHttpClient client,
  45. 45 IClientConfig config,
  46. 46 boolean retryable) {
  47. 47 super(uri, null, null, config);
  48. 48 this.uri = uri;
  49. 49 this.httpMethod = httpMethod;
  50. 50 this.serviceId = serviceId;
  51. 51 this.client = client;
  52. 52 this.config = config;
  53. 53 this.retryable = retryable;
  54. 54 if (retryable && !(client instanceof RetryableRibbonLoadBalancingHttpClient)) {
  55. 55 throw new IllegalArgumentException("Retryable client must be RetryableRibbonLoadBalancingHttpClient");
  56. 56 }
  57. 57 }
  58. 58
  59. 59 @Override
  60. 60 protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
  61. 61 try {
  62. 62 RibbonCommandContext ribbonCommandContext = new RibbonCommandContext(serviceId, httpMethod.name(),
  63. 63 uri.toString(), retryable, headers, new LinkedMultiValueMap<>(), null, new ArrayList<>());
  64. 64
  65. 65 RibbonApacheHttpRequest request = new RibbonApacheHttpRequest(ribbonCommandContext);
  66. 66
  67. 67 HttpResponse response;
  68. 68 if (retryable) {
  69. 69 // RetryableRibbonLoadBalancingHttpClient 本身具备负载均衡的能力
  70. 70 response = client.execute(request, config);
  71. 71 } else {
  72. 72 // RibbonLoadBalancingHttpClient 需调用 executeWithLoadBalancer 才具备负载均衡的能力
  73. 73 response = client.executeWithLoadBalancer(request, config);
  74. 74 }
  75. 75
  76. 76 return new RibbonHttpResponse(response);
  77. 77 } catch (Exception e) {
  78. 78 throw new IOException(e);
  79. 79 }
  80. 80 }
  81. 81 }

2)定制 apache ClientHttpRequestFactory

创建 ApacheClientHttpRequestFactory 继承自 HttpComponentsClientHttpRequestFactory,主要是在 createRequest 方法中创建自定义的 ApacheClientHttpRequest。RibbonLoadBalancingHttpClient 可以从 SpringClientFactory 中获取。

  1. 1 package com.lyyzoo.sunny.register.ribbon.apache;
  2. 2
  3. 3 import java.io.IOException;
  4. 4 import java.net.URI;
  5. 5
  6. 6 import com.netflix.client.config.IClientConfig;
  7. 7 import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
  8. 8 import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
  9. 9 import org.springframework.http.HttpMethod;
  10. 10 import org.springframework.http.client.ClientHttpRequest;
  11. 11 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
  12. 12 import org.springframework.lang.NonNull;
  13. 13
  14. 14 /**
  15. 15 * Apache HttpComponents ClientHttpRequest factory
  16. 16 *
  17. 17 * @author bojiangzhou
  18. 18 */
  19. 19 public class ApacheClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
  20. 20
  21. 21 private final SpringClientFactory clientFactory;
  22. 22 private final boolean retryable;
  23. 23
  24. 24 public ApacheClientHttpRequestFactory(SpringClientFactory clientFactory, boolean retryable) {
  25. 25 this.clientFactory = clientFactory;
  26. 26 this.retryable = retryable;
  27. 27 }
  28. 28
  29. 29 @Override
  30. 30 @NonNull
  31. 31 public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException {
  32. 32 String serviceId = originalUri.getHost();
  33. 33 if (serviceId == null) {
  34. 34 throw new IOException(
  35. 35 "Invalid hostname in the URI [" + originalUri.toASCIIString() + "]");
  36. 36 }
  37. 37 IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
  38. 38 RibbonLoadBalancingHttpClient httpClient = this.clientFactory.getClient(serviceId, RibbonLoadBalancingHttpClient.class);
  39. 39
  40. 40 return new ApacheClientHttpRequest(originalUri, httpMethod, serviceId, httpClient, clientConfig, retryable);
  41. 41 }
  42. 42 }

3)定制 apache ClientHttpRequestFactory 配置类

跟 RestClient 的配置类类似,定制 ApacheClientHttpRequestFactory 的配置类,同样的,默认启用 httpclient。在存在 RetryTemplate 时,就设置 ApacheClientHttpRequestFactory 的 retryable 参数为 true,否则为 false。

然后自定义 RestTemplateCustomizer,将 ApacheClientHttpRequestFactory 设置到 RestTemplate 中,注意这时 LoadBalancerInterceptor 就不会添加到 RestTemplate 中了。

  1. 1 package com.lyyzoo.sunny.register.ribbon.apache;
  2. 2
  3. 3 import org.apache.http.client.HttpClient;
  4. 4 import org.springframework.beans.factory.annotation.Autowired;
  5. 5 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  6. 6 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
  7. 7 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  8. 8 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  9. 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
  10. 10 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  11. 11 import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
  12. 12 import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
  13. 13 import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
  14. 14 import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
  15. 15 import org.springframework.context.annotation.Bean;
  16. 16 import org.springframework.context.annotation.Configuration;
  17. 17 import org.springframework.web.client.RestTemplate;
  18. 18
  19. 19 /**
  20. 20 *
  21. 21 * @author bojiangzhou
  22. 22 */
  23. 23 @Configuration
  24. 24 @ConditionalOnClass(RestTemplate.class)
  25. 25 @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
  26. 26 @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
  27. 27 @ConditionalOnProperty(name = "ribbon.httpclient.restTemplate.enabled", matchIfMissing = true)
  28. 28 public class ApacheClientHttpRequestFactoryConfiguration {
  29. 29
  30. 30 @Configuration(proxyBeanMethods = false)
  31. 31 @ConditionalOnClass(HttpClient.class)
  32. 32 @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true)
  33. 33 static class HttpComponentsClientHttpRequestFactoryConfiguration {
  34. 34
  35. 35 @Autowired
  36. 36 private SpringClientFactory springClientFactory;
  37. 37
  38. 38 @Bean
  39. 39 @ConditionalOnMissingBean
  40. 40 public RestTemplateCustomizer restTemplateCustomizer(
  41. 41 final ApacheClientHttpRequestFactory apacheClientHttpRequestFactory) {
  42. 42 return restTemplate -> restTemplate
  43. 43 .setRequestFactory(apacheClientHttpRequestFactory);
  44. 44 }
  45. 45
  46. 46 @Bean
  47. 47 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  48. 48 public ApacheClientHttpRequestFactory apacheClientHttpRequestFactory() {
  49. 49 return new ApacheClientHttpRequestFactory(springClientFactory, false);
  50. 50 }
  51. 51
  52. 52 @Bean
  53. 53 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
  54. 54 public ApacheClientHttpRequestFactory retryableApacheClientHttpRequestFactory() {
  55. 55 return new ApacheClientHttpRequestFactory(springClientFactory, true);
  56. 56 }
  57. 57 }
  58. 58 }

4)简单调试下

配置好之后,把 demo-consumer 服务启动起来,简单测试下。

a) 首先请求会进入到 RestTemplate 的 doExecute 中,然后通过 createRequest,调用 ApacheClientHttpRequestFactory 创建 ApacheClientHttpRequest。

b) 接着调用 ApacheClientHttpRequest 的 execute 方法,在 ApacheClientHttpRequest  的 executeInternal 中,就会调用 RibbonLoadBalancingHttpClient 的 executeWithLoadBalancer 方法。

c) 最后,进入 RibbonLoadBalancingHttpClient 的 execute 方法中,它又将请求转给了代理对象 delegate 来执行,delegate 就是在 HttpClientRibbonConfiguration 中配置的 CloseableHttpClient 对象,实际类型是 InternalHttpClient。

经过验证,通过自定义的配置,最终使得 RestTemplate 可以使用 apache httpcomponents 组件来执行 HTTP 请求。重试那块后面再来研究。

③ 还是用一张图来总结下 RestTemplate 基于 apache HttpClient 后的执行流程

6、定制 RestTemplate 使用 OkHttp

① 设置 OkHttp3ClientHttpRequestFactory

类似的,可以给 RestTemplate 直接设置 OkHttp3ClientHttpRequestFactory,但它同样也不具备重试的能力。

  1. 1 @Bean
  2. 2 @LoadBalanced
  3. 3 public RestTemplate restTemplate() {
  4. 4 OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory();
  5. 5 RestTemplate restTemplate = new RestTemplate();
  6. 6 restTemplate.setRequestFactory(requestFactory);
  7. 7 return restTemplate;
  8. 8 }

② 定制 OkHttp ClientHttpRequestFactory

与定制 apache httpcomponents  类似,我这里就直接把三个类的代码放出来了。

a) OkHttpClientHttpRequest:

  1. 1 package com.lyyzoo.sunny.register.ribbon.okhttp;
  2. 2
  3. 3 import java.io.IOException;
  4. 4 import java.net.URI;
  5. 5 import java.util.ArrayList;
  6. 6
  7. 7 import com.netflix.client.config.IClientConfig;
  8. 8 import com.netflix.client.http.HttpResponse;
  9. 9 import org.springframework.cloud.netflix.ribbon.RibbonHttpRequest;
  10. 10 import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse;
  11. 11 import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient;
  12. 12 import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonRequest;
  13. 13 import org.springframework.cloud.netflix.ribbon.okhttp.RetryableOkHttpLoadBalancingClient;
  14. 14 import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext;
  15. 15 import org.springframework.http.HttpHeaders;
  16. 16 import org.springframework.http.HttpMethod;
  17. 17 import org.springframework.http.client.ClientHttpResponse;
  18. 18 import org.springframework.util.LinkedMultiValueMap;
  19. 19
  20. 20 /**
  21. 21 * OkHttp ClientHttpRequest
  22. 22 *
  23. 23 * @author bojiangzhou
  24. 24 */
  25. 25 public class OkHttpClientHttpRequest extends RibbonHttpRequest {
  26. 26
  27. 27 private final URI uri;
  28. 28
  29. 29 private final HttpMethod httpMethod;
  30. 30
  31. 31 private final String serviceId;
  32. 32
  33. 33 private final OkHttpLoadBalancingClient client;
  34. 34
  35. 35 private final IClientConfig config;
  36. 36 /**
  37. 37 * 是否重试
  38. 38 */
  39. 39 private final boolean retryable;
  40. 40
  41. 41 public OkHttpClientHttpRequest(URI uri,
  42. 42 HttpMethod httpMethod,
  43. 43 String serviceId,
  44. 44 OkHttpLoadBalancingClient client,
  45. 45 IClientConfig config,
  46. 46 boolean retryable) {
  47. 47 super(uri, null, null, config);
  48. 48 this.uri = uri;
  49. 49 this.httpMethod = httpMethod;
  50. 50 this.serviceId = serviceId;
  51. 51 this.client = client;
  52. 52 this.config = config;
  53. 53 this.retryable = retryable;
  54. 54 if (retryable && !(client instanceof RetryableOkHttpLoadBalancingClient)) {
  55. 55 throw new IllegalArgumentException("Retryable client must be RetryableOkHttpLoadBalancingClient");
  56. 56 }
  57. 57 }
  58. 58
  59. 59 @Override
  60. 60 protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
  61. 61 try {
  62. 62 RibbonCommandContext ribbonCommandContext = new RibbonCommandContext(serviceId, httpMethod.name(),
  63. 63 uri.toString(), retryable, headers, new LinkedMultiValueMap<>(), null, new ArrayList<>());
  64. 64
  65. 65 OkHttpRibbonRequest request = new OkHttpRibbonRequest(ribbonCommandContext);
  66. 66
  67. 67 HttpResponse response;
  68. 68 if (retryable) {
  69. 69 // RetryableRibbonLoadBalancingHttpClient 本身具备负载均衡的能力
  70. 70 response = client.execute(request, config);
  71. 71 } else {
  72. 72 // RibbonLoadBalancingHttpClient 需调用 executeWithLoadBalancer 才具备负载均衡的能力
  73. 73 response = client.executeWithLoadBalancer(request, config);
  74. 74 }
  75. 75
  76. 76 return new RibbonHttpResponse(response);
  77. 77 } catch (Exception e) {
  78. 78 throw new IOException(e);
  79. 79 }
  80. 80 }
  81. 81 }

b) OkHttpClientHttpRequestFactory

  1. 1 package com.lyyzoo.sunny.register.ribbon.okhttp;
  2. 2
  3. 3 import java.io.IOException;
  4. 4 import java.net.URI;
  5. 5
  6. 6 import com.netflix.client.config.IClientConfig;
  7. 7 import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
  8. 8 import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient;
  9. 9 import org.springframework.http.HttpMethod;
  10. 10 import org.springframework.http.client.ClientHttpRequest;
  11. 11 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
  12. 12 import org.springframework.lang.NonNull;
  13. 13
  14. 14 /**
  15. 15 * OkHttp ClientHttpRequest factory
  16. 16 *
  17. 17 * @author bojiangzhou
  18. 18 */
  19. 19 public class OkHttpClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
  20. 20
  21. 21 private final SpringClientFactory clientFactory;
  22. 22 private final boolean retryable;
  23. 23
  24. 24 public OkHttpClientHttpRequestFactory(SpringClientFactory clientFactory, boolean retryable) {
  25. 25 this.clientFactory = clientFactory;
  26. 26 this.retryable = retryable;
  27. 27 }
  28. 28
  29. 29 @Override
  30. 30 @NonNull
  31. 31 public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException {
  32. 32 String serviceId = originalUri.getHost();
  33. 33 if (serviceId == null) {
  34. 34 throw new IOException(
  35. 35 "Invalid hostname in the URI [" + originalUri.toASCIIString() + "]");
  36. 36 }
  37. 37 IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
  38. 38 OkHttpLoadBalancingClient httpClient = this.clientFactory.getClient(serviceId, OkHttpLoadBalancingClient.class);
  39. 39
  40. 40 return new OkHttpClientHttpRequest(originalUri, httpMethod, serviceId, httpClient, clientConfig, retryable);
  41. 41 }
  42. 42 }

c) OkHttpClientHttpRequestFactoryConfiguration

  1. 1 package com.lyyzoo.sunny.register.ribbon.okhttp;
  2. 2
  3. 3 import org.springframework.beans.factory.annotation.Autowired;
  4. 4 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  5. 5 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
  6. 6 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  7. 7 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  8. 8 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
  9. 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  10. 10 import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
  11. 11 import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
  12. 12 import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
  13. 13 import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
  14. 14 import org.springframework.context.annotation.Bean;
  15. 15 import org.springframework.context.annotation.Configuration;
  16. 16 import org.springframework.web.client.RestTemplate;
  17. 17
  18. 18 /**
  19. 19 *
  20. 20 * @author bojiangzhou
  21. 21 */
  22. 22 @Configuration
  23. 23 @ConditionalOnClass(RestTemplate.class)
  24. 24 @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
  25. 25 @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
  26. 26 @ConditionalOnProperty(name = "ribbon.okhttp.restTemplate.enabled", matchIfMissing = true)
  27. 27 public class OkHttpClientHttpRequestFactoryConfiguration {
  28. 28
  29. 29 @Configuration(proxyBeanMethods = false)
  30. 30 @ConditionalOnProperty("ribbon.okhttp.enabled")
  31. 31 @ConditionalOnClass(name = "okhttp3.OkHttpClient")
  32. 32 static class ClientHttpRequestFactoryConfiguration {
  33. 33
  34. 34 @Autowired
  35. 35 private SpringClientFactory springClientFactory;
  36. 36
  37. 37 @Bean
  38. 38 @ConditionalOnMissingBean
  39. 39 public RestTemplateCustomizer restTemplateCustomizer(
  40. 40 final OkHttpClientHttpRequestFactory okHttpClientHttpRequestFactory) {
  41. 41 return restTemplate -> restTemplate
  42. 42 .setRequestFactory(okHttpClientHttpRequestFactory);
  43. 43 }
  44. 44
  45. 45 @Bean
  46. 46 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  47. 47 public OkHttpClientHttpRequestFactory okHttpClientHttpRequestFactory() {
  48. 48 return new OkHttpClientHttpRequestFactory(springClientFactory, false);
  49. 49 }
  50. 50
  51. 51 @Bean
  52. 52 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
  53. 53 public OkHttpClientHttpRequestFactory retryableOkHttpClientHttpRequestFactory() {
  54. 54 return new OkHttpClientHttpRequestFactory(springClientFactory, true);
  55. 55 }
  56. 56 }
  57. 57 }

七、RestTemplate 超时重试

1、AbstractLoadBalancerAwareClient

① AbstractLoadBalancerAwareClient

通过上一节的分析,可以知道有重试功能的其实有两个组件,一个是 Ribbon 的 LoadBalancerCommand,一个是 spring-retry 的 RetryTemplate。RetryableRibbonLoadBalancingHttpClient 和 RetryableOkHttpLoadBalancingClient 都要依赖 RetryTemplate,所以必须先引入 spring-retry 依赖,它们最终都是使用 RetryTemplate 实现请求重试的能力的。除了 RetryTemplate,其它客户端想要获取重试的功能,就要用 ribbon 中的 AbstractLoadBalancerAwareClient 相关的组件,并调用 executeWithLoadBalancer 方法。

再看下 AbstractLoadBalancerAwareClient 的体系,通过源码可以了解到:

  • RetryableFeignLoadBalancer、RetryableRibbonLoadBalancingHttpClient、RetryableOkHttpLoadBalancingClient 都是使用 RetryTemplate 实现重试功能的,也就是 spring-retry 的重试。
  • RestClient、FeignLoadBalancer、RibbonLoadBalancingHttpClient、OkHttpLoadBalancingClient 是在 AbstractLoadBalancerAwareClient 中使用 LoadBalancerCommand 实现重试功能的,就是是 Ribbon 的重试。

② executeWithLoadBalancer

具体的 AbstractLoadBalancerAwareClient 客户端想要负载均衡调用以及能进行重试,需调用 AbstractLoadBalancerAwareClient 的 executeWithLoadBalancer 方法。

在这个方法里面,它先构建了 LoadBalancerCommand,然后用 command 提交了一个 ServerOperation,这个 ServerOperation 中对 URI 进行了 重构,转到具体的 LoadBalancerContext 去执行请求。

  1. 1 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
  2. 2 // 负载均衡命令
  3. 3 LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
  4. 4
  5. 5 try {
  6. 6 return command.submit(
  7. 7 new ServerOperation<T>() {
  8. 8 @Override
  9. 9 public Observable<T> call(Server server) {
  10. 10 // 重构URI
  11. 11 URI finalUri = reconstructURIWithServer(server, request.getUri());
  12. 12 S requestForServer = (S) request.replaceUri(finalUri);
  13. 13 try {
  14. 14 // 使用具体的 AbstractLoadBalancerAwareClient 客户端执行请求
  15. 15 return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
  16. 16 }
  17. 17 catch (Exception e) {
  18. 18 return Observable.error(e);
  19. 19 }
  20. 20 }
  21. 21 })
  22. 22 .toBlocking()
  23. 23 .single();
  24. 24 }
  25. 25 }

再看 buildLoadBalancerCommand 方法,它首先会通过 getRequestSpecificRetryHandler 方法获取请求重试处理器 RequestSpecificRetryHandler,而 getRequestSpecificRetryHandler 是一个抽象方法。这里就要重点注意了。

  1. 1 // 抽象方法,获取请求重试处理器
  2. 2 public abstract RequestSpecificRetryHandler getRequestSpecificRetryHandler(S request, IClientConfig requestConfig);
  3. 3
  4. 4 protected LoadBalancerCommand<T> buildLoadBalancerCommand(final S request, final IClientConfig config) {
  5. 5 // 获取请求重试处理器
  6. 6 RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config);
  7. 7 LoadBalancerCommand.Builder<T> builder = LoadBalancerCommand.<T>builder()
  8. 8 .withLoadBalancerContext(this)
  9. 9 .withRetryHandler(handler)
  10. 10 .withLoadBalancerURI(request.getUri());
  11. 11 customizeLoadBalancerCommandBuilder(request, config, builder);
  12. 12 return builder.build();
  13. 13 }

2、请求重试处理器 RequestSpecificRetryHandler

① 先了解下 RequestSpecificRetryHandler:

  • 首先看它的构造方法,注意第一个参数和第二个参数,因为不同的 getRequestSpecificRetryHandler 方法实现,主要差异就在于这两个参数。
  • 然后看 isRetriableException,这个方法就是 LoadBalancerCommand 用来判断异常后是否需要重试的方法,可以了解到 okToRetryOnAllErrors=true 时就可以重试,否则 okToRetryOnConnectErrors=true 才可能重试。需要注意的是就算这个方法返回 true 也不一定会重试,这跟重试次数也是有一定关系的。
  1. 1 public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) {
  2. 2 Preconditions.checkNotNull(baseRetryHandler);
  3. 3 this.okToRetryOnConnectErrors = okToRetryOnConnectErrors;
  4. 4 this.okToRetryOnAllErrors = okToRetryOnAllErrors;
  5. 5 this.fallback = baseRetryHandler;
  6. 6 if (requestConfig != null) {
  7. 7 // 在同一个Server上重试的次数
  8. 8 if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetries)) {
  9. 9 retrySameServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetries);
  10. 10 }
  11. 11 // 重试下一个Server的次数
  12. 12 if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetriesNextServer)) {
  13. 13 retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer);
  14. 14 }
  15. 15 }
  16. 16 }
  17. 17
  18. 18 @Override
  19. 19 public boolean isRetriableException(Throwable e, boolean sameServer) {
  20. 20 // 所有错误都重试
  21. 21 if (okToRetryOnAllErrors) {
  22. 22 return true;
  23. 23 }
  24. 24 // ClientException 才可能重试
  25. 25 else if (e instanceof ClientException) {
  26. 26 ClientException ce = (ClientException) e;
  27. 27 if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
  28. 28 return !sameServer;
  29. 29 } else {
  30. 30 return false;
  31. 31 }
  32. 32 }
  33. 33 else {
  34. 34 // 连接错误才重试,就是抛出 SocketException 异常时才重试
  35. 35 return okToRetryOnConnectErrors && isConnectionException(e);
  36. 36 }
  37. 37 }

② 不同 LoadBalancerCommand 的 getRequestSpecificRetryHandler 实现

a)RestClient

默认配置下,RestClient 的 getRequestSpecificRetryHandler 会走到最后一步,okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,也就是说 isRetriableException 始终返回 true,也就是说抛出异常都会重试。

  1. 1 @Override
  2. 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
  3. 3 HttpRequest request, IClientConfig requestConfig) {
  4. 4 if (!request.isRetriable()) {
  5. 5 return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig);
  6. 6 }
  7. 7 if (this.ncc.get(CommonClientConfigKey.OkToRetryOnAllOperations, false)) {
  8. 8 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
  9. 9 }
  10. 10 if (request.getVerb() != HttpRequest.Verb.GET) {
  11. 11 return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig);
  12. 12 } else {
  13. 13 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true
  14. 14 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
  15. 15 }
  16. 16 }

b)AbstractLoadBalancingClient

AbstractLoadBalancingClient 中的 getRequestSpecificRetryHandler 相当于一个默认实现,默认情况下 okToRetryOnAllOperations 为 false,最后也会到最后一步,即 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,isRetriableException 始终返回 true。

  1. 1 @Override
  2. 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(final S request, final IClientConfig requestConfig) {
  3. 3 // okToRetryOnAllOperations:是否所有操作都重试,默认 false
  4. 4 if (this.okToRetryOnAllOperations) {
  5. 5 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
  6. 6 }
  7. 7 if (!request.getContext().getMethod().equals("GET")) {
  8. 8 return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig);
  9. 9 }
  10. 10 else {
  11. 11 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
  12. 12 }
  13. 13 }

c)RibbonLoadBalancingHttpClient

RibbonLoadBalancingHttpClient 也重载了 getRequestSpecificRetryHandler,但是它设置了 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false,isRetriableException 始终返回 false。

至此我们应该就知道为什么调用 RibbonLoadBalancingHttpClient 的 executeWithLoadBalancer 不具备重试的功能的原因了。所以启用 apache httpclient 时,RibbonLoadBalancingHttpClient 调用是不支持重试的。

  1. 1 @Override
  2. 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
  3. 3 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
  4. 4 return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, requestConfig);
  5. 5 }

RetryableRibbonLoadBalancingHttpClient 中也重写了 getRequestSpecificRetryHandler,同样也是设置 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false。但是在引入 spring-retry 后,它会使用 RetryTemplate 实现重试的功能。

  1. 1 @Override
  2. 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
  3. 3 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
  4. 4 return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
  5. 5 }

d)OkHttpLoadBalancingClient

OkHttpLoadBalancingClient 并没有重写 getRequestSpecificRetryHandler,所以它是使用父类 AbstractLoadBalancingClient 中的方法,也就是 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true。所以,启用 okhttp 时,OkHttpLoadBalancingClient 是支持重试的,这里需要注意。

而 RetryableOkHttpLoadBalancingClient 跟 RetryableRibbonLoadBalancingHttpClient 一样的重写方式,使用 RetryTemplate 实现重试。

  1. 1 @Override
  2. 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
  3. 3 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
  4. 4 return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
  5. 5 }

3、LoadBalancerCommand

看 LoadBalancerCommand 的 submit 方法,这个方法是重试的核心代码。

  • 首先获取了同一个Server重试次数 maxRetrysSame 和 重试下一个Server的次数 maxRetrysNext,其实就是前面配置的 ribbon.MaxAutoRetries 和 ribbon.MaxAutoRetriesNextServer,我设置的是 1。
  • 然后创建了一个 Observable,它的第一层会先通过 loadBalancerContext 获取 Server。在重试下一个 Server 时,这里就会获取下一个 Server。
  • 在第二层,又创建了一个 Observable,这个 Observable 就是调用 ServerOperation 的,就是重构 URI,调用具体的 AbstractLoadBalancerAwareClient 执行请求。
  • 在第二层里,会根据 maxRetrysSame 重试同一个 Server,从 retryPolicy 中可以了解到,当重试次数大于 maxRetrysSame 后,同一个 Server 重试就结束了,否则就用 retryHandler.isRetriableException 判断是否重试,这个前面已经分析过了。
  • 在外层,则根据 maxRetrysNext 重试不同的 Server,从 retryPolicy 中可以了解到,当不同Server重试次数大于 maxRetrysNext 后,就重试结束了,整个重试也就结束了,如果还是失败,就会进入 onErrorResumeNext 进行最后的失败处理。

最后来总结一下 LoadBalancerCommand 重试:

  • 重试分为同一个 Server 重试和重试下一个Server,当重试次数大于设置的重试值时,就停止重试。否则通过 retryHandler.isRetriableException 判断是否重试。
  • 那这里一共请求了多少次呢?可以总结出如下公式:请求次数 = (maxRetrysSame + 1) * (maxRetrysNext + 1),所以按 ribbon.MaxAutoRetries = 1、ribbon.MaxAutoRetriesNextServer = 1 的配置,如果每次请求都超时,就会发起 4 次请求。
  1. 1 public Observable<T> submit(final ServerOperation<T> operation) {
  2. 2 final ExecutionInfoContext context = new ExecutionInfoContext();
  3. 3
  4. 4 // 同一个Server重试次数
  5. 5 final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
  6. 6 // 重试下一个Server的次数
  7. 7 final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
  8. 8
  9. 9 // 创建一个 Observable
  10. 10 Observable<T> o =
  11. 11 // 使用 loadBalancerContext 获取 Server
  12. 12 (server == null ? selectServer() : Observable.just(server))
  13. 13 .concatMap(new Func1<Server, Observable<T>>() {
  14. 14 @Override
  15. 15 public Observable<T> call(Server server) {
  16. 16 // 设置Server
  17. 17 context.setServer(server);
  18. 18 final ServerStats stats = loadBalancerContext.getServerStats(server);
  19. 19
  20. 20 // 创建 Observable
  21. 21 Observable<T> o = Observable
  22. 22 .just(server)
  23. 23 .concatMap(new Func1<Server, Observable<T>>() {
  24. 24 @Override
  25. 25 public Observable<T> call(final Server server) {
  26. 26 // 增加尝试次数
  27. 27 context.incAttemptCount();
  28. 28 // ...
  29. 29 // 调用 ServerOperation
  30. 30 return operation.call(server).doOnEach(new Observer<T>() {
  31. 31 // 一些回调方法
  32. 32 });
  33. 33 }
  34. 34 });
  35. 35 // 重试同一个Server
  36. 36 if (maxRetrysSame > 0)
  37. 37 o = o.retry(retryPolicy(maxRetrysSame, true));
  38. 38 return o;
  39. 39 }
  40. 40 });
  41. 41
  42. 42 if (maxRetrysNext > 0 && server == null)
  43. 43 // 重试不同Server
  44. 44 o = o.retry(retryPolicy(maxRetrysNext, false));
  45. 45
  46. 46 return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
  47. 47 @Override
  48. 48 public Observable<T> call(Throwable e) {
  49. 49 // 异常处理
  50. 50 return Observable.error(e);
  51. 51 }
  52. 52 });
  53. 53 }
  54. 54
  55. 55 // retryPolicy 返回一个是否重试的断言
  56. 56 private Func2<Integer, Throwable, Boolean> retryPolicy(final int maxRetrys, final boolean same) {
  57. 57 return new Func2<Integer, Throwable, Boolean>() {
  58. 58 @Override
  59. 59 public Boolean call(Integer tryCount, Throwable e) {
  60. 60 // 请求拒绝异常就不允许重试
  61. 61 if (e instanceof AbortExecutionException) {
  62. 62 return false;
  63. 63 }
  64. 64 // 尝试次数是否大于最大重试次数
  65. 65 if (tryCount > maxRetrys) {
  66. 66 return false;
  67. 67 }
  68. 68 // 使用 RequestSpecificRetryHandler 判断是否重试
  69. 69 return retryHandler.isRetriableException(e, same);
  70. 70 }
  71. 71 };
  72. 72 }

4、RetryTemplate

① spring-retry

要启用 RetryTemplate 需先引入 spring-retry:

  1. 1 <dependency>
  2. 2 <groupId>org.springframework.retry</groupId>
  3. 3 <artifactId>spring-retry</artifactId>
  4. 4 </dependency>

以 RetryableRibbonLoadBalancingHttpClient 为例,先看看它的 execute 方法,它先创建了负载均衡重试策略类 LoadBalancedRetryPolicy,然后将请求调用的逻辑封装到 RetryCallback 中,最后其实就是用 RetryTemplate 执行这个 RetryCallback,也就是说请求重试的逻辑都在 RetryTemplate 中。

  1. 1 public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
  2. 2 //...
  3. 3
  4. 4 // 负载均衡重试策略 RibbonLoadBalancedRetryPolicy
  5. 5 final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this);
  6. 6
  7. 7 RetryCallback<RibbonApacheHttpResponse, Exception> retryCallback = context -> {
  8. 8 // ...
  9. 9 // delegate => CloseableHttpClient
  10. 10 final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
  11. 11 // ...
  12. 12 // 成功 返回结果
  13. 13 return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
  14. 14 };
  15. 15
  16. 16 LoadBalancedRecoveryCallback<RibbonApacheHttpResponse, HttpResponse> recoveryCallback = new LoadBalancedRecoveryCallback<RibbonApacheHttpResponse, HttpResponse>()//...
  17. 17
  18. 18 return this.executeWithRetry(request, retryPolicy, retryCallback, recoveryCallback);
  19. 19 }
  20. 20
  21. 21 private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request,
  22. 22 LoadBalancedRetryPolicy retryPolicy,
  23. 23 RetryCallback<RibbonApacheHttpResponse, Exception> callback,
  24. 24 RecoveryCallback<RibbonApacheHttpResponse> recoveryCallback)
  25. 25 throws Exception {
  26. 26 RetryTemplate retryTemplate = new RetryTemplate();
  27. 27
  28. 28 // retryable => 取自 RibbonCommandContext 设置的 retryable 参数
  29. 29 boolean retryable = isRequestRetryable(request);
  30. 30 // 设置重试策略
  31. 31 retryTemplate.setRetryPolicy(retryPolicy == null || !retryable
  32. 32 ? new NeverRetryPolicy() : new RetryPolicy(request, retryPolicy, this, this.getClientName()));
  33. 33
  34. 34 BackOffPolicy backOffPolicy = loadBalancedRetryFactory.createBackOffPolicy(this.getClientName());
  35. 35 retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
  36. 36
  37. 37 // 利用 retryTemplate 执行请求 callback
  38. 38 return retryTemplate.execute(callback, recoveryCallback);
  39. 39 }

需要注意的是,在 executeWithRetry 中,会判断是否要重试,判断的逻辑中 getRetryable 其实就是取的 ApacheClientHttpRequest 中 executeInternal 方法里创建的 RibbonCommandContext 设置的 retryable 参数,这就和前面定制化的逻辑衔接上了。

  1. 1 private boolean isRequestRetryable(ContextAwareRequest request) {
  2. 2 if (request.getContext() == null || request.getContext().getRetryable() == null) {
  3. 3 return true;
  4. 4 }
  5. 5 return request.getContext().getRetryable();
  6. 6 }

② RetryTemplate

进入 RetryTemplate 的 execute 方法,核心的逻辑我精简成如下代码,主要就是一个 while 循环判断是否可以重试,然后调用 retryCallback 执行请求。请求失败后,比如超时,抛出异常,就会 registerThrowable 来注册异常。

  1. 1 protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
  2. 2 RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {
  3. 3
  4. 4 // retryPolicy => InterceptorRetryPolicy
  5. 5 RetryPolicy retryPolicy = this.retryPolicy;
  6. 6 BackOffPolicy backOffPolicy = this.backOffPolicy;
  7. 7 //....
  8. 8 try {
  9. 9 // ...
  10. 10 // canRetry 判断是否重试
  11. 11 while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
  12. 12 try {
  13. 13 // retryCallback 调用
  14. 14 return retryCallback.doWithRetry(context);
  15. 15 }
  16. 16 catch (Throwable e) {
  17. 17 // ...
  18. 18 // 注册异常
  19. 19 registerThrowable(retryPolicy, state, context, e);
  20. 20 // ...
  21. 21 }
  22. 22 }
  23. 23 exhausted = true;
  24. 24 return handleRetryExhausted(recoveryCallback, context, state);
  25. 25 }
  26. 26 //...
  27. 27 }

看 canRetry 方法,它实际是调用了 InterceptorRetryPolicy 的 canRetry。第一次调用时,会去获取 Server;否则就用 RibbonLoadBalancedRetryPolicy 判断是否重试下一个 Server,注意它判断的逻辑是 GET 请求或者允许所有操作操作重试,且 Server 重试次数 nextServerCount  小于等于配置的 MaxAutoRetriesNextServer 。也就是说,while 循环判断的 canRetry 是重试下一个 Server 的。

  1. 1 protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
  2. 2 return retryPolicy.canRetry(context);
  3. 3 }
  4. 4
  5. 5 //////////// InterceptorRetryPolicy
  6. 6 public boolean canRetry(RetryContext context) {
  7. 7 LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
  8. 8 if (lbContext.getRetryCount() == 0 && lbContext.getServiceInstance() == null) {
  9. 9 // 获取 Server
  10. 10 lbContext.setServiceInstance(this.serviceInstanceChooser.choose(this.serviceName));
  11. 11 return true;
  12. 12 }
  13. 13 // RibbonLoadBalancedRetryPolicy => 重试下一个Server
  14. 14 return this.policy.canRetryNextServer(lbContext);
  15. 15 }
  16. 16
  17. 17 ///////// RibbonLoadBalancedRetryPolicy
  18. 18 public boolean canRetryNextServer(LoadBalancedRetryContext context) {
  19. 19 // 判断重试下一个Server
  20. 20 return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer()
  21. 21 && canRetry(context);
  22. 22 }
  23. 23
  24. 24 public boolean canRetry(LoadBalancedRetryContext context) {
  25. 25 // GET 请求或者允许所有操作重试时,就允许重试
  26. 26 HttpMethod method = context.getRequest().getMethod();
  27. 27 return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
  28. 28 }

接着看请求失败后的注册异常 registerThrowable,它最后会向 RibbonLoadBalancedRetryPolicy 注册异常。在 RibbonLoadBalancedRetryPolicy 的 registerThrowable 方法中,如果不重试同一个Server且可以重试下一个Server,就会轮询获取下一个Server。如果可以在同一个Server上重试,sameServerCount 计数器就+1,否则重置 sameServerCount,然后 nextServerCount +1。

  1. 1 protected void registerThrowable(RetryPolicy retryPolicy, RetryState state,
  2. 2 RetryContext context, Throwable e) {
  3. 3 retryPolicy.registerThrowable(context, e);
  4. 4 registerContext(context, state);
  5. 5 }
  6. 6
  7. 7 ///////// InterceptorRetryPolicy /////////
  8. 8 public void registerThrowable(RetryContext context, Throwable throwable) {
  9. 9 LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
  10. 10 lbContext.registerThrowable(throwable);
  11. 11 // RibbonLoadBalancedRetryPolicy
  12. 12 this.policy.registerThrowable(lbContext, throwable);
  13. 13 }
  14. 14
  15. 15 ///////// RibbonLoadBalancedRetryPolicy /////////
  16. 16 public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) {
  17. 17 //...
  18. 18 // 如果不在在同一个Server 上重试且可以重试下一个Server,则重新选择一个 Server
  19. 19 if (!canRetrySameServer(context) && canRetryNextServer(context)) {
  20. 20 context.setServiceInstance(loadBalanceChooser.choose(serviceId));
  21. 21 }
  22. 22
  23. 23 // 同一个Server重试超过设置的值后,就重置 sameServerCount
  24. 24 if (sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer()
  25. 25 && canRetry(context)) {
  26. 26 // 重置 nextServerCount
  27. 27 sameServerCount = 0;
  28. 28 // 下一个Server重试次数+1
  29. 29 nextServerCount++;
  30. 30 if (!canRetryNextServer(context)) {
  31. 31 // 不能重试下一个Server了
  32. 32 context.setExhaustedOnly();
  33. 33 }
  34. 34 }
  35. 35 else {
  36. 36 // 同一个Server重试次数+1
  37. 37 sameServerCount++;
  38. 38 }
  39. 39 }
  40. 40
  41. 41 // 判断是否重试同一个Server
  42. 42 public boolean canRetrySameServer(LoadBalancedRetryContext context) {
  43. 43 return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer()
  44. 44 && canRetry(context);
  45. 45 }
  46. 46
  47. 47 public boolean canRetry(LoadBalancedRetryContext context) {
  48. 48 // GET 请求或者允许所有操作重试时,就允许重试
  49. 49 HttpMethod method = context.getRequest().getMethod();
  50. 50 return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
  51. 51 }

5、Ribbon 重试总结

① 首先,Ribbon 关于超时和重试的配置参数如下,这些参数也可以针对某个客户端配置:

  1. 1 ribbon:
  2. 2 # 客户端读取超时时间
  3. 3 ReadTimeout: 1000
  4. 4 # 客户端连接超时时间
  5. 5 ConnectTimeout: 1000
  6. 6 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  7. 7 OkToRetryOnAllOperations: false
  8. 8 # 同一个Server重试次数
  9. 9 MaxAutoRetries: 1
  10. 10 # 最多重试几个Server
  11. 11 MaxAutoRetriesNextServer: 1

② RetryTemplate 是 spring-retry 的重试组件,LoadBalancerCommand 是 Ribbon 的重试组件。它们重试的请求次数是一样的,重试逻辑也是类似,都是先重试当前 Server,再重试下一个Server,总的请求次数 = (MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)。

③ 但是有点差别的是,RetryTemplate 会判断请求方法为 GET 或者 OkToRetryOnAllOperations=true 时才允许重试,而 LoadBalancerCommand  是所有 http 方法都可以重试。这个其实是有问题的,一般只有GET才允许重试,因为GET是查询操作,接口是幂等的,而POST、PUT、DELETE一般是非幂等的。所以一般更建议使用 RetryTemplate,并且配置 OkToRetryOnAllOperations=false。

④ 为了提升服务间通信性能,一般可以启用 apache httpclient 或者 OkHttp,如果要启用重试功能,还需要引入 spring-retry 依赖。重试时,当前Server就不要重试了(MaxAutoRetries=0),直接重试下一个Server。

  1. 1 ribbon:
  2. 2 # 客户端读取超时时间
  3. 3 ReadTimeout: 1000
  4. 4 # 客户端连接超时时间
  5. 5 ConnectTimeout: 1000
  6. 6 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  7. 7 OkToRetryOnAllOperations: false
  8. 8 # 同一个Server重试次数
  9. 9 MaxAutoRetries: 0
  10. 10 # 最多重试几个Server
  11. 11 MaxAutoRetriesNextServer: 1
  12. 12 # 启用 httpclient
  13. 13 httpclient:
  14. 14 enabled: false
  15. 15 # 启用 RestClient
  16. 16 restclient:
  17. 17 enabled: false
  18. 18 # 启用 okhttp
  19. 19 okhttp:
  20. 20 enabled: true

SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)的更多相关文章

  1. SpringCloud 源码系列(6)—— 声明式服务调用 Feign

    SpringCloud 源码系列(1)-- 注册中心 Eureka(上) SpringCloud 源码系列(2)-- 注册中心 Eureka(中) SpringCloud 源码系列(3)-- 注册中心 ...

  2. SpringCloud 源码系列(4)—— 负载均衡 Ribbon

    一.负载均衡 1.RestTemplate 在研究 eureka 源码上篇中,我们在 demo-consumer 消费者服务中定义了用 @LoadBalanced 标记的 RestTemplate,然 ...

  3. SpringCloud之实现客户端的负载均衡Ribbon(二)

    一 Ribbon简介 Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为.为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务 ...

  4. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

  5. SpringCloud 源码系列(1)—— 注册中心 Eureka(上)

    Eureka 是 Netflix 公司开源的一个服务注册与发现的组件,和其他 Netflix 公司的服务组件(例如负载均衡.熔断器.网关等)一起,被 Spring Cloud 整合为 Spring C ...

  6. SpringCloud 源码系列(3)—— 注册中心 Eureka(下)

    十一.Eureka Server 集群 在实际的生产环境中,可能有几十个或者几百个的微服务实例,Eureka Server 承担了非常高的负载,而且为了保证注册中心高可用,一般都要部署成集群的,下面就 ...

  7. dubbo源码解析之负载均衡

    在分布式系统中,负载均衡是必不可少的一个模块,dubbo 中提供了五种负载均衡的实现,在阅读这块源码之前,建议先学习负载均衡的基础知识.把看源码当做一个印证自己心中所想的过程,这样会得到事半功倍的效果 ...

  8. dubbo源码分析1——负载均衡

    dubbo中涉及到的负载均衡算法只要有四种:Random LoadBalance(随机均衡算法).RoundRobin LoadBalance(权重轮循均衡算法).LeastAction LoadBa ...

  9. dubbo源码阅读之负载均衡

    负载均衡 在之前集群的文章中,我们分析了通过监听注册中心可以获取到多个服务提供者,并创建多个Invoker,然后通过集群类如FailoverClusterInvoker将多个Invoker封装在一起, ...

随机推荐

  1. C语言讲义——错误处理

    errno C语言不提供对错误处理的直接支持. 以返回值的形式表示是否出错. 在发生错误时,大多数的C函数调用返回1或NULL. 同时设置一个错误代码errno(全局变量),表示在函数调用期间发生了错 ...

  2. Java基础教程——垃圾回收机制

    垃圾回收机制 Garbage Collection,GC 垃圾回收是Java的重要功能之一. |--堆内存:垃圾回收机制只回收堆内存中对象,不回收数据库连接.IO等物理资源. |--失去使用价值,即为 ...

  3. 移动端和web端的性能指标

    移动端的性能指标: 1.内存:80% 2.CPU 3.流量 4.电量 5.启动速度 6.滑动速度.界面切换速度 7.与服务器交互的网络速度 web端的性能指标: 1.CPU 2.内存 3.网络 4.I ...

  4. Java中的Set对象去重

    前言部分 Set<T> 去重相信大家一定不陌生,尤其是在 Set<String>.Set<Integer> 等等,但是在使用 Set<实体> ,在不重写 ...

  5. day4(编写注册接口)

    1.编写注册接口 1.1 user/urls.py中添加路由 urlpatterns = [    path('register/', views.RegisterView.as_view()),  ...

  6. PyQt(Python+Qt)学习随笔:QTabWidget部件信号简介

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTabWidget自身提供的信号包括如下: currentChanged(int index):每 ...

  7. PyQt(Python+Qt)学习随笔:调用disconnect进行信号连接断开时的信号签名与断开参数的匹配要求

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在使用信号调用disconnect()方法断开信号和槽的连接时,信号可以带签名也可不带签名,参数可以 ...

  8. 凌乱的与ctf无关的小知识点

    (1)在网页中一般不要用记住密码.否则想要知道你的密码很简单. 例子:看样子很安全,别人无法通过这些来获得你的其他密码(尤其是想我这样密码强度不高的人),但是知道要修改前端的选项,你的密码就会被暴露. ...

  9. 3、pytorch实现最基础的MLP网络

    %matplotlib inline import numpy as np import torch from torch import nn import matplotlib.pyplot as ...

  10. Hangfire&Autofac与ASP.NET CORE注入失败

    Hangfire.Autofac与ASP.NET CORE注入失败 项目里面使用了Hangfire,因为之前没用过吧,遇到了个问题,就是使用了ico容器后,再用Hangfire总是注入不上对象,总是后 ...