Spring-Cloud-Gateway 預設整合了一些負載均衡策略,比如輪詢、隨機、基於響應時間設定權重等等。由於業務需要,需要自定義一個策略,於是花時間先研究了下原始碼。先上結論:
一、結論
-
LoadBalancerClient 介面中定義了 ServiceInstance choose(String serviceId) 方法,根據服務名獲取具體的服務例項;
-
SpringClientFactory 類中定義了 Map<String, AnnotationConfigApplicationContext> contexts,其中 key 為 serviceId,value 為 AnnotationConfigApplicationContext 物件。這意味著可以為每個服務設定不同的負載均衡策略。AnnotationConfigApplicationContext 中主要儲存了 ILoadBalancer bean,定義了負載均衡的實現;而後者又依賴於:
- IClientConfig:定義了客戶端配置,用於初始化客戶端以及負載均衡配置;
- IRule:負載均衡的策略,比如輪詢等;
- IPing:定義瞭如何確定服務例項是否正常;
- ServerList:定義了獲取伺服器列表的方法;
- ServerListFilter:根據配置或者過濾規則選擇特定的伺服器列表;
- ServerListUpdater:定義了動態更新伺服器列表的策略。
關係圖如下所示:
- AnnotationConfigApplicationContext 中確定 ILoadBalancer 的次序是:優先通過外部配置匯入,其次是配置類。
二、原始碼解析
1. LoadBalancerClient
LoadBalancerClient 是一個負載均衡客戶端,定義了 ServiceInstance choose(String serviceId) 方法,即通過服務名按照給定的策略獲取服務例項。
其介面定義如下:
public interface LoadBalancerClient extends ServiceInstanceChooser {
...
}
public interface ServiceInstanceChooser {
/**
* Chooses a ServiceInstance from the LoadBalancer for the specified service.
* @param serviceId The service ID to look up the LoadBalancer.
* @return A ServiceInstance that matches the serviceId.
*/
ServiceInstance choose(String serviceId);
}
public interface ServiceInstance {
default String getInstanceId() {
return null;
}
String getServiceId();
String getHost();
int getPort();
boolean isSecure();
URI getUri();
Map<String, String> getMetadata();
default String getScheme() {
return null;
}
}
複製程式碼
預設實現類是 RibbonLoadBalancerClient:
public class RibbonLoadBalancerClient implements LoadBalancerClient {
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
}
複製程式碼
第 9 行:getLoadBalancer(serviceId),先根據服務名獲取負載均衡策略;getServer(getLoadBalancer(serviceId), hint),然後根據策略選擇具體的服務例項。
第 18 行:根據服務名獲取負載均衡策略由 SpringClientFactory 類實現。
2. SpringClientFactory
SpringClientFactory 中儲存了每個服務的負載均衡的完整實現。
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
}
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
// contexts 儲存了每個 serviceId 對應的所有配置
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
// 返回 serviceId 對應的 AnnotationConfigApplicationContext
// 如果不存在,呼叫 createContext(String name) 方法建立
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
// 建立服務 name 對應的 AnnotationConfigApplicationContext
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 1. 先檢查外部匯入的配置 configurations 中是否包含 serviceId 服務,有的話匯入
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
// 2. 檢查外部匯入的配置中是否含有預設配置,有的話匯入
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 3. 將 PropertyPlaceholderAutoConfiguration 以及 defaultConfigType 註冊類中的 bean 註冊到 context 中(如果使用 SpringClientFactory 的預設 bean 的話,這裡的 this.defaultConfigType 指的是 RibbonClientConfiguration)
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object> singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
}
複製程式碼
3. ILoadBalancer
LoadBalancerClient 的 getServer 方法最終呼叫的是 ILoadBalancer 介面的 chooseServer 方法。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
...
}
public interface ILoadBalancer {
/**
* Choose a server from load balancer.
*/
public Server chooseServer(Object key);
...
}
複製程式碼
根據 ILoadBalancer 的 chooseServer(Object key) 方法選擇服務例項。
ILoadBalancer 介面的實現類如下圖所示,預設的 bean 為 ZoneAwareLoadBalancer。
分析下 ILoadBase 的基本要素:
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
複製程式碼
- ClientConfig:定義了客戶端配置,用於初始化客戶端以及負載均衡配置。
- IRule:負載均衡的規則(策略),比如輪詢等。
- IPing:定義瞭如何確定服務例項是否正常。
- ServerList:定義了獲取伺服器列表的方法。
- ServerListUpdater:定義了動態更新伺服器列表的策略。
- ServerListFilter:根據配置或者過濾規則選擇特定的伺服器列表,使用 consul 作為註冊中心的話預設實現為 HealthServiceServerListFilter。
在 RibbonClientConfiguration 配置類中定義了預設實現:
@Configuration
@EnableConfigurationProperties
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
@RibbonClientName
private String name = "client";
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);
}
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
return this.propertiesFactory.get(ServerListFilter.class, config, name);
}
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
@Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
@Bean
@ConditionalOnMissingBean
public RetryHandler retryHandler(IClientConfig config) {
return new DefaultLoadBalancerRetryHandler(config);
}
@Bean
@ConditionalOnMissingBean
public ServerIntrospector serverIntrospector() {
return new DefaultServerIntrospector();
}
@PostConstruct
public void preprocess() {
setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
}
static class OverrideRestClient extends RestClient {
private IClientConfig config;
private ServerIntrospector serverIntrospector;
protected OverrideRestClient(IClientConfig config,
ServerIntrospector serverIntrospector) {
super();
this.config = config;
this.serverIntrospector = serverIntrospector;
initWithNiwsConfig(this.config);
}
@Override
public URI reconstructURIWithServer(Server server, URI original) {
URI uri = updateToSecureConnectionIfNeeded(original, this.config,
this.serverIntrospector, server);
return super.reconstructURIWithServer(server, uri);
}
@Override
protected Client apacheHttpClientSpecificInitialization() {
ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
apache.getClientHandler().getHttpClient().getParams().setParameter(
ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
return apache;
}
}
}
複製程式碼
如果使用 consul 註冊中心的話,預設實現如下:
@Configuration
public class ConsulRibbonClientConfiguration {
@Autowired
private ConsulClient client;
@Value("${ribbon.client.name}")
private String serviceId = "client";
protected static final String VALUE_NOT_SET = "__not__set__";
protected static final String DEFAULT_NAMESPACE = "ribbon";
public ConsulRibbonClientConfiguration() {
}
public ConsulRibbonClientConfiguration(String serviceId) {
this.serviceId = serviceId;
}
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config, ConsulDiscoveryProperties properties) {
ConsulServerList serverList = new ConsulServerList(client, properties);
serverList.initWithNiwsConfig(config);
return serverList;
}
@Bean
@ConditionalOnMissingBean
public ServerListFilter<Server> ribbonServerListFilter() {
return new HealthServiceServerListFilter();
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing() {
return new ConsulPing();
}
@Bean
@ConditionalOnMissingBean
public ConsulServerIntrospector serverIntrospector() {
return new ConsulServerIntrospector();
}
@PostConstruct
public void preprocess() {
setProp(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId);
setProp(this.serviceId, EnableZoneAffinity.key(), "true");
}
protected void setProp(String serviceId, String suffix, String value) {
// how to set the namespace properly?
String key = getKey(serviceId, suffix);
DynamicStringProperty property = getProperty(key);
if (property.get().equals(VALUE_NOT_SET)) {
ConfigurationManager.getConfigInstance().setProperty(key, value);
}
}
protected DynamicStringProperty getProperty(String key) {
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
}
protected String getKey(String serviceId, String suffix) {
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
}
}
複製程式碼
可以看到,改寫了幾個 bean 的預設實現:
- ServerList:ConsulServerList;
- ServerListFilter:HealthServiceServerListFilter;
- IPing:ConsulPing。
繼續檢視原始碼,最終定位到由 IRule 介面的 choose(Object key) 方法來選擇服務例項。
public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
@Override
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
...
}
...
}
public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener, IClientConfigAware {
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
}
public interface IRule {
public Server choose(Object key);
}
複製程式碼
來看下 IRule 介面的實現類,
預設的 ZoneAvoidanceRule 是根據 zone 區域以及可用性來進行選擇。
ZoneAvoidanceRule 繼承了 PredicateBasedRule 類,
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// lb.getAllServers(): 獲取所有服務例項
// 然後根據 serviceId 以及負載均衡策略選擇例項
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
// 獲取所有服務例項方法如下
public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener, IClientConfigAware {
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
// 直接返回 allServerList,說明在生成 BaseLoadBalancer 物件的時候已經完成了初始化操作
@Override
public List<Server> getAllServers() {
return Collections.unmodifiableList(allServerList);
}
}
複製程式碼
看下 ZoneAwareLoadBalancer bean 是如何生成的:
public class RibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
}
public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
}
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// 初始化操作
restOfInit(clientConfig);
}
}
複製程式碼
最終定位到了 restOfInit(clientConfig) 方法,繼續分析該方法:
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
this.setEnablePrimingConnections(false);
// enableAndInitLearnNewServersFeature() 方法裡面定義了一個定時任務,會定時執行 updateListOfServers()
enableAndInitLearnNewServersFeature();
// 立刻執行
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
public void enableAndInitLearnNewServersFeature() {
LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
serverListUpdater.start(updateAction);
}
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList();
if (this.serverListImpl != null) {
// 1. 獲取所有伺服器列表,呼叫 ConsulServerList 類
// ConsulServerList 的實現是通過呼叫 consul 提供的 http 介面:/health/service/:service,通過設定 passing(指定是否需要返回所有健康檢查都通過的例項)引數為 false 返回所有例項
servers = this.serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
if (this.filter != null) {
// 2. 獲取過濾後的伺服器列表(返回通過健康檢查的服務例項)
// 呼叫 HealthServiceServerListFilter 類
servers = this.filter.getFilteredListOfServers((List)servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
}
}
// 3. 更新可用的服務例項
this.updateAllServerList((List)servers);
}
protected void updateAllServerList(List<T> ls) {
// other threads might be doing this - in which case, we pass
if (serverListUpdateInProgress.compareAndSet(false, true)) {
try {
for (T s : ls) {
s.setAlive(true); // set so that clients can start using these
// servers right away instead
// of having to wait out the ping cycle.
}
setServersList(ls);
super.forceQuickPing();
} finally {
serverListUpdateInProgress.set(false);
}
}
}
}
public class PollingServerListUpdater implements ServerListUpdater {
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
if (!isActive.get()) {
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
return;
}
try {
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
// 定義了一個執行緒組,用來執行定時任務,執行緒名為 PollingServerListUpdater-%d
// 初次執行延遲時間為 1s,之後執行間隔時間預設為 30s
// 定時任務主要是上面的 updateAction.doUpdate() 方法
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
// 定時任務的間隔時間優先從 clientConfig 中獲取,如果沒有設定的話,預設為 30s
private static long getRefreshIntervalMs(IClientConfig clientConfig) {
return clientConfig.get(CommonClientConfigKey.ServerListRefreshInterval, LISTOFSERVERS_CACHE_REPEAT_INTERVAL);
}
}
複製程式碼
以上程式碼的邏輯是:通過呼叫 restOfInit 方法對所有服務例項列表、可用服務例項列表等進行初始化;並且通過在 PollingServerListUpdater 類中設定了一個執行緒組執行定時任務,預設間隔 30s,用於更新所有服務例項以及通過健康檢查的例項列表。
以上程式碼用到了 IRule,ServerList,ServerListFilter 等介面,那麼 IPing 的作用又是什麼呢?
繼續檢視原始碼,
public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener, IClientConfigAware {
// 1. 建構函式中有個設定 setupPingTask 方法
public BaseLoadBalancer() {
this.name = DEFAULT_NAME;
this.ping = null;
setRule(DEFAULT_RULE);
setupPingTask();
lbStats = new LoadBalancerStats(DEFAULT_NAME);
}
// 2. 該方法中針對每個服務都設定了一個執行緒,定時執行 PingTask
void setupPingTask() {
// 可以跳過
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000); // 預設10s
forceQuickPing();
}
class PingTask extends TimerTask {
public void run() {
try {
new Pinger(pingStrategy).runPinger();
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error pinging", name, e);
}
}
}
class Pinger {
// 3. 具體的 PingTask 內容
public void runPinger() throws Exception {
if (!pingInProgress.compareAndSet(false, true)) {
return; // Ping in progress - nothing to do
}
Server[] allServers = null;
boolean[] results = null;
Lock allLock = null;
Lock upLock = null;
try {
allLock = allServerLock.readLock();
allLock.lock();
// 3.1 如果採用註冊中心的話,allServerList 是從註冊中心獲取的伺服器列表
allServers = allServerList.toArray(new Server[allServerList.size()]);
allLock.unlock();
int numCandidates = allServers.length;
// 3.2 檢查每個服務例項的健康狀態,健康為 true
// DynamicServerListLoadBalancer 中的 updateListOfServers 設定了 allServers 為通過健康檢查的服務例項
results = pingerStrategy.pingServers(ping, allServers);
final List<Server> newUpList = new ArrayList<Server>();
final List<Server> changedServers = new ArrayList<Server>();
for (int i = 0; i < numCandidates; i++) {
boolean isAlive = results[i];
Server svr = allServers[i];
boolean oldIsAlive = svr.isAlive();
svr.setAlive(isAlive);
// 3.3 比較和之前的狀態是否不一樣
if (oldIsAlive != isAlive) {
changedServers.add(svr);
logger.debug("LoadBalancer [{}]: Server [{}] status changed to {}",
name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
}
// 3.4 更新本地變數 newUpList
if (isAlive) {
newUpList.add(svr);
}
}
upLock = upServerLock.writeLock();
upLock.lock();
// 3.5 更新可用的服務例項列表
upServerList = newUpList;
upLock.unlock();
notifyServerStatusChangeListener(changedServers);
} finally {
pingInProgress.set(false);
}
}
}
}
複製程式碼
經過以上程式碼,得出的流程大概是:
- PollingServerListUpdater 以及 DynamicServerListLoadBalancer 負責每隔 30s 從註冊中心獲取所有的服務例項,並且篩選出狀態正常的例項加入到 allServerList 以及 upServerList 中;
- BaseLoadBalancer 中的 setupPingTask() 方法負責每隔 10s "ping" 下這些 allServerList 中的伺服器,如果狀態異常,則從 upServerList 中剔除。
如果有服務例項掛了,給人的感覺是最多會有 10s 左右的時間部分請求可能會報錯,很不幸,這是不對的。最多有可能達到 30s 左右。
深入 3.2 results = pingerStrategy.pingServers(ping, allServers) 這行原始碼,最終定位到(如果採用 ConsulPing 的話):
public class ConsulPing implements IPing {
@Override
public boolean isAlive(Server server) {
boolean isAlive = true;
if (server != null && server instanceof ConsulServer) {
ConsulServer consulServer = (ConsulServer) server;
return consulServer.isPassingChecks();
}
return isAlive;
}
}
複製程式碼
這裡的入參是 DynamicServerListLoadBalancer 返回的狀態正常的伺服器列表,那麼其執行 ConsulPing 的 isAlive 方法,其結果肯定為 true;原因在於呼叫 ConsulPing 的 isAlive 方法,並沒有執行真正意義上的 "ping" 操作(比如再次呼叫 consul 的 api 介面去確認該服務例項是否正常),而是直接返回 ConsulServer 物件中的健康檢查狀態。
三、不算問題的問題
- BaseLoadBalancer 類,為每個服務都設定了一個執行緒用於定期執行 PingTask 任務;作為閘道器,如果需要對接上百個微服務的話,就會產生上百個執行緒,而每個執行緒大部分時間處於閒置狀態。可以考慮設定一個較小的執行緒池替代。
- Consul 作為註冊中心的話,ConsulPing 的實現沒有起到期望的作用。可以考慮跳過 Ping 檢查(比如設定預設 IPing 為 DummyPing),通過縮小 PollingServerListUpdater 中定時任務的執行間隔來降低個別服務例項掉線的影響。