SpringCloud 源码系列(1)—— 注册中心 Eureka(上)
SpringCloud 源码系列(2)—— 注册中心 Eureka(中)
SpringCloud 源码系列(3)—— 注册中心 Eureka(下)
SpringCloud 源码系列(4)—— 负载均衡 Ribbon(上)
SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)
SpringCloud 源码系列(6)—— 声明式服务调用 Feign
五、Ribbon 核心接口
前面已经了解到 Ribbon 核心接口以及默认实现如何协作来查找要调用的一个实例,这节再来看下各个核心接口的一些特性及其它实现类。
1、客户端配置 — IClientConfig
IClientConfig 就是管理客户端配置的核心接口,它的默认实现类是 DefaultClientConfigImpl。可以看到在创建 IClientConfig 时,设置了 Ribbon 客户端默认的连接和读取超时时间为 1 秒,例如读取如果超过1秒,就会返回超时,这两个一般需要根据实际情况来调整。、
1 @Bean 2 @ConditionalOnMissingBean 3 public IClientConfig ribbonClientConfig() { 4 DefaultClientConfigImpl config = new DefaultClientConfigImpl(); 5 // 加载配置 6 config.loadProperties(this.name); 7 // 连接超时默认 1 秒 8 config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT); 9 // 读取超时默认 1 秒 10 config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT); 11 config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD); 12 return config; 13 }
CommonClientConfigKey 这个类定义了 Ribbon 客户端相关的所有配置的键常量,可以通过这个类来看有哪些配置。
1 public abstract class CommonClientConfigKey<T> implements IClientConfigKey<T> { 2 3 public static final IClientConfigKey<String> AppName = new CommonClientConfigKey<String>("AppName"){}; 4 5 public static final IClientConfigKey<String> Version = new CommonClientConfigKey<String>("Version"){}; 6 7 public static final IClientConfigKey<Integer> Port = new CommonClientConfigKey<Integer>("Port"){}; 8 9 public static final IClientConfigKey<Integer> SecurePort = new CommonClientConfigKey<Integer>("SecurePort"){}; 10 11 public static final IClientConfigKey<String> VipAddress = new CommonClientConfigKey<String>("VipAddress"){}; 12 13 public static final IClientConfigKey<Boolean> ForceClientPortConfiguration = new CommonClientConfigKey<Boolean>("ForceClientPortConfiguration"){}; // use client defined port regardless of server advert 14 15 public static final IClientConfigKey<String> DeploymentContextBasedVipAddresses = new CommonClientConfigKey<String>("DeploymentContextBasedVipAddresses"){}; 16 17 public static final IClientConfigKey<Integer> MaxAutoRetries = new CommonClientConfigKey<Integer>("MaxAutoRetries"){}; 18 19 public static final IClientConfigKey<Integer> MaxAutoRetriesNextServer = new CommonClientConfigKey<Integer>("MaxAutoRetriesNextServer"){}; 20 21 public static final IClientConfigKey<Boolean> OkToRetryOnAllOperations = new CommonClientConfigKey<Boolean>("OkToRetryOnAllOperations"){}; 22 23 public static final IClientConfigKey<Boolean> RequestSpecificRetryOn = new CommonClientConfigKey<Boolean>("RequestSpecificRetryOn"){}; 24 25 public static final IClientConfigKey<Integer> ReceiveBufferSize = new CommonClientConfigKey<Integer>("ReceiveBufferSize"){}; 26 27 public static final IClientConfigKey<Boolean> EnablePrimeConnections = new CommonClientConfigKey<Boolean>("EnablePrimeConnections"){}; 28 29 public static final IClientConfigKey<String> PrimeConnectionsClassName = new CommonClientConfigKey<String>("PrimeConnectionsClassName"){}; 30 31 public static final IClientConfigKey<Integer> MaxRetriesPerServerPrimeConnection = new CommonClientConfigKey<Integer>("MaxRetriesPerServerPrimeConnection"){}; 32 33 public static final IClientConfigKey<Integer> MaxTotalTimeToPrimeConnections = new CommonClientConfigKey<Integer>("MaxTotalTimeToPrimeConnections"){}; 34 35 public static final IClientConfigKey<Float> MinPrimeConnectionsRatio = new CommonClientConfigKey<Float>("MinPrimeConnectionsRatio"){}; 36 37 public static final IClientConfigKey<String> PrimeConnectionsURI = new CommonClientConfigKey<String>("PrimeConnectionsURI"){}; 38 39 public static final IClientConfigKey<Integer> PoolMaxThreads = new CommonClientConfigKey<Integer>("PoolMaxThreads"){}; 40 41 public static final IClientConfigKey<Integer> PoolMinThreads = new CommonClientConfigKey<Integer>("PoolMinThreads"){}; 42 43 public static final IClientConfigKey<Integer> PoolKeepAliveTime = new CommonClientConfigKey<Integer>("PoolKeepAliveTime"){}; 44 45 public static final IClientConfigKey<String> PoolKeepAliveTimeUnits = new CommonClientConfigKey<String>("PoolKeepAliveTimeUnits"){}; 46 47 public static final IClientConfigKey<Boolean> EnableConnectionPool = new CommonClientConfigKey<Boolean>("EnableConnectionPool") {}; 48 49 /** 50 * Use {@link #MaxConnectionsPerHost} 51 */ 52 @Deprecated 53 public static final IClientConfigKey<Integer> MaxHttpConnectionsPerHost = new CommonClientConfigKey<Integer>("MaxHttpConnectionsPerHost"){}; 54 55 /** 56 * Use {@link #MaxTotalConnections} 57 */ 58 @Deprecated 59 public static final IClientConfigKey<Integer> MaxTotalHttpConnections = new CommonClientConfigKey<Integer>("MaxTotalHttpConnections"){}; 60 61 public static final IClientConfigKey<Integer> MaxConnectionsPerHost = new CommonClientConfigKey<Integer>("MaxConnectionsPerHost"){}; 62 63 public static final IClientConfigKey<Integer> MaxTotalConnections = new CommonClientConfigKey<Integer>("MaxTotalConnections"){}; 64 65 public static final IClientConfigKey<Boolean> IsSecure = new CommonClientConfigKey<Boolean>("IsSecure"){}; 66 67 public static final IClientConfigKey<Boolean> GZipPayload = new CommonClientConfigKey<Boolean>("GZipPayload"){}; 68 69 public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout"){}; 70 71 public static final IClientConfigKey<Integer> BackoffInterval = new CommonClientConfigKey<Integer>("BackoffTimeout"){}; 72 73 public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout"){}; 74 75 public static final IClientConfigKey<Integer> SendBufferSize = new CommonClientConfigKey<Integer>("SendBufferSize"){}; 76 77 public static final IClientConfigKey<Boolean> StaleCheckingEnabled = new CommonClientConfigKey<Boolean>("StaleCheckingEnabled"){}; 78 79 public static final IClientConfigKey<Integer> Linger = new CommonClientConfigKey<Integer>("Linger"){}; 80 81 public static final IClientConfigKey<Integer> ConnectionManagerTimeout = new CommonClientConfigKey<Integer>("ConnectionManagerTimeout"){}; 82 83 public static final IClientConfigKey<Boolean> FollowRedirects = new CommonClientConfigKey<Boolean>("FollowRedirects"){}; 84 85 public static final IClientConfigKey<Boolean> ConnectionPoolCleanerTaskEnabled = new CommonClientConfigKey<Boolean>("ConnectionPoolCleanerTaskEnabled"){}; 86 87 public static final IClientConfigKey<Integer> ConnIdleEvictTimeMilliSeconds = new CommonClientConfigKey<Integer>("ConnIdleEvictTimeMilliSeconds"){}; 88 89 public static final IClientConfigKey<Integer> ConnectionCleanerRepeatInterval = new CommonClientConfigKey<Integer>("ConnectionCleanerRepeatInterval"){}; 90 91 public static final IClientConfigKey<Boolean> EnableGZIPContentEncodingFilter = new CommonClientConfigKey<Boolean>("EnableGZIPContentEncodingFilter"){}; 92 93 public static final IClientConfigKey<String> ProxyHost = new CommonClientConfigKey<String>("ProxyHost"){}; 94 95 public static final IClientConfigKey<Integer> ProxyPort = new CommonClientConfigKey<Integer>("ProxyPort"){}; 96 97 public static final IClientConfigKey<String> KeyStore = new CommonClientConfigKey<String>("KeyStore"){}; 98 99 public static final IClientConfigKey<String> KeyStorePassword = new CommonClientConfigKey<String>("KeyStorePassword"){}; 100 101 public static final IClientConfigKey<String> TrustStore = new CommonClientConfigKey<String>("TrustStore"){}; 102 103 public static final IClientConfigKey<String> TrustStorePassword = new CommonClientConfigKey<String>("TrustStorePassword"){}; 104 105 // if this is a secure rest client, must we use client auth too? 106 public static final IClientConfigKey<Boolean> IsClientAuthRequired = new CommonClientConfigKey<Boolean>("IsClientAuthRequired"){}; 107 108 public static final IClientConfigKey<String> CustomSSLSocketFactoryClassName = new CommonClientConfigKey<String>("CustomSSLSocketFactoryClassName"){}; 109 // must host name match name in certificate? 110 public static final IClientConfigKey<Boolean> IsHostnameValidationRequired = new CommonClientConfigKey<Boolean>("IsHostnameValidationRequired"){}; 111 112 // see also http://hc.apache.org/httpcomponents-client-ga/tutorial/html/advanced.html 113 public static final IClientConfigKey<Boolean> IgnoreUserTokenInConnectionPoolForSecureClient = new CommonClientConfigKey<Boolean>("IgnoreUserTokenInConnectionPoolForSecureClient"){}; 114 115 // Client implementation 116 public static final IClientConfigKey<String> ClientClassName = new CommonClientConfigKey<String>("ClientClassName"){}; 117 118 //LoadBalancer Related 119 public static final IClientConfigKey<Boolean> InitializeNFLoadBalancer = new CommonClientConfigKey<Boolean>("InitializeNFLoadBalancer"){}; 120 121 public static final IClientConfigKey<String> NFLoadBalancerClassName = new CommonClientConfigKey<String>("NFLoadBalancerClassName"){}; 122 123 public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName"){}; 124 125 public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName"){}; 126 127 public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval"){}; 128 129 public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime"){}; 130 131 public static final IClientConfigKey<String> NFLoadBalancerStatsClassName = new CommonClientConfigKey<String>("NFLoadBalancerStatsClassName"){}; 132 133 public static final IClientConfigKey<String> NIWSServerListClassName = new CommonClientConfigKey<String>("NIWSServerListClassName"){}; 134 135 public static final IClientConfigKey<String> ServerListUpdaterClassName = new CommonClientConfigKey<String>("ServerListUpdaterClassName"){}; 136 137 public static final IClientConfigKey<String> NIWSServerListFilterClassName = new CommonClientConfigKey<String>("NIWSServerListFilterClassName"){}; 138 139 public static final IClientConfigKey<Integer> ServerListRefreshInterval = new CommonClientConfigKey<Integer>("ServerListRefreshInterval"){}; 140 141 public static final IClientConfigKey<Boolean> EnableMarkingServerDownOnReachingFailureLimit = new CommonClientConfigKey<Boolean>("EnableMarkingServerDownOnReachingFailureLimit"){}; 142 143 public static final IClientConfigKey<Integer> ServerDownFailureLimit = new CommonClientConfigKey<Integer>("ServerDownFailureLimit"){}; 144 145 public static final IClientConfigKey<Integer> ServerDownStatWindowInMillis = new CommonClientConfigKey<Integer>("ServerDownStatWindowInMillis"){}; 146 147 public static final IClientConfigKey<Boolean> EnableZoneAffinity = new CommonClientConfigKey<Boolean>("EnableZoneAffinity"){}; 148 149 public static final IClientConfigKey<Boolean> EnableZoneExclusivity = new CommonClientConfigKey<Boolean>("EnableZoneExclusivity"){}; 150 151 public static final IClientConfigKey<Boolean> PrioritizeVipAddressBasedServers = new CommonClientConfigKey<Boolean>("PrioritizeVipAddressBasedServers"){}; 152 153 public static final IClientConfigKey<String> VipAddressResolverClassName = new CommonClientConfigKey<String>("VipAddressResolverClassName"){}; 154 155 public static final IClientConfigKey<String> TargetRegion = new CommonClientConfigKey<String>("TargetRegion"){}; 156 157 public static final IClientConfigKey<String> RulePredicateClasses = new CommonClientConfigKey<String>("RulePredicateClasses"){}; 158 159 public static final IClientConfigKey<String> RequestIdHeaderName = new CommonClientConfigKey<String>("RequestIdHeaderName") {}; 160 161 public static final IClientConfigKey<Boolean> UseIPAddrForServer = new CommonClientConfigKey<Boolean>("UseIPAddrForServer") {}; 162 163 public static final IClientConfigKey<String> ListOfServers = new CommonClientConfigKey<String>("listOfServers") {}; 164 165 private static final Set<IClientConfigKey> keys = new HashSet<IClientConfigKey>(); 166 167 // ... 168 }
进入到 DefaultClientConfigImpl,可以看到 CommonClientConfigKey 中的每个配置都对应了一个默认值。在加载配置的时候,如果用户没有定制配置,就会使用默认的配置。
1 public class DefaultClientConfigImpl implements IClientConfig { 2 3 public static final Boolean DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS = Boolean.TRUE; 4 5 public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing"; // DummyPing.class.getName(); 6 7 public static final String DEFAULT_NFLOADBALANCER_RULE_CLASSNAME = "com.netflix.loadbalancer.AvailabilityFilteringRule"; 8 9 public static final String DEFAULT_NFLOADBALANCER_CLASSNAME = "com.netflix.loadbalancer.ZoneAwareLoadBalancer"; 10 11 public static final boolean DEFAULT_USEIPADDRESS_FOR_SERVER = Boolean.FALSE; 12 13 public static final String DEFAULT_CLIENT_CLASSNAME = "com.netflix.niws.client.http.RestClient"; 14 15 public static final String DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME = "com.netflix.client.SimpleVipAddressResolver"; 16 17 public static final String DEFAULT_PRIME_CONNECTIONS_URI = "/"; 18 19 public static final int DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS = 30000; 20 21 public static final int DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION = 9; 22 23 public static final Boolean DEFAULT_ENABLE_PRIME_CONNECTIONS = Boolean.FALSE; 24 25 public static final int DEFAULT_MAX_REQUESTS_ALLOWED_PER_WINDOW = Integer.MAX_VALUE; 26 27 public static final int DEFAULT_REQUEST_THROTTLING_WINDOW_IN_MILLIS = 60000; 28 29 public static final Boolean DEFAULT_ENABLE_REQUEST_THROTTLING = Boolean.FALSE; 30 31 public static final Boolean DEFAULT_ENABLE_GZIP_CONTENT_ENCODING_FILTER = Boolean.FALSE; 32 33 public static final Boolean DEFAULT_CONNECTION_POOL_CLEANER_TASK_ENABLED = Boolean.TRUE; 34 35 public static final Boolean DEFAULT_FOLLOW_REDIRECTS = Boolean.FALSE; 36 37 public static final float DEFAULT_PERCENTAGE_NIWS_EVENT_LOGGED = 0.0f; 38 39 public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1; 40 41 public static final int DEFAULT_MAX_AUTO_RETRIES = 0; 42 43 public static final int DEFAULT_BACKOFF_INTERVAL = 0; 44 45 public static final int DEFAULT_READ_TIMEOUT = 5000; 46 47 public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000; 48 49 public static final int DEFAULT_CONNECT_TIMEOUT = 2000; 50 51 public static final Boolean DEFAULT_ENABLE_CONNECTION_POOL = Boolean.TRUE; 52 53 @Deprecated 54 public static final int DEFAULT_MAX_HTTP_CONNECTIONS_PER_HOST = 50; 55 56 @Deprecated 57 public static final int DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS = 200; 58 59 public static final int DEFAULT_MAX_CONNECTIONS_PER_HOST = 50; 60 61 public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 200; 62 63 public static final float DEFAULT_MIN_PRIME_CONNECTIONS_RATIO = 1.0f; 64 65 public static final String DEFAULT_PRIME_CONNECTIONS_CLASS = "com.netflix.niws.client.http.HttpPrimeConnection"; 66 67 public static final String DEFAULT_SEVER_LIST_CLASS = "com.netflix.loadbalancer.ConfigurationBasedServerList"; 68 69 public static final String DEFAULT_SERVER_LIST_UPDATER_CLASS = "com.netflix.loadbalancer.PollingServerListUpdater"; 70 71 public static final int DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS = 30000; // every half minute (30 secs) 72 73 public static final int DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS = 30000; // all connections idle for 30 secs 74 75 protected volatile Map<String, Object> properties = new ConcurrentHashMap<String, Object>(); 76 77 protected Map<IClientConfigKey<?>, Object> typedProperties = new ConcurrentHashMap<IClientConfigKey<?>, Object>(); 78 79 private static final Logger LOG = LoggerFactory.getLogger(DefaultClientConfigImpl.class); 80 81 private String clientName = null; 82 83 private VipAddressResolver resolver = null; 84 85 private boolean enableDynamicProperties = true; 86 /** 87 * Defaults for the parameters for the thread pool used by batchParallel 88 * calls 89 */ 90 public static final int DEFAULT_POOL_MAX_THREADS = DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS; 91 public static final int DEFAULT_POOL_MIN_THREADS = 1; 92 public static final long DEFAULT_POOL_KEEP_ALIVE_TIME = 15 * 60L; 93 public static final TimeUnit DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS = TimeUnit.SECONDS; 94 public static final Boolean DEFAULT_ENABLE_ZONE_AFFINITY = Boolean.FALSE; 95 public static final Boolean DEFAULT_ENABLE_ZONE_EXCLUSIVITY = Boolean.FALSE; 96 public static final int DEFAULT_PORT = 7001; 97 public static final Boolean DEFAULT_ENABLE_LOADBALANCER = Boolean.TRUE; 98 99 public static final String DEFAULT_PROPERTY_NAME_SPACE = "ribbon"; 100 101 private String propertyNameSpace = DEFAULT_PROPERTY_NAME_SPACE; 102 103 public static final Boolean DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS = Boolean.FALSE; 104 105 public static final Boolean DEFAULT_ENABLE_NIWS_EVENT_LOGGING = Boolean.TRUE; 106 107 public static final Boolean DEFAULT_IS_CLIENT_AUTH_REQUIRED = Boolean.FALSE; 108 109 private final Map<String, DynamicStringProperty> dynamicProperties = new ConcurrentHashMap<String, DynamicStringProperty>(); 110 111 public Boolean getDefaultPrioritizeVipAddressBasedServers() { 112 return DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS; 113 } 114 115 public String getDefaultNfloadbalancerPingClassname() { 116 return DEFAULT_NFLOADBALANCER_PING_CLASSNAME; 117 } 118 119 public String getDefaultNfloadbalancerRuleClassname() { 120 return DEFAULT_NFLOADBALANCER_RULE_CLASSNAME; 121 } 122 123 public String getDefaultNfloadbalancerClassname() { 124 return DEFAULT_NFLOADBALANCER_CLASSNAME; 125 } 126 127 public boolean getDefaultUseIpAddressForServer() { 128 return DEFAULT_USEIPADDRESS_FOR_SERVER; 129 } 130 131 public String getDefaultClientClassname() { 132 return DEFAULT_CLIENT_CLASSNAME; 133 } 134 135 public String getDefaultVipaddressResolverClassname() { 136 return DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME; 137 } 138 139 public String getDefaultPrimeConnectionsUri() { 140 return DEFAULT_PRIME_CONNECTIONS_URI; 141 } 142 143 public int getDefaultMaxTotalTimeToPrimeConnections() { 144 return DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS; 145 } 146 147 public int getDefaultMaxRetriesPerServerPrimeConnection() { 148 return DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION; 149 } 150 151 public Boolean getDefaultEnablePrimeConnections() { 152 return DEFAULT_ENABLE_PRIME_CONNECTIONS; 153 } 154 155 public int getDefaultMaxRequestsAllowedPerWindow() { 156 return DEFAULT_MAX_REQUESTS_ALLOWED_PER_WINDOW; 157 } 158 159 public int getDefaultRequestThrottlingWindowInMillis() { 160 return DEFAULT_REQUEST_THROTTLING_WINDOW_IN_MILLIS; 161 } 162 163 public Boolean getDefaultEnableRequestThrottling() { 164 return DEFAULT_ENABLE_REQUEST_THROTTLING; 165 } 166 167 public Boolean getDefaultEnableGzipContentEncodingFilter() { 168 return DEFAULT_ENABLE_GZIP_CONTENT_ENCODING_FILTER; 169 } 170 171 public Boolean getDefaultConnectionPoolCleanerTaskEnabled() { 172 return DEFAULT_CONNECTION_POOL_CLEANER_TASK_ENABLED; 173 } 174 175 public Boolean getDefaultFollowRedirects() { 176 return DEFAULT_FOLLOW_REDIRECTS; 177 } 178 179 public float getDefaultPercentageNiwsEventLogged() { 180 return DEFAULT_PERCENTAGE_NIWS_EVENT_LOGGED; 181 } 182 183 public int getDefaultMaxAutoRetriesNextServer() { 184 return DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER; 185 } 186 187 public int getDefaultMaxAutoRetries() { 188 return DEFAULT_MAX_AUTO_RETRIES; 189 } 190 191 public int getDefaultReadTimeout() { 192 return DEFAULT_READ_TIMEOUT; 193 } 194 195 public int getDefaultConnectionManagerTimeout() { 196 return DEFAULT_CONNECTION_MANAGER_TIMEOUT; 197 } 198 199 public int getDefaultConnectTimeout() { 200 return DEFAULT_CONNECT_TIMEOUT; 201 } 202 203 @Deprecated 204 public int getDefaultMaxHttpConnectionsPerHost() { 205 return DEFAULT_MAX_HTTP_CONNECTIONS_PER_HOST; 206 } 207 208 @Deprecated 209 public int getDefaultMaxTotalHttpConnections() { 210 return DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS; 211 } 212 213 public int getDefaultMaxConnectionsPerHost() { 214 return DEFAULT_MAX_CONNECTIONS_PER_HOST; 215 } 216 217 public int getDefaultMaxTotalConnections() { 218 return DEFAULT_MAX_TOTAL_CONNECTIONS; 219 } 220 221 public float getDefaultMinPrimeConnectionsRatio() { 222 return DEFAULT_MIN_PRIME_CONNECTIONS_RATIO; 223 } 224 225 public String getDefaultPrimeConnectionsClass() { 226 return DEFAULT_PRIME_CONNECTIONS_CLASS; 227 } 228 229 public String getDefaultSeverListClass() { 230 return DEFAULT_SEVER_LIST_CLASS; 231 } 232 233 public int getDefaultConnectionIdleTimertaskRepeatInMsecs() { 234 return DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS; 235 } 236 237 public int getDefaultConnectionidleTimeInMsecs() { 238 return DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS; 239 } 240 241 public VipAddressResolver getResolver() { 242 return resolver; 243 } 244 245 public boolean isEnableDynamicProperties() { 246 return enableDynamicProperties; 247 } 248 249 public int getDefaultPoolMaxThreads() { 250 return DEFAULT_POOL_MAX_THREADS; 251 } 252 253 public int getDefaultPoolMinThreads() { 254 return DEFAULT_POOL_MIN_THREADS; 255 } 256 257 public long getDefaultPoolKeepAliveTime() { 258 return DEFAULT_POOL_KEEP_ALIVE_TIME; 259 } 260 261 public TimeUnit getDefaultPoolKeepAliveTimeUnits() { 262 return DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS; 263 } 264 265 public Boolean getDefaultEnableZoneAffinity() { 266 return DEFAULT_ENABLE_ZONE_AFFINITY; 267 } 268 269 public Boolean getDefaultEnableZoneExclusivity() { 270 return DEFAULT_ENABLE_ZONE_EXCLUSIVITY; 271 } 272 273 public int getDefaultPort() { 274 return DEFAULT_PORT; 275 } 276 277 public Boolean getDefaultEnableLoadbalancer() { 278 return DEFAULT_ENABLE_LOADBALANCER; 279 } 280 281 282 public Boolean getDefaultOkToRetryOnAllOperations() { 283 return DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS; 284 } 285 286 public Boolean getDefaultIsClientAuthRequired(){ 287 return DEFAULT_IS_CLIENT_AUTH_REQUIRED; 288 } 289 290 291 /** 292 * Create instance with no properties in default name space {@link #DEFAULT_PROPERTY_NAME_SPACE} 293 */ 294 public DefaultClientConfigImpl() { 295 this.dynamicProperties.clear(); 296 this.enableDynamicProperties = false; 297 } 298 299 /** 300 * Create instance with no properties in the specified name space 301 */ 302 public DefaultClientConfigImpl(String nameSpace) { 303 this(); 304 this.propertyNameSpace = nameSpace; 305 } 306 307 public void loadDefaultValues() { 308 putDefaultIntegerProperty(CommonClientConfigKey.MaxHttpConnectionsPerHost, getDefaultMaxHttpConnectionsPerHost()); 309 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalHttpConnections, getDefaultMaxTotalHttpConnections()); 310 putDefaultBooleanProperty(CommonClientConfigKey.EnableConnectionPool, getDefaultEnableConnectionPool()); 311 putDefaultIntegerProperty(CommonClientConfigKey.MaxConnectionsPerHost, getDefaultMaxConnectionsPerHost()); 312 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalConnections, getDefaultMaxTotalConnections()); 313 putDefaultIntegerProperty(CommonClientConfigKey.ConnectTimeout, getDefaultConnectTimeout()); 314 putDefaultIntegerProperty(CommonClientConfigKey.ConnectionManagerTimeout, getDefaultConnectionManagerTimeout()); 315 putDefaultIntegerProperty(CommonClientConfigKey.ReadTimeout, getDefaultReadTimeout()); 316 putDefaultIntegerProperty(CommonClientConfigKey.MaxAutoRetries, getDefaultMaxAutoRetries()); 317 putDefaultIntegerProperty(CommonClientConfigKey.MaxAutoRetriesNextServer, getDefaultMaxAutoRetriesNextServer()); 318 putDefaultBooleanProperty(CommonClientConfigKey.OkToRetryOnAllOperations, getDefaultOkToRetryOnAllOperations()); 319 putDefaultBooleanProperty(CommonClientConfigKey.FollowRedirects, getDefaultFollowRedirects()); 320 putDefaultBooleanProperty(CommonClientConfigKey.ConnectionPoolCleanerTaskEnabled, getDefaultConnectionPoolCleanerTaskEnabled()); 321 putDefaultIntegerProperty(CommonClientConfigKey.ConnIdleEvictTimeMilliSeconds, getDefaultConnectionidleTimeInMsecs()); 322 putDefaultIntegerProperty(CommonClientConfigKey.ConnectionCleanerRepeatInterval, getDefaultConnectionIdleTimertaskRepeatInMsecs()); 323 putDefaultBooleanProperty(CommonClientConfigKey.EnableGZIPContentEncodingFilter, getDefaultEnableGzipContentEncodingFilter()); 324 String proxyHost = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(CommonClientConfigKey.ProxyHost.key())); 325 if (proxyHost != null && proxyHost.length() > 0) { 326 setProperty(CommonClientConfigKey.ProxyHost, proxyHost); 327 } 328 Integer proxyPort = ConfigurationManager 329 .getConfigInstance() 330 .getInteger( 331 getDefaultPropName(CommonClientConfigKey.ProxyPort), 332 (Integer.MIN_VALUE + 1)); // + 1 just to avoid potential clash with user setting 333 if (proxyPort != (Integer.MIN_VALUE + 1)) { 334 setProperty(CommonClientConfigKey.ProxyPort, proxyPort); 335 } 336 putDefaultIntegerProperty(CommonClientConfigKey.Port, getDefaultPort()); 337 putDefaultBooleanProperty(CommonClientConfigKey.EnablePrimeConnections, getDefaultEnablePrimeConnections()); 338 putDefaultIntegerProperty(CommonClientConfigKey.MaxRetriesPerServerPrimeConnection, getDefaultMaxRetriesPerServerPrimeConnection()); 339 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalTimeToPrimeConnections, getDefaultMaxTotalTimeToPrimeConnections()); 340 putDefaultStringProperty(CommonClientConfigKey.PrimeConnectionsURI, getDefaultPrimeConnectionsUri()); 341 putDefaultIntegerProperty(CommonClientConfigKey.PoolMinThreads, getDefaultPoolMinThreads()); 342 putDefaultIntegerProperty(CommonClientConfigKey.PoolMaxThreads, getDefaultPoolMaxThreads()); 343 putDefaultLongProperty(CommonClientConfigKey.PoolKeepAliveTime, getDefaultPoolKeepAliveTime()); 344 putDefaultTimeUnitProperty(CommonClientConfigKey.PoolKeepAliveTimeUnits, getDefaultPoolKeepAliveTimeUnits()); 345 putDefaultBooleanProperty(CommonClientConfigKey.EnableZoneAffinity, getDefaultEnableZoneAffinity()); 346 putDefaultBooleanProperty(CommonClientConfigKey.EnableZoneExclusivity, getDefaultEnableZoneExclusivity()); 347 putDefaultStringProperty(CommonClientConfigKey.ClientClassName, getDefaultClientClassname()); 348 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerClassName, getDefaultNfloadbalancerClassname()); 349 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, getDefaultNfloadbalancerRuleClassname()); 350 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerPingClassName, getDefaultNfloadbalancerPingClassname()); 351 putDefaultBooleanProperty(CommonClientConfigKey.PrioritizeVipAddressBasedServers, getDefaultPrioritizeVipAddressBasedServers()); 352 putDefaultFloatProperty(CommonClientConfigKey.MinPrimeConnectionsRatio, getDefaultMinPrimeConnectionsRatio()); 353 putDefaultStringProperty(CommonClientConfigKey.PrimeConnectionsClassName, getDefaultPrimeConnectionsClass()); 354 putDefaultStringProperty(CommonClientConfigKey.NIWSServerListClassName, getDefaultSeverListClass()); 355 putDefaultStringProperty(CommonClientConfigKey.VipAddressResolverClassName, getDefaultVipaddressResolverClassname()); 356 putDefaultBooleanProperty(CommonClientConfigKey.IsClientAuthRequired, getDefaultIsClientAuthRequired()); 357 // putDefaultStringProperty(CommonClientConfigKey.RequestIdHeaderName, getDefaultRequestIdHeaderName()); 358 putDefaultBooleanProperty(CommonClientConfigKey.UseIPAddrForServer, getDefaultUseIpAddressForServer()); 359 putDefaultStringProperty(CommonClientConfigKey.ListOfServers, ""); 360 } 361 }
也可以在配置文件中定制配置,例如配置超时和重试:
1 # 全局配置 2 ribbon: 3 # 客户端读取超时时间 4 ReadTimeout: 3000 5 # 客户端连接超时时间 6 ConnectTimeout: 3000 7 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE 8 OkToRetryOnAllOperations: false 9 # 重试次数 10 MaxAutoRetries: 1 11 # 最多重试几个实例 12 MaxAutoRetriesNextServer: 1 13 14 # 只针对 demo-producer 客户端 15 demo-producer: 16 ribbon: 17 # 客户端读取超时时间 18 ReadTimeout: 5000 19 # 客户端连接超时时间 20 ConnectTimeout: 3000
2、均衡策略 — IRule
IRule 是最终选择 Server 的策略规则类,核心的接口就是 choose。
1 public interface IRule{ 2 3 // 选择 Server 4 public Server choose(Object key); 5 6 // 设置 ILoadBalancer 7 public void setLoadBalancer(ILoadBalancer lb); 8 9 // 获取 ILoadBalancer 10 public ILoadBalancer getLoadBalancer(); 11 }
Ribbon 提供了丰富的负载均衡策略,我们也可以通过配置指定使用某个均衡策略。下面是整个Ribbon提供的 IRule 均衡策略。
3、服务检查 — IPing
IPing 是用于定期检查 Server 的可用性的,它只提供了一个接口,用来判断 Server 是否存活:
1 public interface IPing {
2
3 public boolean isAlive(Server server);
4 }
IPing 也提供了多种策略可选,下面是整个 IPing 体系结构:
4、获取服务列表 — ServerList
ServerList 提供了两个接口,一个是第一次获取 Server 列表,一个是更新 Server 列表,其中 getUpdatedListOfServers 会每被 Loadbalancer 隔 30 秒调一次来更新 allServerList。
1 public interface ServerList<T extends Server> {
2
3 public List<T> getInitialListOfServers();
4
5 /**
6 * Return updated list of servers. This is called say every 30 secs
7 * (configurable) by the Loadbalancer's Ping cycle
8 */
9 public List<T> getUpdatedListOfServers();
10 }
ServerList 也提供了多种实现,ServerList 体系结构如下:
5、过滤服务 — ServerListFilter
ServerListFilter 提供了一个接口用来过滤出可用的 Server。
1 public interface ServerListFilter<T extends Server> { 2 3 public List<T> getFilteredListOfServers(List<T> servers); 4 }
ServerListFilter 体系结构如下:
6、服务列表更新 — ServerListUpdater
ServerListUpdater 有多个接口,最核心的就是 start 开启定时任务调用 updateAction 来更新 allServerList。
1 public interface ServerListUpdater {
2
3 /**
4 * an interface for the updateAction that actually executes a server list update
5 */
6 public interface UpdateAction {
7 void doUpdate();
8 }
9
10 /**
11 * start the serverList updater with the given update action
12 * This call should be idempotent.
13 */
14 void start(UpdateAction updateAction);
15 }
默认有两个实现类:
7、负载均衡器 — ILoadBalancer
ILoadBalancer 是负载均衡选择服务的核心接口,主要提供了如下的获取Server列表和根据客户端名称选择Server的接口。
1 public interface ILoadBalancer {
2
3 // 添加Server
4 public void addServers(List<Server> newServers);
5
6 // 根据key选择一个Server
7 public Server chooseServer(Object key);
8
9 // 获取存活的Server列表,返回 upServerList
10 public List<Server> getReachableServers();
11
12 // 获取所有Server列表,返回 allServerList
13 public List<Server> getAllServers();
14 }
ILoadBalancer 的体系结构如下:
8、Ribbon 相关配置类
从前面一直看下来,可以发现有很多与 Ribbon 相关的配置类,这里总结下与 Ribbon 相关的配置类,看每个配置类的配置顺序,以及都主要配置了哪些东西。
① 首先是Eureka客户端配置类 EurekaClientAutoConfiguration,这个自动化配置类主要配置了 Ribbon 所需的 EurekaClient。
1 @Configuration(proxyBeanMethods = false) 2 @EnableConfigurationProperties 3 @ConditionalOnClass(EurekaClientConfig.class) 4 @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true) 5 @ConditionalOnDiscoveryEnabled 6 @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class, 7 CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class }) 8 @AutoConfigureAfter(name = { 9 "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration", 10 "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration", 11 "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration", 12 "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" }) 13 public class EurekaClientAutoConfiguration { 14 // .... 15 }
② 接着是Ribbon自动化配置类 RibbonAutoConfiguration,这个类主要配置了如下类:
- SpringClientFactory:管理 Ribbon 客户端上下文。
- LoadBalancerClient:负载均衡客户端,默认实现类为 RibbonLoadBalancerClient(实际是在 RibbonClientConfiguration 中配置的)。
- PropertiesFactory:用于判断配置文件中是否自定义了核心接口的实现类,如 NFLoadBalancerClassName、NFLoadBalancerPingClassName 等。
- RibbonApplicationContextInitializer:开启饥饿配置的时候,用这个类来在启动时初始化 Ribbon 客户端上下文。
1 package org.springframework.cloud.netflix.ribbon; 2 3 @Configuration 4 @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class) 5 @RibbonClients 6 // 在 EurekaClientAutoConfiguration 之后配置 7 @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") 8 // 在 LoadBalancerAutoConfiguration、AsyncLoadBalancerAutoConfiguration 之前配置 9 @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class }) 10 @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class }) 11 public class RibbonAutoConfiguration { 12 13 @Autowired(required = false) 14 private List<RibbonClientSpecification> configurations = new ArrayList<>(); 15 16 @Autowired 17 private RibbonEagerLoadProperties ribbonEagerLoadProperties; 18 19 @Bean 20 public HasFeatures ribbonFeature() { 21 return HasFeatures.namedFeature("Ribbon", Ribbon.class); 22 } 23 24 @Bean 25 @ConditionalOnMissingBean 26 public SpringClientFactory springClientFactory() { 27 SpringClientFactory factory = new SpringClientFactory(); 28 factory.setConfigurations(this.configurations); 29 return factory; 30 } 31 32 @Bean 33 @ConditionalOnMissingBean(LoadBalancerClient.class) 34 public LoadBalancerClient loadBalancerClient() { 35 return new RibbonLoadBalancerClient(springClientFactory()); 36 } 37 38 @Bean 39 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") 40 @ConditionalOnMissingBean 41 public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) { 42 return new RibbonLoadBalancedRetryFactory(clientFactory); 43 } 44 45 @Bean 46 @ConditionalOnMissingBean 47 public PropertiesFactory propertiesFactory() { 48 return new PropertiesFactory(); 49 } 50 51 @Bean 52 @ConditionalOnProperty("ribbon.eager-load.enabled") 53 public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() { 54 return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients()); 55 } 56 }
③ 接着是负载均衡器配置类 LoadBalancerAutoConfiguration,这个类主要是创建了负载均衡拦截器 LoadBalancerInterceptor,并添加到 RestTemplae 的拦截器中。
1 package org.springframework.cloud.client.loadbalancer; 2 3 @Configuration(proxyBeanMethods = false) 4 @ConditionalOnClass(RestTemplate.class) 5 @ConditionalOnBean(LoadBalancerClient.class) 6 @EnableConfigurationProperties(LoadBalancerRetryProperties.class) 7 public class LoadBalancerAutoConfiguration { 8 9 @LoadBalanced 10 @Autowired(required = false) 11 private List<RestTemplate> restTemplates = Collections.emptyList(); 12 13 @Autowired(required = false) 14 private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); 15 16 // 对 RestTemplate 定制化 17 @Bean 18 public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( 19 final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { 20 return () -> restTemplateCustomizers.ifAvailable(customizers -> { 21 for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { 22 for (RestTemplateCustomizer customizer : customizers) { 23 customizer.customize(restTemplate); 24 } 25 } 26 }); 27 } 28 29 @Bean 30 @ConditionalOnMissingBean 31 public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) { 32 return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); 33 } 34 35 @Configuration(proxyBeanMethods = false) 36 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 37 static class LoadBalancerInterceptorConfig { 38 39 // 创建 RestTemplate 拦截器 40 @Bean 41 public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, 42 LoadBalancerRequestFactory requestFactory) { 43 return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); 44 } 45 46 @Bean 47 @ConditionalOnMissingBean 48 public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) { 49 return restTemplate -> { 50 List<ClientHttpRequestInterceptor> list = new ArrayList<>( 51 restTemplate.getInterceptors()); 52 list.add(loadBalancerInterceptor); 53 restTemplate.setInterceptors(list); 54 }; 55 } 56 57 } 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 package org.springframework.cloud.netflix.ribbon; 2 3 @SuppressWarnings("deprecation") 4 @Configuration(proxyBeanMethods = false) 5 @EnableConfigurationProperties 6 @Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, 7 RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class }) 8 public class RibbonClientConfiguration { 9 public static final int DEFAULT_CONNECT_TIMEOUT = 1000; 10 public static final int DEFAULT_READ_TIMEOUT = 1000; 11 public static final boolean DEFAULT_GZIP_PAYLOAD = true; 12 13 @RibbonClientName 14 private String name = "client"; 15 16 @Autowired 17 private PropertiesFactory propertiesFactory; 18 19 @Bean 20 @ConditionalOnMissingBean 21 public IClientConfig ribbonClientConfig() { 22 DefaultClientConfigImpl config = new DefaultClientConfigImpl(); 23 config.loadProperties(this.name); 24 config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT); 25 config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT); 26 config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD); 27 return config; 28 } 29 30 @Bean 31 @ConditionalOnMissingBean 32 public IRule ribbonRule(IClientConfig config) { 33 if (this.propertiesFactory.isSet(IRule.class, name)) { 34 return this.propertiesFactory.get(IRule.class, config, name); 35 } 36 ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); 37 rule.initWithNiwsConfig(config); 38 return rule; 39 } 40 41 @Bean 42 @ConditionalOnMissingBean 43 public IPing ribbonPing(IClientConfig config) { 44 if (this.propertiesFactory.isSet(IPing.class, name)) { 45 return this.propertiesFactory.get(IPing.class, config, name); 46 } 47 return new DummyPing(); 48 } 49 50 @Bean 51 @ConditionalOnMissingBean 52 @SuppressWarnings("unchecked") 53 public ServerList<Server> ribbonServerList(IClientConfig config) { 54 if (this.propertiesFactory.isSet(ServerList.class, name)) { 55 return this.propertiesFactory.get(ServerList.class, config, name); 56 } 57 ConfigurationBasedServerList serverList = new ConfigurationBasedServerList(); 58 serverList.initWithNiwsConfig(config); 59 return serverList; 60 } 61 62 @Bean 63 @ConditionalOnMissingBean 64 public ServerListUpdater ribbonServerListUpdater(IClientConfig config) { 65 return new PollingServerListUpdater(config); 66 } 67 68 @Bean 69 @ConditionalOnMissingBean 70 public ILoadBalancer ribbonLoadBalancer(IClientConfig config, 71 ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, 72 IRule rule, IPing ping, ServerListUpdater serverListUpdater) { 73 if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) { 74 return this.propertiesFactory.get(ILoadBalancer.class, config, name); 75 } 76 return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, 77 serverListFilter, serverListUpdater); 78 } 79 80 @Bean 81 @ConditionalOnMissingBean 82 @SuppressWarnings("unchecked") 83 public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) { 84 if (this.propertiesFactory.isSet(ServerListFilter.class, name)) { 85 return this.propertiesFactory.get(ServerListFilter.class, config, name); 86 } 87 ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); 88 filter.initWithNiwsConfig(config); 89 return filter; 90 } 91 92 @Bean 93 @ConditionalOnMissingBean 94 public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer, 95 IClientConfig config, RetryHandler retryHandler) { 96 return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler); 97 } 98 99 @Bean 100 @ConditionalOnMissingBean 101 public RetryHandler retryHandler(IClientConfig config) { 102 return new DefaultLoadBalancerRetryHandler(config); 103 } 104 105 @Bean 106 @ConditionalOnMissingBean 107 public ServerIntrospector serverIntrospector() { 108 return new DefaultServerIntrospector(); 109 } 110 }
⑤ Ribbon Eureka 自动化配置类 RibbonEurekaAutoConfiguration,判断是否启用 Ribbon Eureka,并触发 EurekaRibbonClientConfiguration 配置类。
1 package org.springframework.cloud.netflix.ribbon.eureka; 2 3 @Configuration(proxyBeanMethods = false) 4 @EnableConfigurationProperties 5 @ConditionalOnRibbonAndEurekaEnabled 6 @AutoConfigureAfter(RibbonAutoConfiguration.class) 7 @RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class) 8 public class RibbonEurekaAutoConfiguration { 9 10 }
⑥ 默认启用 Ribbon Eureka 的情况下,会使用 Ribbon Eureka 客户端配置类 EurekaRibbonClientConfiguration:
- IPing:替换了默认实现类 DummyPing,改为 NIWSDiscoveryPing,通过判断 InstanceInfo 的状态是否为 UP 来判断 Server 是否存活。
- ServerList:替换了默认的实现类 ConfigurationBasedServerList,改为 DomainExtractingServerList,实际是 DiscoveryEnabledNIWSServerList,从 EurekaClient 获取 Server 列表。
1 package org.springframework.cloud.netflix.ribbon.eureka; 2 3 @Configuration(proxyBeanMethods = false) 4 public class EurekaRibbonClientConfiguration { 5 6 private static final Log log = LogFactory.getLog(EurekaRibbonClientConfiguration.class); 7 8 @Value("${ribbon.eureka.approximateZoneFromHostname:false}") 9 private boolean approximateZoneFromHostname = false; 10 @RibbonClientName 11 private String serviceId = "client"; 12 @Autowired(required = false) 13 private EurekaClientConfig clientConfig; 14 @Autowired(required = false) 15 private EurekaInstanceConfig eurekaConfig; 16 @Autowired 17 private PropertiesFactory propertiesFactory; 18 19 public EurekaRibbonClientConfiguration() { 20 } 21 22 public EurekaRibbonClientConfiguration(EurekaClientConfig clientConfig, 23 String serviceId, EurekaInstanceConfig eurekaConfig, 24 boolean approximateZoneFromHostname) { 25 this.clientConfig = clientConfig; 26 this.serviceId = serviceId; 27 this.eurekaConfig = eurekaConfig; 28 this.approximateZoneFromHostname = approximateZoneFromHostname; 29 } 30 31 @Bean 32 @ConditionalOnMissingBean 33 public IPing ribbonPing(IClientConfig config) { 34 if (this.propertiesFactory.isSet(IPing.class, serviceId)) { 35 return this.propertiesFactory.get(IPing.class, config, serviceId); 36 } 37 NIWSDiscoveryPing ping = new NIWSDiscoveryPing(); 38 ping.initWithNiwsConfig(config); 39 return ping; 40 } 41 42 @Bean 43 @ConditionalOnMissingBean 44 public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) { 45 if (this.propertiesFactory.isSet(ServerList.class, serviceId)) { 46 return this.propertiesFactory.get(ServerList.class, config, serviceId); 47 } 48 DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList( 49 config, eurekaClientProvider); 50 DomainExtractingServerList serverList = new DomainExtractingServerList( 51 discoveryServerList, config, this.approximateZoneFromHostname); 52 return serverList; 53 } 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 ribbon: 2 # 客户端读取超时时间 3 ReadTimeout: 1000 4 # 客户端连接超时时间 5 ConnectTimeout: 1000 6 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE 7 OkToRetryOnAllOperations: false 8 # 重试次数 9 MaxAutoRetries: 1 10 # 最多重试几个实例 11 MaxAutoRetriesNextServer: 1
然后 demo-producer 的接口休眠3秒,造成网络延迟的现象,并且 demo-producer 启两个实例。
1 @GetMapping("/v1/uuid") 2 public ResponseEntity<String> getUUID() throws InterruptedException { 3 String uuid = UUID.randomUUID().toString(); 4 logger.info("generate uuid: {}", uuid); 5 Thread.sleep(3000); 6 return ResponseEntity.ok(uuid); 7 }
2、Ribbon 默认使用 HttpURLConnection
① Ribbon 默认的 HTTP 组件
在不添加其它配置的情况下,我们来看下 Ribbon 默认使用的 HTTP 组件是什么。
首先通过之前的分析可以知道,默认情况下,LoadBalancerAutoConfiguration 配置类会向 RestTemplate 添加 LoadBalancerInterceptor 拦截器。然后在 RestTemplate 调用时,即在 doExecute 方法中,创建 ClientHttpRequest 时,因为配置了拦截器,所以 ClientHttpRequestFactory 就是 InterceptingClientHttpRequestFactory,而且创建 InterceptingClientHttpRequestFactory 传入的 ClientHttpRequestFactory 默认是父类的 SimpleClientHttpRequestFactory。
1 protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, 2 @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { 3 //... 4 ClientHttpResponse response = null; 5 try { 6 // 创建 ClientHttpRequest 7 ClientHttpRequest request = createRequest(url, method); 8 if (requestCallback != null) { 9 requestCallback.doWithRequest(request); 10 } 11 // ClientHttpRequest 发起请求 12 response = request.execute(); 13 handleResponse(url, method, response); 14 return (responseExtractor != null ? responseExtractor.extractData(response) : null); 15 } 16 catch (IOException ex) { 17 // ... 18 } 19 finally { 20 if (response != null) { 21 response.close(); 22 } 23 } 24 } 25 26 protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { 27 // getRequestFactory 获取 ClientHttpRequestFactory 28 ClientHttpRequest request = getRequestFactory().createRequest(url, method); 29 initialize(request); 30 if (logger.isDebugEnabled()) { 31 logger.debug("HTTP " + method.name() + " " + url); 32 } 33 return request; 34 } 35 36 public ClientHttpRequestFactory getRequestFactory() { 37 List<ClientHttpRequestInterceptor> interceptors = getInterceptors(); 38 if (!CollectionUtils.isEmpty(interceptors)) { 39 ClientHttpRequestFactory factory = this.interceptingRequestFactory; 40 if (factory == null) { 41 // 有拦截器的情况,super.getRequestFactory() 默认返回的是 SimpleClientHttpRequestFactory 42 factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors); 43 this.interceptingRequestFactory = factory; 44 } 45 return factory; 46 } 47 else { 48 // 无拦截器的情况 49 return super.getRequestFactory(); 50 } 51 }
InterceptingClientHttpRequestFactory 这个工厂类创建的 ClientHttpRequest 类型是 InterceptingClientHttpRequest。最终 RestTemplate 的 doExecute 方法中调用 ClientHttpRequest 的 execute 方法时,就调用到了 InterceptingClientHttpRequest 中的内部类 InterceptingRequestExecution 中。
在 InterceptingRequestExecution 的 execute 方法中,首先是遍历所有拦截器对 RestTemplate 定制化,最后则通过 requestFactory 创建 ClientHttpRequest 来发起最终的 HTTP 调用。从这里可以看出,无论有没有拦截器,其实最终都会使用 requestFactory 来创建 ClientHttpRequest。
1 private class InterceptingRequestExecution implements ClientHttpRequestExecution { 2 private final Iterator<ClientHttpRequestInterceptor> iterator; 3 4 @Override 5 public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { 6 if (this.iterator.hasNext()) { 7 // 拦截器定制化 RestTemplate 8 ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); 9 return nextInterceptor.intercept(request, body, this); 10 } 11 else { 12 HttpMethod method = request.getMethod(); 13 // delegate => SimpleBufferingClientHttpRequest 14 ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method); 15 //... 16 return delegate.execute(); 17 } 18 } 19 }
这里的 requestFactory 就是前面传进来的 SimpleClientHttpRequestFactory,从它的 createRequest 方法可以看出,默认情况下,就是用的 JDK 标准 HTTP 库组件 HttpURLConnection 来进行服务间的请求通信。
1 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { 2 // JDK 标准HTTP库 HttpURLConnection 3 HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); 4 prepareConnection(connection, httpMethod.name()); 5 6 if (this.bufferRequestBody) { 7 return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); 8 } 9 else { 10 return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); 11 } 12 }
总结:
从前面的源码分析可以看出,Ribbon 默认的 HTTP 客户端是 HttpURLConnection。
在前面默认的超时配置下,可以验证出超时配置并未生效,一直阻塞3秒后才返回了结果,说明 Ribbon 默认情况下就不支持超时重试。
而且 HttpURLConnection 每次都是新创建的,请求返回来之后就关闭连接,没有连接池管理机制,网络连接的建立和关闭本身就会损耗一定的性能,所以正式环境下,最好不要使用默认的配置。
② HttpClient 配置类
另外,我们从 RibbonClientConfiguration 配置类的定义可以看到,其导入了 HttpClientConfiguration、OkHttpRibbonConfiguration、RestClientRibbonConfiguration、HttpClientRibbonConfiguration 四个 HttpClient 的配置类,通过注释也可以了解到,最后一个是默认配置类,前面三个在某些配置启用的情况下才会生效。
1 @Configuration(proxyBeanMethods = false) 2 @EnableConfigurationProperties 3 // Order is important here, last should be the default, first should be optional 4 @Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, 5 RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class }) 6 public class RibbonClientConfiguration { 7 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 @Configuration(proxyBeanMethods = false) 2 @ConditionalOnClass(name = "org.apache.http.client.HttpClient") 3 // ribbon.httpclient.enabled more文为 true 4 @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) 5 public class HttpClientRibbonConfiguration { 6 7 @RibbonClientName 8 private String name = "client"; 9 10 // RibbonLoadBalancingHttpClient 11 @Bean 12 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) 13 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 14 public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( 15 IClientConfig config, ServerIntrospector serverIntrospector, 16 ILoadBalancer loadBalancer, RetryHandler retryHandler, 17 CloseableHttpClient httpClient) { 18 RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( 19 httpClient, config, serverIntrospector); 20 client.setLoadBalancer(loadBalancer); 21 client.setRetryHandler(retryHandler); 22 Monitors.registerObject("Client_" + this.name, client); 23 return client; 24 } 25 26 // 在引入了 spring-retry 时,即可以重试的 RetryTemplate 时,就创建 RetryableRibbonLoadBalancingHttpClient 27 @Bean 28 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) 29 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") 30 public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient( 31 IClientConfig config, ServerIntrospector serverIntrospector, 32 ILoadBalancer loadBalancer, RetryHandler retryHandler, 33 LoadBalancedRetryFactory loadBalancedRetryFactory, 34 CloseableHttpClient httpClient, 35 RibbonLoadBalancerContext ribbonLoadBalancerContext) { 36 RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient( 37 httpClient, config, serverIntrospector, loadBalancedRetryFactory); 38 client.setLoadBalancer(loadBalancer); 39 client.setRetryHandler(retryHandler); 40 client.setRibbonLoadBalancerContext(ribbonLoadBalancerContext); 41 Monitors.registerObject("Client_" + this.name, client); 42 return client; 43 } 44 45 @Configuration(proxyBeanMethods = false) 46 protected static class ApacheHttpClientConfiguration { 47 48 private final Timer connectionManagerTimer = new Timer( 49 "RibbonApacheHttpClientConfiguration.connectionManagerTimer", true); 50 51 private CloseableHttpClient httpClient; 52 53 @Autowired(required = false) 54 private RegistryBuilder registryBuilder; 55 56 // HttpClient 连接池管理器 57 @Bean 58 @ConditionalOnMissingBean(HttpClientConnectionManager.class) 59 public HttpClientConnectionManager httpClientConnectionManager( 60 IClientConfig config, 61 ApacheHttpClientConnectionManagerFactory connectionManagerFactory) { 62 RibbonProperties ribbon = RibbonProperties.from(config); 63 int maxTotalConnections = ribbon.maxTotalConnections(); 64 int maxConnectionsPerHost = ribbon.maxConnectionsPerHost(); 65 int timerRepeat = ribbon.connectionCleanerRepeatInterval(); 66 long timeToLive = ribbon.poolKeepAliveTime(); 67 TimeUnit ttlUnit = ribbon.getPoolKeepAliveTimeUnits(); 68 final HttpClientConnectionManager connectionManager = connectionManagerFactory 69 .newConnectionManager(false, maxTotalConnections, 70 maxConnectionsPerHost, timeToLive, ttlUnit, registryBuilder); 71 this.connectionManagerTimer.schedule(new TimerTask() { 72 @Override 73 public void run() { 74 connectionManager.closeExpiredConnections(); 75 } 76 }, 30000, timerRepeat); 77 return connectionManager; 78 } 79 80 // HttpClient => CloseableHttpClient 81 @Bean 82 @ConditionalOnMissingBean(CloseableHttpClient.class) 83 public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, 84 HttpClientConnectionManager connectionManager, IClientConfig config) { 85 RibbonProperties ribbon = RibbonProperties.from(config); 86 Boolean followRedirects = ribbon.isFollowRedirects(); 87 Integer connectTimeout = ribbon.connectTimeout(); 88 RequestConfig defaultRequestConfig = RequestConfig.custom() 89 .setConnectTimeout(connectTimeout) 90 .setRedirectsEnabled(followRedirects).build(); 91 this.httpClient = httpClientFactory.createBuilder() 92 .setDefaultRequestConfig(defaultRequestConfig) 93 .setConnectionManager(connectionManager).build(); 94 return httpClient; 95 } 96 } 97 }
③ 默认配置下的 RestTemplate 的调用过程大致可以用下图来表示。
3、启用 RestClient
① 启用 RestClient
可以添加如下配置启用 RestClient:
1 ribbon: 2 # 关闭 httpclient 3 httpclient: 4 enabled: false 5 # 启用 RestClient 6 restclient: 7 enabled: true 8 # 启用 RestClient 9 http: 10 client: 11 enabled: true
进入 RestClientRibbonConfiguration 可以看到,只要 ribbon.http.client.enabled、ribbon.restclient.enabled 其中一个配置了启用,就可以启用 RestClient。
1 @SuppressWarnings("deprecation") 2 @Configuration(proxyBeanMethods = false) 3 // 启用条件 ConditionalOnRibbonRestClient 4 @RibbonAutoConfiguration.ConditionalOnRibbonRestClient 5 class RestClientRibbonConfiguration { 6 @RibbonClientName 7 private String name = "client"; 8 9 // RestClient 已过期 10 @Bean 11 @Lazy 12 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) 13 public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer, 14 ServerIntrospector serverIntrospector, RetryHandler retryHandler) { 15 RestClient client = new RibbonClientConfiguration.OverrideRestClient(config, serverIntrospector); 16 client.setLoadBalancer(loadBalancer); 17 client.setRetryHandler(retryHandler); 18 return client; 19 } 20 21 } 22 23 @Target({ ElementType.TYPE, ElementType.METHOD }) 24 @Retention(RetentionPolicy.RUNTIME) 25 @Documented 26 @Conditional(OnRibbonRestClientCondition.class) 27 @interface ConditionalOnRibbonRestClient { 28 } 29 30 private static class OnRibbonRestClientCondition extends AnyNestedCondition { 31 @Deprecated // remove in Edgware" 32 @ConditionalOnProperty("ribbon.http.client.enabled") 33 static class ZuulProperty { 34 } 35 36 @ConditionalOnProperty("ribbon.restclient.enabled") 37 static class RibbonProperty { 38 } 39 }
RestClient 继承自 AbstractLoadBalancerAwareClient。需要注意的是,RestClient 已经过期,所以生产环境中我们就不要启用 RestTemplate 了。
1 @Deprecated 2 public class RestClient extends AbstractLoadBalancerAwareClient<HttpRequest, HttpResponse> { 3 4 }
② LoadBalancerContext 类体系结构
负载均衡上下文 LoadBalancerContext 体系的类结构如下。可以看出,Ribbon 是支持 Feign、OkHttp、HttpClient、RestClient 的。默认配置下使用的实现类是 RibbonLoadBalancerContext。
③ RestTemplate 的 ClientHttpRequest 工厂类配置
接着看 RibbonAutoConfiguration 中有如下的配置,跟前面 RestClientRibbonConfiguration 也是一样,满足 @ConditionalOnRibbonRestClient 的条件。
可以看到,它会创建 RibbonClientHttpRequestFactory 并设置到 RestTemplate 中,也就是说,这时 RestTemplate 中的 requestFactory 就不是默认的 SimpleClientHttpRequestFactory 了,而是 RibbonClientHttpRequestFactory。
1 @Configuration(proxyBeanMethods = false) 2 @ConditionalOnClass(HttpRequest.class) 3 @ConditionalOnRibbonRestClient 4 protected static class RibbonClientHttpRequestFactoryConfiguration { 5 @Autowired 6 private SpringClientFactory springClientFactory; 7 8 @Bean 9 public RestTemplateCustomizer restTemplateCustomizer( 10 final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) { 11 // RestTemplate 设置 requestFactory 为 RibbonClientHttpRequestFactory 12 return restTemplate -> restTemplate 13 .setRequestFactory(ribbonClientHttpRequestFactory); 14 } 15 16 // ClientHttpRequest 工厂类 => RibbonClientHttpRequestFactory 17 @Bean 18 public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() { 19 return new RibbonClientHttpRequestFactory(this.springClientFactory); 20 } 21 }
而且,由于这里配置了 RestTemplateCustomizer,原本默认配置下,在 LoadBalancerAutoConfiguration 中创建 RestTemplateCustomizer 的方法就不会生效了。
LoadBalancerAutoConfiguration 中的 RestTemplateCustomizer 是向 RestTemplate 中添加 LoadBalancerInterceptor 拦截器,所以在启用了 RestClient 的情况下,原本的 LoadBalancerInterceptor 就不会生效了。
1 @Configuration(proxyBeanMethods = false) 2 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 3 static class LoadBalancerInterceptorConfig { 4 5 @Bean 6 public LoadBalancerInterceptor ribbonInterceptor( 7 LoadBalancerClient loadBalancerClient, 8 LoadBalancerRequestFactory requestFactory) { 9 return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); 10 } 11 12 @Bean 13 @ConditionalOnMissingBean 14 public RestTemplateCustomizer restTemplateCustomizer( 15 final LoadBalancerInterceptor loadBalancerInterceptor) { 16 return restTemplate -> { 17 List<ClientHttpRequestInterceptor> list = new ArrayList<>( 18 restTemplate.getInterceptors()); 19 list.add(loadBalancerInterceptor); 20 restTemplate.setInterceptors(list); 21 }; 22 } 23 }
那么 RestTemplate 的 doExecute 方法中,在调用 createRequest 方法创建 ClientHttpRequest 时,就会用 RibbonClientHttpRequestFactory 来创建,进去可以看到 ClientHttpRequest 的实际类型就是 RibbonHttpRequest。
1 public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException { 2 String serviceId = originalUri.getHost(); 3 if (serviceId == null) { 4 throw new IOException("Invalid hostname in the URI [" + originalUri.toASCIIString() + "]"); 5 } 6 IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId); 7 RestClient client = this.clientFactory.getClient(serviceId, RestClient.class); 8 HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name()); 9 10 return new RibbonHttpRequest(originalUri, verb, client, clientConfig); 11 }
调用 RibbonHttpRequest 的 execute 方法,实际组中是调用了它的 executeInternal 方法,然后最后是使用 RestClient 来发起负载均衡的调用。
1 protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { 2 try { 3 // ... 4 HttpRequest request = builder.build(); 5 // client => RestClient 6 HttpResponse response = client.executeWithLoadBalancer(request, config); 7 return new RibbonHttpResponse(response); 8 } 9 catch (Exception e) { 10 throw new IOException(e); 11 } 12 }
④ RestClient HTTP 调用
RestClient 的 executeWithLoadBalancer 实际是进入到父类 AbstractLoadBalancerAwareClient 的 executeWithLoadBalancer 方法中。
从这个方法可以知道,主要的负载均衡请求是在 LoadBalancerCommand 中的,LoadBalancerCommand 必定会通过负载均衡器 ILoadBalancer 得到一个 Server,然后通过 submit 的这个 ServerOperation 对原始URI进行重构,重构之后调用 RestClient 的 execute 发起HTTP请求。
1 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { 2 // 负载均衡命令 3 LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); 4 5 try { 6 // 发起负载均衡请求 7 return command.submit( 8 new ServerOperation<T>() { 9 @Override 10 public Observable<T> call(Server server) { 11 // 重构 URI,将服务名用 Server 的 IP 和端口替换 12 URI finalUri = reconstructURIWithServer(server, request.getUri()); 13 S requestForServer = (S) request.replaceUri(finalUri); 14 try { 15 // execute 发起调用,实际调用的是 RestClient 中的 execute 16 return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)); 17 } 18 catch (Exception e) { 19 return Observable.error(e); 20 } 21 } 22 }) 23 .toBlocking() 24 .single(); 25 } catch (Exception e) { 26 //.... 27 } 28 }
再看 RestClient 的 execute 方法,最终可以发现,RestClient 其实是使用基于 jersey 的 WebResource 来发起 HTTP 请求的。
1 private HttpResponse execute(HttpRequest.Verb verb, URI uri, 2 Map<String, Collection<String>> headers, Map<String, Collection<String>> params, 3 IClientConfig overriddenClientConfig, Object requestEntity) throws Exception { 4 // ... 5 // WebResource 是基于 jersey 封装的 HTTP 客户端组件 6 WebResource xResource = restClient.resource(uri.toString()); 7 ClientResponse jerseyResponse; 8 Builder b = xResource.getRequestBuilder(); 9 Object entity = requestEntity; 10 11 switch (verb) { 12 case GET: 13 jerseyResponse = b.get(ClientResponse.class); 14 break; 15 case POST: 16 jerseyResponse = b.post(ClientResponse.class, entity); 17 break; 18 case PUT: 19 jerseyResponse = b.put(ClientResponse.class, entity); 20 break; 21 case DELETE: 22 jerseyResponse = b.delete(ClientResponse.class); 23 break; 24 case HEAD: 25 jerseyResponse = b.head(); 26 break; 27 case OPTIONS: 28 jerseyResponse = b.options(ClientResponse.class); 29 break; 30 default: 31 throw new ClientException( 32 ClientException.ErrorType.GENERAL, 33 "You have to one of the REST verbs such as GET, POST etc."); 34 } 35 36 thisResponse = new HttpClientResponse(jerseyResponse, uri, overriddenClientConfig); 37 if (thisResponse.getStatus() == 503){ 38 thisResponse.close(); 39 throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED); 40 } 41 return thisResponse; 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 <dependency> 2 <groupId>com.squareup.okhttp3</groupId> 3 <artifactId>okhttp</artifactId> 4 </dependency>
然后添加如下配置就可以启用 OkHttp:
1 ribbon:
2 httpclient:
3 enabled: false
4 # 启用 okhttp
5 okhttp:
6 enabled: true
配置 ribbon.okhttp.enabled=true 后,在 OkHttpRibbonConfiguration 中会初始化 OkHttp 相关的组件。
但是调试之后会发现,其实它还是走的默认的流程,就是最终用 HttpURLConnection 发起 HTTP 请求,跟 httpcomponents 是一样的效果。
1 @Configuration(proxyBeanMethods = false) 2 @ConditionalOnProperty("ribbon.okhttp.enabled") 3 @ConditionalOnClass(name = "okhttp3.OkHttpClient") 4 public class OkHttpRibbonConfiguration { 5 6 @RibbonClientName 7 private String name = "client"; 8 9 @Bean 10 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) 11 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") 12 public RetryableOkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient( 13 IClientConfig config, ServerIntrospector serverIntrospector, 14 ILoadBalancer loadBalancer, RetryHandler retryHandler, 15 LoadBalancedRetryFactory loadBalancedRetryFactory, OkHttpClient delegate, 16 RibbonLoadBalancerContext ribbonLoadBalancerContext) { 17 RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient( 18 delegate, config, serverIntrospector, loadBalancedRetryFactory); 19 client.setLoadBalancer(loadBalancer); 20 client.setRetryHandler(retryHandler); 21 client.setRibbonLoadBalancerContext(ribbonLoadBalancerContext); 22 Monitors.registerObject("Client_" + this.name, client); 23 return client; 24 } 25 26 @Bean 27 @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) 28 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 29 public OkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config, 30 ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, 31 RetryHandler retryHandler, OkHttpClient delegate) { 32 OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(delegate, config, 33 serverIntrospector); 34 client.setLoadBalancer(loadBalancer); 35 client.setRetryHandler(retryHandler); 36 Monitors.registerObject("Client_" + this.name, client); 37 return client; 38 } 39 40 @Configuration(proxyBeanMethods = false) 41 protected static class OkHttpClientConfiguration { 42 43 private OkHttpClient httpClient; 44 45 @Bean 46 @ConditionalOnMissingBean(ConnectionPool.class) 47 public ConnectionPool httpClientConnectionPool(IClientConfig config, 48 OkHttpClientConnectionPoolFactory connectionPoolFactory) { 49 RibbonProperties ribbon = RibbonProperties.from(config); 50 int maxTotalConnections = ribbon.maxTotalConnections(); 51 long timeToLive = ribbon.poolKeepAliveTime(); 52 TimeUnit ttlUnit = ribbon.getPoolKeepAliveTimeUnits(); 53 return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit); 54 } 55 56 @Bean 57 @ConditionalOnMissingBean(OkHttpClient.class) 58 public OkHttpClient client(OkHttpClientFactory httpClientFactory, 59 ConnectionPool connectionPool, IClientConfig config) { 60 RibbonProperties ribbon = RibbonProperties.from(config); 61 this.httpClient = httpClientFactory.createBuilder(false) 62 .connectTimeout(ribbon.connectTimeout(), TimeUnit.MILLISECONDS) 63 .readTimeout(ribbon.readTimeout(), TimeUnit.MILLISECONDS) 64 .followRedirects(ribbon.isFollowRedirects()) 65 .connectionPool(connectionPool).build(); 66 return this.httpClient; 67 } 68 69 @PreDestroy 70 public void destroy() { 71 if (httpClient != null) { 72 httpClient.dispatcher().executorService().shutdown(); 73 httpClient.connectionPool().evictAll(); 74 } 75 } 76 77 } 78 79 }
③ 启用 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 /** 2 * Set the request factory that this accessor uses for obtaining client request handles. 3 * <p>The default is a {@link SimpleClientHttpRequestFactory} based on the JDK's own 4 * HTTP libraries ({@link java.net.HttpURLConnection}). 5 * <p><b>Note that the standard JDK HTTP library does not support the HTTP PATCH method. 6 * Configure the Apache HttpComponents or OkHttp request factory to enable PATCH.</b> 7 * @see #createRequest(URI, HttpMethod) 8 * @see SimpleClientHttpRequestFactory 9 * @see org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory 10 * @see org.springframework.http.client.OkHttp3ClientHttpRequestFactory 11 */ 12 public void setRequestFactory(ClientHttpRequestFactory requestFactory) { 13 Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null"); 14 this.requestFactory = requestFactory; 15 }
ClientHttpRequestFactory 体系类结构如下:
那 RestClient 又是如何生效的呢?通过上一节的分析可以知道,在 RibbonAutoConfiguration 中有如下的配置,这个 RibbonClientHttpRequestFactoryConfiguration 通过自定义 RestTemplateCustomizer 向 RestTemplate 设置了 requestFactory 为 RibbonClientHttpRequestFactory。
1 @Configuration(proxyBeanMethods = false) 2 @ConditionalOnClass(HttpRequest.class) 3 @ConditionalOnRibbonRestClient 4 protected static class RibbonClientHttpRequestFactoryConfiguration { 5 @Autowired 6 private SpringClientFactory springClientFactory; 7 8 @Bean 9 public RestTemplateCustomizer restTemplateCustomizer(final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) { 10 return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory); 11 } 12 13 @Bean 14 public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() { 15 return new RibbonClientHttpRequestFactory(this.springClientFactory); 16 } 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 final class HttpComponentsClientHttpRequest extends AbstractBufferingClientHttpRequest { 2 private final HttpClient httpClient; 3 private final HttpUriRequest httpRequest; 4 private final HttpContext httpContext; 5 6 @Override 7 protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { 8 // ... 9 // httpClient => CloseableHttpClient 10 HttpResponse httpResponse = this.httpClient.execute(this.httpRequest, this.httpContext); 11 return new HttpComponentsClientHttpResponse(httpResponse); 12 } 13 }
所以,如果不需要重试的功能,可以直接创建一个 HttpComponentsClientHttpRequest,并设置给 RestTemplate 即可。这样就会使用 LoadBalancerInterceptor 来做负载均衡,重构 URI,然后用 HttpComponentsClientHttpRequest 来执行请求。
1 @Bean 2 @LoadBalanced 3 public RestTemplate restTemplate() { 4 HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); 5 RestTemplate restTemplate = new RestTemplate(); 6 restTemplate.setRequestFactory(requestFactory); 7 return restTemplate; 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 package com.lyyzoo.sunny.register.ribbon.apache; 2 3 import java.io.ByteArrayInputStream; 4 import java.io.ByteArrayOutputStream; 5 import java.io.IOException; 6 import java.net.URI; 7 import java.util.ArrayList; 8 9 import com.netflix.client.config.IClientConfig; 10 import com.netflix.client.http.HttpResponse; 11 import org.springframework.cloud.netflix.ribbon.RibbonHttpRequest; 12 import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse; 13 import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient; 14 import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpRequest; 15 import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; 16 import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; 17 import org.springframework.http.HttpHeaders; 18 import org.springframework.http.HttpMethod; 19 import org.springframework.http.client.ClientHttpResponse; 20 import org.springframework.util.LinkedMultiValueMap; 21 22 /** 23 * Apache ClientHttpRequest 24 * 25 * @author bojiangzhou 26 */ 27 public class ApacheClientHttpRequest extends RibbonHttpRequest { 28 29 private final URI uri; 30 31 private final HttpMethod httpMethod; 32 33 private final String serviceId; 34 35 private final RibbonLoadBalancingHttpClient client; 36 37 private final IClientConfig config; 38 /** 39 * 是否重试 40 */ 41 private final boolean retryable; 42 43 public ApacheClientHttpRequest(URI uri, 44 HttpMethod httpMethod, 45 String serviceId, 46 RibbonLoadBalancingHttpClient client, 47 IClientConfig config, 48 boolean retryable) { 49 super(uri, null, null, config); 50 this.uri = uri; 51 this.httpMethod = httpMethod; 52 this.serviceId = serviceId; 53 this.client = client; 54 this.config = config; 55 this.retryable = retryable; 56 if (retryable && !(client instanceof RetryableRibbonLoadBalancingHttpClient)) { 57 throw new IllegalArgumentException("Retryable client must be RetryableRibbonLoadBalancingHttpClient"); 58 } 59 } 60 61 @Override 62 protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { 63 try { 64 RibbonApacheHttpRequest request = new RibbonApacheHttpRequest(buildCommandContext(headers)); 65 66 HttpResponse response; 67 if (retryable) { 68 // RetryableRibbonLoadBalancingHttpClient 使用 RetryTemplate 做负载均衡和重试 69 response = client.execute(request, config); 70 } else { 71 // RibbonLoadBalancingHttpClient 需调用 executeWithLoadBalancer 才具备负载均衡的能力 72 response = client.executeWithLoadBalancer(request, config); 73 } 74 75 return new RibbonHttpResponse(response); 76 } catch (Exception e) { 77 throw new IOException(e); 78 } 79 } 80 81 protected RibbonCommandContext buildCommandContext(HttpHeaders headers) throws IOException { 82 ByteArrayInputStream requestEntity = null; 83 ByteArrayOutputStream bufferedOutput = (ByteArrayOutputStream) this.getBodyInternal(headers); 84 if (bufferedOutput != null) { 85 requestEntity = new ByteArrayInputStream(bufferedOutput.toByteArray()); 86 bufferedOutput.close(); 87 } 88 89 return new RibbonCommandContext(serviceId, httpMethod.name(), uri.toString(), retryable, 90 headers, new LinkedMultiValueMap<>(), requestEntity, new ArrayList<>()); 91 } 92 }
2)定制 apache ClientHttpRequestFactory
创建 ApacheClientHttpRequestFactory 继承自 HttpComponentsClientHttpRequestFactory,主要是在 createRequest 方法中创建自定义的 ApacheClientHttpRequest。RibbonLoadBalancingHttpClient 可以从 SpringClientFactory 中获取。
1 package com.lyyzoo.sunny.register.ribbon.apache; 2 3 import java.io.IOException; 4 import java.net.URI; 5 6 import com.netflix.client.config.IClientConfig; 7 import org.springframework.cloud.netflix.ribbon.SpringClientFactory; 8 import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; 9 import org.springframework.http.HttpMethod; 10 import org.springframework.http.client.ClientHttpRequest; 11 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 12 import org.springframework.lang.NonNull; 13 14 /** 15 * Apache HttpComponents ClientHttpRequest factory 16 * 17 * @author bojiangzhou 18 */ 19 public class ApacheClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory { 20 21 private final SpringClientFactory clientFactory; 22 private final boolean retryable; 23 24 public ApacheClientHttpRequestFactory(SpringClientFactory clientFactory, boolean retryable) { 25 this.clientFactory = clientFactory; 26 this.retryable = retryable; 27 } 28 29 @Override 30 @NonNull 31 public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException { 32 String serviceId = originalUri.getHost(); 33 if (serviceId == null) { 34 throw new IOException( 35 "Invalid hostname in the URI [" + originalUri.toASCIIString() + "]"); 36 } 37 IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId); 38 RibbonLoadBalancingHttpClient httpClient = this.clientFactory.getClient(serviceId, RibbonLoadBalancingHttpClient.class); 39 40 return new ApacheClientHttpRequest(originalUri, httpMethod, serviceId, httpClient, clientConfig, retryable); 41 } 42 }
3)定制 apache ClientHttpRequestFactory 配置类
跟 RestClient 的配置类类似,定制 ApacheClientHttpRequestFactory 的配置类,同样的,默认启用 httpclient。在存在 RetryTemplate 时,就设置 ApacheClientHttpRequestFactory 的 retryable 参数为 true,否则为 false。
然后自定义 RestTemplateCustomizer,将 ApacheClientHttpRequestFactory 设置到 RestTemplate 中,注意这时 LoadBalancerInterceptor 就不会添加到 RestTemplate 中了。
1 package com.lyyzoo.sunny.register.ribbon.apache; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import org.apache.http.client.HttpClient; 7 import org.apache.http.protocol.HTTP; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.boot.autoconfigure.AutoConfigureAfter; 10 import org.springframework.boot.autoconfigure.AutoConfigureBefore; 11 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 12 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 13 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 14 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration; 16 import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; 17 import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; 18 import org.springframework.cloud.netflix.ribbon.SpringClientFactory; 19 import org.springframework.context.annotation.Bean; 20 import org.springframework.context.annotation.Configuration; 21 import org.springframework.http.client.ClientHttpRequestInterceptor; 22 import org.springframework.web.client.RestTemplate; 23 24 /** 25 * 26 * @author bojiangzhou 27 */ 28 @Configuration 29 @ConditionalOnClass(RestTemplate.class) 30 @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") 31 @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class}) 32 @ConditionalOnProperty(name = "ribbon.httpclient.restTemplate.enabled", matchIfMissing = true) 33 public class ApacheClientHttpRequestFactoryConfiguration { 34 35 @Configuration(proxyBeanMethods = false) 36 @ConditionalOnClass(HttpClient.class) 37 @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) 38 static class ClientHttpRequestFactoryConfiguration { 39 40 @Autowired 41 private SpringClientFactory springClientFactory; 42 43 @Bean 44 @ConditionalOnMissingBean 45 public RestTemplateCustomizer restTemplateCustomizer( 46 final ApacheClientHttpRequestFactory apacheClientHttpRequestFactory) { 47 return restTemplate -> { 48 // 设置 RequestFactory 49 restTemplate.setRequestFactory(apacheClientHttpRequestFactory); 50 51 // 添加移除 Content-Length 的拦截器,否则会报错 52 ClientHttpRequestInterceptor removeHeaderLenInterceptor = (request, bytes, execution) -> { 53 request.getHeaders().remove(HTTP.CONTENT_LEN); 54 return execution.execute(request, bytes); 55 }; 56 57 List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors()); 58 // 添加移除Content-Length请求头的Interceptor 59 interceptors.add(removeHeaderLenInterceptor); 60 restTemplate.setInterceptors(interceptors); 61 }; 62 } 63 64 @Bean 65 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 66 public ApacheClientHttpRequestFactory apacheClientHttpRequestFactory() { 67 return new ApacheClientHttpRequestFactory(springClientFactory, false); 68 } 69 70 @Bean 71 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") 72 public ApacheClientHttpRequestFactory retryableApacheClientHttpRequestFactory() { 73 return new ApacheClientHttpRequestFactory(springClientFactory, true); 74 } 75 76 77 } 78 }
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 @Bean 2 @LoadBalanced 3 public RestTemplate restTemplate() { 4 OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory(); 5 RestTemplate restTemplate = new RestTemplate(); 6 restTemplate.setRequestFactory(requestFactory); 7 return restTemplate; 8 }
② 定制 OkHttp ClientHttpRequestFactory
与定制 apache httpcomponents 类似,我这里就直接把三个类的代码放出来了。主要的差异就在于使用的 AbstractLoadBalancingClient 不同,apache 是 RibbonLoadBalancingHttpClient,okhttp 是 OkHttpLoadBalancingClient。
a) OkHttpClientHttpRequest:
1 package com.lyyzoo.sunny.register.ribbon.okhttp; 2 3 import java.io.ByteArrayInputStream; 4 import java.io.ByteArrayOutputStream; 5 import java.io.IOException; 6 import java.net.URI; 7 import java.util.ArrayList; 8 9 import com.netflix.client.config.IClientConfig; 10 import com.netflix.client.http.HttpResponse; 11 import org.springframework.cloud.netflix.ribbon.RibbonHttpRequest; 12 import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse; 13 import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; 14 import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonRequest; 15 import org.springframework.cloud.netflix.ribbon.okhttp.RetryableOkHttpLoadBalancingClient; 16 import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; 17 import org.springframework.http.HttpHeaders; 18 import org.springframework.http.HttpMethod; 19 import org.springframework.http.client.ClientHttpResponse; 20 import org.springframework.util.LinkedMultiValueMap; 21 22 /** 23 * OkHttp ClientHttpRequest 24 * 25 * @author bojiangzhou 26 */ 27 public class OkHttpClientHttpRequest extends RibbonHttpRequest { 28 29 private final URI uri; 30 31 private final HttpMethod httpMethod; 32 33 private final String serviceId; 34 35 private final OkHttpLoadBalancingClient client; 36 37 private final IClientConfig config; 38 /** 39 * 是否重试 40 */ 41 private final boolean retryable; 42 43 public OkHttpClientHttpRequest(URI uri, 44 HttpMethod httpMethod, 45 String serviceId, 46 OkHttpLoadBalancingClient client, 47 IClientConfig config, 48 boolean retryable) { 49 super(uri, null, null, config); 50 this.uri = uri; 51 this.httpMethod = httpMethod; 52 this.serviceId = serviceId; 53 this.client = client; 54 this.config = config; 55 this.retryable = retryable; 56 if (retryable && !(client instanceof RetryableOkHttpLoadBalancingClient)) { 57 throw new IllegalArgumentException("Retryable client must be RetryableOkHttpLoadBalancingClient"); 58 } 59 } 60 61 @Override 62 protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { 63 try { 64 OkHttpRibbonRequest request = new OkHttpRibbonRequest(buildCommandContext(headers)); 65 66 HttpResponse response; 67 if (retryable) { 68 // RetryableRibbonLoadBalancingHttpClient 本身具备负载均衡的能力 69 response = client.execute(request, config); 70 } else { 71 // RibbonLoadBalancingHttpClient 需调用 executeWithLoadBalancer 才具备负载均衡的能力 72 response = client.executeWithLoadBalancer(request, config); 73 } 74 75 return new RibbonHttpResponse(response); 76 } catch (Exception e) { 77 throw new IOException(e); 78 } 79 } 80 81 protected RibbonCommandContext buildCommandContext(HttpHeaders headers) throws IOException { 82 ByteArrayInputStream requestEntity = null; 83 ByteArrayOutputStream bufferedOutput = (ByteArrayOutputStream) this.getBodyInternal(headers); 84 if (bufferedOutput != null) { 85 requestEntity = new ByteArrayInputStream(bufferedOutput.toByteArray()); 86 bufferedOutput.close(); 87 } 88 89 return new RibbonCommandContext(serviceId, httpMethod.name(), uri.toString(), retryable, 90 headers, new LinkedMultiValueMap<>(), requestEntity, new ArrayList<>()); 91 } 92 }
b) OkHttpClientHttpRequestFactory
1 package com.lyyzoo.sunny.register.ribbon.okhttp; 2 3 import java.net.URI; 4 5 import com.netflix.client.config.IClientConfig; 6 import org.springframework.cloud.netflix.ribbon.SpringClientFactory; 7 import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; 8 import org.springframework.http.HttpMethod; 9 import org.springframework.http.client.ClientHttpRequest; 10 import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; 11 import org.springframework.lang.NonNull; 12 13 /** 14 * OkHttp ClientHttpRequest factory 15 * 16 * @author bojiangzhou 17 */ 18 public class OkHttpClientHttpRequestFactory extends OkHttp3ClientHttpRequestFactory { 19 20 private final SpringClientFactory clientFactory; 21 private final boolean retryable; 22 23 public OkHttpClientHttpRequestFactory(SpringClientFactory clientFactory, boolean retryable) { 24 this.clientFactory = clientFactory; 25 this.retryable = retryable; 26 } 27 28 @Override 29 @NonNull 30 public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) { 31 String serviceId = originalUri.getHost(); 32 if (serviceId == null) { 33 throw new IllegalStateException( 34 "Invalid hostname in the URI [" + originalUri.toASCIIString() + "]"); 35 } 36 IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId); 37 OkHttpLoadBalancingClient httpClient = this.clientFactory.getClient(serviceId, OkHttpLoadBalancingClient.class); 38 39 return new OkHttpClientHttpRequest(originalUri, httpMethod, serviceId, httpClient, clientConfig, retryable); 40 } 41 }
c) OkHttpClientHttpRequestFactoryConfiguration
1 package com.lyyzoo.sunny.register.ribbon.okhttp; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.boot.autoconfigure.AutoConfigureAfter; 5 import org.springframework.boot.autoconfigure.AutoConfigureBefore; 6 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 7 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration; 11 import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; 12 import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; 13 import org.springframework.cloud.netflix.ribbon.SpringClientFactory; 14 import org.springframework.context.annotation.Bean; 15 import org.springframework.context.annotation.Configuration; 16 import org.springframework.web.client.RestTemplate; 17 18 /** 19 * 20 * @author bojiangzhou 21 */ 22 @Configuration 23 @ConditionalOnClass(RestTemplate.class) 24 @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") 25 @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class}) 26 @ConditionalOnProperty(name = "ribbon.okhttp.restTemplate.enabled", matchIfMissing = true) 27 public class OkHttpClientHttpRequestFactoryConfiguration { 28 29 @Configuration(proxyBeanMethods = false) 30 @ConditionalOnProperty("ribbon.okhttp.enabled") 31 @ConditionalOnClass(name = "okhttp3.OkHttpClient") 32 static class ClientHttpRequestFactoryConfiguration { 33 34 @Autowired 35 private SpringClientFactory springClientFactory; 36 37 @Bean 38 @ConditionalOnMissingBean 39 public RestTemplateCustomizer restTemplateCustomizer( 40 final OkHttpClientHttpRequestFactory okHttpClientHttpRequestFactory) { 41 return restTemplate -> restTemplate 42 .setRequestFactory(okHttpClientHttpRequestFactory); 43 } 44 45 @Bean 46 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 47 public OkHttpClientHttpRequestFactory okHttpClientHttpRequestFactory() { 48 return new OkHttpClientHttpRequestFactory(springClientFactory, false); 49 } 50 51 @Bean 52 @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") 53 public OkHttpClientHttpRequestFactory retryableOkHttpClientHttpRequestFactory() { 54 return new OkHttpClientHttpRequestFactory(springClientFactory, true); 55 } 56 } 57 }
七、RetryTemplate 和 Ribbon 重试
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 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { 2 // 负载均衡命令 3 LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); 4 5 try { 6 return command.submit( 7 new ServerOperation<T>() { 8 @Override 9 public Observable<T> call(Server server) { 10 // 重构URI 11 URI finalUri = reconstructURIWithServer(server, request.getUri()); 12 S requestForServer = (S) request.replaceUri(finalUri); 13 try { 14 // 使用具体的 AbstractLoadBalancerAwareClient 客户端执行请求 15 return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)); 16 } 17 catch (Exception e) { 18 return Observable.error(e); 19 } 20 } 21 }) 22 .toBlocking() 23 .single(); 24 } 25 }
再看 buildLoadBalancerCommand 方法,它首先会通过 getRequestSpecificRetryHandler 方法获取请求重试处理器 RequestSpecificRetryHandler,而 getRequestSpecificRetryHandler 是一个抽象方法。这里就要重点注意了。
1 // 抽象方法,获取请求重试处理器 2 public abstract RequestSpecificRetryHandler getRequestSpecificRetryHandler(S request, IClientConfig requestConfig); 3 4 protected LoadBalancerCommand<T> buildLoadBalancerCommand(final S request, final IClientConfig config) { 5 // 获取请求重试处理器 6 RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config); 7 LoadBalancerCommand.Builder<T> builder = LoadBalancerCommand.<T>builder() 8 .withLoadBalancerContext(this) 9 .withRetryHandler(handler) 10 .withLoadBalancerURI(request.getUri()); 11 customizeLoadBalancerCommandBuilder(request, config, builder); 12 return builder.build(); 13 }
2、请求重试处理器 RequestSpecificRetryHandler
① 先了解下 RequestSpecificRetryHandler:
- 首先看它的构造方法,注意第一个参数和第二个参数,因为不同的 getRequestSpecificRetryHandler 方法实现,主要差异就在于这两个参数。
- 然后看 isRetriableException,这个方法就是 LoadBalancerCommand 用来判断异常后是否需要重试的方法,可以了解到 okToRetryOnAllErrors=true 时就可以重试,否则 okToRetryOnConnectErrors=true 才可能重试。需要注意的是就算这个方法返回 true 也不一定会重试,这跟重试次数也是有一定关系的。
1 public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) { 2 Preconditions.checkNotNull(baseRetryHandler); 3 this.okToRetryOnConnectErrors = okToRetryOnConnectErrors; 4 this.okToRetryOnAllErrors = okToRetryOnAllErrors; 5 this.fallback = baseRetryHandler; 6 if (requestConfig != null) { 7 // 在同一个Server上重试的次数 8 if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetries)) { 9 retrySameServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetries); 10 } 11 // 重试下一个Server的次数 12 if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetriesNextServer)) { 13 retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer); 14 } 15 } 16 } 17 18 @Override 19 public boolean isRetriableException(Throwable e, boolean sameServer) { 20 // 所有错误都重试 21 if (okToRetryOnAllErrors) { 22 return true; 23 } 24 // ClientException 才可能重试 25 else if (e instanceof ClientException) { 26 ClientException ce = (ClientException) e; 27 if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) { 28 return !sameServer; 29 } else { 30 return false; 31 } 32 } 33 else { 34 // 连接错误才重试,就是抛出 SocketException 异常时才重试 35 return okToRetryOnConnectErrors && isConnectionException(e); 36 } 37 }
② 不同 AbstractLoadBalancerAwareClient 的 getRequestSpecificRetryHandler 实现
a)RestClient
默认配置下,RestClient 的 getRequestSpecificRetryHandler 会走到最后一步,okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,也就是说 isRetriableException 始终返回 true,也就是说抛出异常都会重试。非GET请求时,okToRetryOnAllErrors为 false,只有连接异常时才会重试。
1 @Override 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler( 3 HttpRequest request, IClientConfig requestConfig) { 4 if (!request.isRetriable()) { 5 return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig); 6 } 7 if (this.ncc.get(CommonClientConfigKey.OkToRetryOnAllOperations, false)) { 8 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); 9 } 10 if (request.getVerb() != HttpRequest.Verb.GET) { 11 return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig); 12 } else { 13 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true 14 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); 15 } 16 }
b)AbstractLoadBalancingClient
AbstractLoadBalancingClient 中的 getRequestSpecificRetryHandler 相当于一个默认实现,默认情况下 okToRetryOnAllOperations 为 false,最后也会到最后一步,即 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,isRetriableException 始终返回 true。非GET请求时,okToRetryOnAllErrors为 false,只有连接异常时才会重试。
1 @Override 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(final S request, final IClientConfig requestConfig) { 3 // okToRetryOnAllOperations:是否所有操作都重试,默认 false 4 if (this.okToRetryOnAllOperations) { 5 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); 6 } 7 if (!request.getContext().getMethod().equals("GET")) { 8 return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig); 9 } 10 else { 11 return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); 12 } 13 }
c)RibbonLoadBalancingHttpClient
RibbonLoadBalancingHttpClient 也重载了 getRequestSpecificRetryHandler,但是它设置了 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false,isRetriableException 始终返回 false。
至此我们应该就知道为什么调用 RibbonLoadBalancingHttpClient 的 executeWithLoadBalancer 不具备重试的功能的原因了。所以启用 apache httpclient 时,RibbonLoadBalancingHttpClient 调用是不支持重试的。
1 @Override 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { 3 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false 4 return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, requestConfig); 5 }
RetryableRibbonLoadBalancingHttpClient 中也重写了 getRequestSpecificRetryHandler,同样也是设置 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false。但是在引入 spring-retry 后,它会使用 RetryTemplate 实现重试的功能。
1 @Override 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { 3 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false 4 return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null); 5 }
d)OkHttpLoadBalancingClient
OkHttpLoadBalancingClient 并没有重写 getRequestSpecificRetryHandler,所以它是使用父类 AbstractLoadBalancingClient 中的方法,也就是 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true。
所以,启用 okhttp 时,OkHttpLoadBalancingClient 是支持所有GET重试的,非GET请求则在抛出连接异常(SocketException)时支持重试。
而 RetryableOkHttpLoadBalancingClient 跟 RetryableRibbonLoadBalancingHttpClient 一样的重写方式,使用 RetryTemplate 实现重试。
1 @Override 2 public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { 3 // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false 4 return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null); 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 public Observable<T> submit(final ServerOperation<T> operation) { 2 final ExecutionInfoContext context = new ExecutionInfoContext(); 3 4 // 同一个Server重试次数 5 final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer(); 6 // 重试下一个Server的次数 7 final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer(); 8 9 // 创建一个 Observable 10 Observable<T> o = 11 // 使用 loadBalancerContext 获取 Server 12 (server == null ? selectServer() : Observable.just(server)) 13 .concatMap(new Func1<Server, Observable<T>>() { 14 @Override 15 public Observable<T> call(Server server) { 16 // 设置Server 17 context.setServer(server); 18 final ServerStats stats = loadBalancerContext.getServerStats(server); 19 20 // 创建 Observable 21 Observable<T> o = Observable 22 .just(server) 23 .concatMap(new Func1<Server, Observable<T>>() { 24 @Override 25 public Observable<T> call(final Server server) { 26 // 增加尝试次数 27 context.incAttemptCount(); 28 // ... 29 // 调用 ServerOperation 30 return operation.call(server).doOnEach(new Observer<T>() { 31 // 一些回调方法 32 }); 33 } 34 }); 35 // 重试同一个Server 36 if (maxRetrysSame > 0) 37 o = o.retry(retryPolicy(maxRetrysSame, true)); 38 return o; 39 } 40 }); 41 42 if (maxRetrysNext > 0 && server == null) 43 // 重试不同Server 44 o = o.retry(retryPolicy(maxRetrysNext, false)); 45 46 return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() { 47 @Override 48 public Observable<T> call(Throwable e) { 49 // 异常处理 50 return Observable.error(e); 51 } 52 }); 53 } 54 55 // retryPolicy 返回一个是否重试的断言 56 private Func2<Integer, Throwable, Boolean> retryPolicy(final int maxRetrys, final boolean same) { 57 return new Func2<Integer, Throwable, Boolean>() { 58 @Override 59 public Boolean call(Integer tryCount, Throwable e) { 60 // 请求拒绝异常就不允许重试 61 if (e instanceof AbortExecutionException) { 62 return false; 63 } 64 // 尝试次数是否大于最大重试次数 65 if (tryCount > maxRetrys) { 66 return false; 67 } 68 // 使用 RequestSpecificRetryHandler 判断是否重试 69 return retryHandler.isRetriableException(e, same); 70 } 71 }; 72 }
4、RetryTemplate
① spring-retry
要启用 RetryTemplate 需先引入 spring-retry:
1 <dependency> 2 <groupId>org.springframework.retry</groupId> 3 <artifactId>spring-retry</artifactId> 4 </dependency>
以 RetryableRibbonLoadBalancingHttpClient 为例,先看看它的 execute 方法,它先创建了负载均衡重试策略类 LoadBalancedRetryPolicy,然后将请求调用的逻辑封装到 RetryCallback 中,最后其实就是用 RetryTemplate 执行这个 RetryCallback,也就是说请求重试的逻辑都在 RetryTemplate 中。
1 public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception { 2 //... 3 4 // 负载均衡重试策略 RibbonLoadBalancedRetryPolicy 5 final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this); 6 7 RetryCallback<RibbonApacheHttpResponse, Exception> retryCallback = context -> { 8 // ... 9 // delegate => CloseableHttpClient 10 final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest); 11 // ... 12 // 成功 返回结果 13 return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); 14 }; 15 16 LoadBalancedRecoveryCallback<RibbonApacheHttpResponse, HttpResponse> recoveryCallback = new LoadBalancedRecoveryCallback<RibbonApacheHttpResponse, HttpResponse>()//... 17 18 return this.executeWithRetry(request, retryPolicy, retryCallback, recoveryCallback); 19 } 20 21 private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, 22 LoadBalancedRetryPolicy retryPolicy, 23 RetryCallback<RibbonApacheHttpResponse, Exception> callback, 24 RecoveryCallback<RibbonApacheHttpResponse> recoveryCallback) 25 throws Exception { 26 RetryTemplate retryTemplate = new RetryTemplate(); 27 28 // retryable => 取自 RibbonCommandContext 设置的 retryable 参数 29 boolean retryable = isRequestRetryable(request); 30 // 设置重试策略 31 retryTemplate.setRetryPolicy(retryPolicy == null || !retryable 32 ? new NeverRetryPolicy() : new RetryPolicy(request, retryPolicy, this, this.getClientName())); 33 34 BackOffPolicy backOffPolicy = loadBalancedRetryFactory.createBackOffPolicy(this.getClientName()); 35 retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); 36 37 // 利用 retryTemplate 执行请求 callback 38 return retryTemplate.execute(callback, recoveryCallback); 39 }
需要注意的是,在 executeWithRetry 中,会判断是否要重试,判断的逻辑中 getRetryable 其实就是取的 ApacheClientHttpRequest 中 executeInternal 方法里创建的 RibbonCommandContext 设置的 retryable 参数,这就和前面定制化的逻辑衔接上了。
1 private boolean isRequestRetryable(ContextAwareRequest request) { 2 if (request.getContext() == null || request.getContext().getRetryable() == null) { 3 return true; 4 } 5 return request.getContext().getRetryable(); 6 }
② RetryTemplate
进入 RetryTemplate 的 execute 方法,核心的逻辑我精简成如下代码,主要就是一个 while 循环判断是否可以重试,然后调用 retryCallback 执行请求。请求失败后,比如超时,抛出异常,就会 registerThrowable 来注册异常。
1 protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback, 2 RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException { 3 4 // retryPolicy => InterceptorRetryPolicy 5 RetryPolicy retryPolicy = this.retryPolicy; 6 BackOffPolicy backOffPolicy = this.backOffPolicy; 7 //.... 8 try { 9 // ... 10 // canRetry 判断是否重试 11 while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) { 12 try { 13 // retryCallback 调用 14 return retryCallback.doWithRetry(context); 15 } 16 catch (Throwable e) { 17 // ... 18 // 注册异常 19 registerThrowable(retryPolicy, state, context, e); 20 // ... 21 } 22 } 23 exhausted = true; 24 return handleRetryExhausted(recoveryCallback, context, state); 25 } 26 //... 27 }
看 canRetry 方法,它实际是调用了 InterceptorRetryPolicy 的 canRetry。第一次调用时,会去获取 Server;否则就用 RibbonLoadBalancedRetryPolicy 判断是否重试下一个 Server,注意它判断的逻辑是 GET 请求或者允许所有操作操作重试,且 Server 重试次数 nextServerCount 小于等于配置的 MaxAutoRetriesNextServer 。也就是说,while 循环判断的 canRetry 是重试下一个 Server 的。
1 protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) { 2 return retryPolicy.canRetry(context); 3 } 4 5 //////////// InterceptorRetryPolicy 6 public boolean canRetry(RetryContext context) { 7 LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context; 8 if (lbContext.getRetryCount() == 0 && lbContext.getServiceInstance() == null) { 9 // 获取 Server 10 lbContext.setServiceInstance(this.serviceInstanceChooser.choose(this.serviceName)); 11 return true; 12 } 13 // RibbonLoadBalancedRetryPolicy => 重试下一个Server 14 return this.policy.canRetryNextServer(lbContext); 15 } 16 17 ///////// RibbonLoadBalancedRetryPolicy 18 public boolean canRetryNextServer(LoadBalancedRetryContext context) { 19 // 判断重试下一个Server 20 return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer() 21 && canRetry(context); 22 } 23 24 public boolean canRetry(LoadBalancedRetryContext context) { 25 // GET 请求或者允许所有操作重试时,就允许重试 26 HttpMethod method = context.getRequest().getMethod(); 27 return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations(); 28 }
接着看请求失败后的注册异常 registerThrowable,它最后会向 RibbonLoadBalancedRetryPolicy 注册异常。在 RibbonLoadBalancedRetryPolicy 的 registerThrowable 方法中,如果不重试同一个Server且可以重试下一个Server,就会轮询获取下一个Server。如果可以在同一个Server上重试,sameServerCount 计数器就+1,否则重置 sameServerCount,然后 nextServerCount +1。
1 protected void registerThrowable(RetryPolicy retryPolicy, RetryState state, 2 RetryContext context, Throwable e) { 3 retryPolicy.registerThrowable(context, e); 4 registerContext(context, state); 5 } 6 7 ///////// InterceptorRetryPolicy ///////// 8 public void registerThrowable(RetryContext context, Throwable throwable) { 9 LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context; 10 lbContext.registerThrowable(throwable); 11 // RibbonLoadBalancedRetryPolicy 12 this.policy.registerThrowable(lbContext, throwable); 13 } 14 15 ///////// RibbonLoadBalancedRetryPolicy ///////// 16 public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) { 17 //... 18 // 如果不在在同一个Server 上重试且可以重试下一个Server,则重新选择一个 Server 19 if (!canRetrySameServer(context) && canRetryNextServer(context)) { 20 context.setServiceInstance(loadBalanceChooser.choose(serviceId)); 21 } 22 23 // 同一个Server重试超过设置的值后,就重置 sameServerCount 24 if (sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer() 25 && canRetry(context)) { 26 // 重置 nextServerCount 27 sameServerCount = 0; 28 // 下一个Server重试次数+1 29 nextServerCount++; 30 if (!canRetryNextServer(context)) { 31 // 不能重试下一个Server了 32 context.setExhaustedOnly(); 33 } 34 } 35 else { 36 // 同一个Server重试次数+1 37 sameServerCount++; 38 } 39 } 40 41 // 判断是否重试同一个Server 42 public boolean canRetrySameServer(LoadBalancedRetryContext context) { 43 return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer() 44 && canRetry(context); 45 } 46 47 public boolean canRetry(LoadBalancedRetryContext context) { 48 // GET 请求或者允许所有操作重试时,就允许重试 49 HttpMethod method = context.getRequest().getMethod(); 50 return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations(); 51 }
5、Ribbon 重试总结
① 首先,Ribbon 关于超时和重试的配置参数如下,这些参数也可以针对某个客户端配置:
1 ribbon: 2 # 客户端读取超时时间 3 ReadTimeout: 1000 4 # 客户端连接超时时间 5 ConnectTimeout: 1000 6 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE 7 OkToRetryOnAllOperations: false 8 # 同一个Server重试次数 9 MaxAutoRetries: 1 10 # 最多重试几个Server 11 MaxAutoRetriesNextServer: 1
② RetryTemplate 是 spring-retry 的重试组件,LoadBalancerCommand 是 Ribbon 的重试组件。它们重试的请求次数是一样的,重试逻辑也是类似,都是先重试当前 Server,再重试下一个Server,总的请求次数 = (MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)。
③ 但是有点差别的是,RetryTemplate 会判断请求方法为 GET 或者 OkToRetryOnAllOperations=true 时才允许重试,而 LoadBalancerCommand 是GET方法都可以重试,非GET方法在抛出连接异常时也可以重试。这个要注意下,一般只有GET才允许重试,因为GET是查询操作,接口是幂等的,而POST、PUT、DELETE一般是非幂等的。所以一般更建议使用 RetryTemplate,并且配置 OkToRetryOnAllOperations=false。
④ 为了提升服务间通信性能,一般可以启用 apache httpclient 或者 OkHttp,如果要启用重试功能,还需要引入 spring-retry 依赖。重试时,当前Server就不要重试了(MaxAutoRetries=0),直接重试下一个Server。
1 ribbon: 2 # 客户端读取超时时间 3 ReadTimeout: 1000 4 # 客户端连接超时时间 5 ConnectTimeout: 1000 6 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE 7 OkToRetryOnAllOperations: false 8 # 同一个Server重试次数 9 MaxAutoRetries: 0 10 # 最多重试几个Server 11 MaxAutoRetriesNextServer: 1 12 # 启用 httpclient 13 httpclient: 14 enabled: false 15 # 启用 RestClient 16 restclient: 17 enabled: false 18 # 启用 okhttp 19 okhttp: 20 enabled: true
八、Ribbon 架构图
最后,将 Ribbon 核心组件架构用两张类图总结下。
① 负载均衡器 ILoadBalancer
② 负载均衡客户端