本文主要分析springcloud路由主流程。springcloud路由主要使用Ribbon这个组件。 本文分析方法是看官方文档,网上查阅资料及源码调试。关于源码调试如何找到入口类?springcloud源码体系庞大,如果你要分析其源码不单要下载springcloud的源码而且要下载其依赖。springcloud源码只是冰山一角看他源码会一头雾水。可以通过debug日志、异常点切入进入核心流程源码。
一、应用客户端路由过程分析
springcloud应用端如何路由这点是本次分析的关键,大概流程有下面几步。
- ribbon拉取eureka的服务列表获取allServerList
- ribbon通过心跳维护allServerList上下线状态
- ribbon通过获取upServerList的IP、端口实现路由
- 然后发出http请求拿到数据反序列化转化成我们需要的对象
1.1、客户端分区服务
现实场景我们会有把eureka部署到不同的分区的场景。比如,eureka一部分机器部署一个地方、另外的一部分机器部署在另外的地方。我们希望客户端先使用自己的分区,如果自己分区不可用在使用另外的分区。eureka在这块是支持的。eureka分区服务官方介绍
1.1.1、客户端如何使用分区服务
服务访问的时候优先获取分区为huawei标志的服务,如果分区huawei的服务全部down或者下线,需要等待30秒后才能访问另外一个分区的服务。调试源码发现当ribbon调一个节点发生异常他马上会调同分区的其他的节点。垮分区需要等待30秒。为什么springcloud这么设计?网上禁用eureka server缓存、提高客户端ribbon心跳评率、禁用eureka保护模式。这些和官方的推荐相悖故没有实验。
#在客户端生产者、消费者加入如下配置
##服务在数据元加入zone标识,这个等于给服务打标,灰度发布、版本控制的原理也是基于这个。zone是
eureka.instance.metadataMap.zone = huawei
##优先选择zone标记的服务
eureka.client.preferSameZoneEureka = true
//ribbon切换服务核心代码
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
1.2、客户端ribbon路由过程
1.2.1、客户端如何路由服务
eureka feign服务调用的原理是,请求url会在路由表中匹配一个服务器节点。如果这个节点服务正常在线就能正常提供服务,如果服务不在线访问就出现异常。所以服务是否能正常访问就需要路由表是否能正确的提供在线的服务节点了。
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
//选择一个合适的服务节点 demo:loadBalancerURI = http:///findeureka
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
/**路由表类 维护路由服务节点的 上线、下线、标记、查询*/
public interface ILoadBalancer {
/**
* 添加服务
*/
public void addServers(List<Server> newServers);
/**
* 选择服务
*/
public Server chooseServer(Object key);
/**
* 标记下线服务
*/
public void markServerDown(Server server);
/**
* 根据状态获取所有服务
*/
@Deprecated
public List<Server> getServerList(boolean availableOnly);
/**
* 获取可用服务
*/
public List<Server> getReachableServers();
/**
* 获取所有服务
*/
public List<Server> getAllServers();
}
1.2.2、ribbon路由表如何刷新
//1、客户端路由表缓存
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
//心跳周期周期初始化
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping) {
this.config = clientConfig;
String clientName = clientConfig.getClientName();
this.name = clientName;
int pingIntervalTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerPingInterval,
Integer.parseInt("30")));
int maxTotalPingTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
Integer.parseInt("2")));
//心跳时间初始化
setPingInterval(pingIntervalTime);
setMaxTotalPingTime(maxTotalPingTime);
}
//2、心跳定时器
void setupPingTask() {
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
//定时器默认10秒心跳一次
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
forceQuickPing();
}
//3、心跳定时器
public void runPinger() throws Exception {
if (!pingInProgress.compareAndSet(false, true)) {
return; // Ping in progress - nothing to do
}
// we are "in" - we get to Ping
Server[] allServers = null;
boolean[] results = null;
Lock allLock = null;
Lock upLock = null;
try {
/*
* The readLock should be free unless an addServer operation is
* going on...
*/
allLock = allServerLock.readLock();
allLock.lock();
allServers = allServerList.toArray(new Server[allServerList.size()]);
allLock.unlock();
int numCandidates = allServers.length;
//检查服务生命状态
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);
if (oldIsAlive != isAlive) {
changedServers.add(svr);
logger.debug("LoadBalancer [{}]: Server [{}] status changed to {}",
name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
}
if (isAlive) {
newUpList.add(svr);
}
}
upLock = upServerLock.writeLock();
upLock.lock();
upServerList = newUpList;
upLock.unlock();
notifyServerStatusChangeListener(changedServers);
} finally {
pingInProgress.set(false);
}
}
}
/*4、检查服务生命周期方法*/
public boolean isAlive(Server server) {
boolean isAlive = true;
if (server!=null && server instanceof DiscoveryEnabledServer){
DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
//5、从instanceInfo缓存中获取服务状态信息,而不是直接去请求对应服务的状态。
InstanceInfo instanceInfo = dServer.getInstanceInfo();
if (instanceInfo!=null){
InstanceStatus status = instanceInfo.getStatus();
if (status!=null){
isAlive = status.equals(InstanceStatus.UP);
}
}
}
return isAlive;
}
//验证服务生命周期的命令
http://10.xxx.10.xxx:8888/health
1.2.3、如何修改客户端ribbon心跳刷新时间
ribbon是springcloud一种路由模块,springcloud对其文档很少主要依靠其默认设置。所以我们控制ribbon要通过调试代码的方式找到点然后在修改,然后调试看修改是否符合预期。
//1、引入starter jar 为什么这么干?如果你不用starter说明你还没有了解springcloud。
//2、理由一:starter提供客户化提示的配置,让我们知道这个模块的配置参数是什么
//3、理由二、我们通过配置可以直接访问配置对应代码方便调试
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
//4、设置ribbon刷新周期
ribbon:
eager-load:
clients:
//服务提供者名称 application name
- nrmp-admin
nrmp-admin:
ribbon:
//5、心跳参数修改5秒
NFLoadBalancerPingInterval: 5