空闲模式
在最新的版本(1.0.0-pre1)中,gRPC 引入了 Channel 的空闲模式(idle mode)。
工作方式(初步)
在 InUseStateAggregator 中,控制 Channel (准备)进入空闲模式和退出空闲模式:
final InUseStateAggregator<Object> inUseStateAggregator = new InUseStateAggregator<Object>() {
@Override
void handleInUse() {
// 被使用时,就退出空闲模式
exitIdleMode();
}
@Override
void handleNotInUse() {
if (shutdown) {
return;
}
// 如果不被使用,就开始安排定时器准备进入空闲模式
rescheduleIdleTimer();
}
};
idle 相关属性
static final long IDLE_TIMEOUT_MILLIS_DISABLE = -1;
/** 进入空闲模式的超时时间 **/
private final long idleTimeoutMillis;
idle timer
// 不能为null,一定会被 channelExecutor 使用
@Nullable
private ScheduledFuture< ?> idleModeTimerFuture;
// 不能为null,一定会被 channelExecutor 使用
@Nullable
private IdleModeTimer idleModeTimer;
类 IdleModeTimer 实际是一个 Runnable (按说取名应该是IdleModeTimerTask才对),用于使 Channel 进入空闲模式,并关闭以下内容:
- Name Resolver
- Load Balancer
- subchannelPicker
// 由 channelExecutor 运行
private class IdleModeTimer implements Runnable {
// 仅仅由 channelExecutor 修改
boolean cancelled;
@Override
public void run() {
if (cancelled) {
// 检测到竞争: 这个任务在 channelExecutor 中安排在 cancelIdleTimer() 之前
// 需要取消timer
return;
}
log.log(Level.FINE, "[{0}] Entering idle mode", getLogId());
// nameResolver 和 loadBalancer 保证不为null。如果他们中的任何一个是 null ,
// 要不是 idleModeTimer 在没有退出空闲模式的情况下运行了两次,
// 就是 shutdown() 中的任务没有取消 idleModeTimer,两者都是bug
// 1. 关闭当前的 nameResolver, 并重新创建一个新的 nameResolver
nameResolver.shutdown();
nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
// 2. 关闭当前的 loadBalancer 并置为 null
loadBalancer.shutdown();
loadBalancer = null;
// 3. 当前 subchannelPicker 置为 null
subchannelPicker = null;
}
}
注:没有看懂,为什么要调用 getNameResolver()方法来创建新的 nameResolver?
退出空闲模式
让 Channel 退出空闲模式,如果它处于空闲模式中。返回一个新的可以用于处理新请求的 LoadBalancer。如果 Channel 被关闭则返回null。
//让 channel 退出空闲模式,如果它处于空闲模式
//必须由 channelExecutor 调用
void exitIdleMode() {
if (shutdown.get()) {
return;
}
if (inUseStateAggregator.isInUse()) {
// 立即取消 timer,这样由于 timer 导致的竞争不会将 channel 设置为空闲
// 注: 如果正在使用中,则后台会有一个idle timer,需要取消这个timer
cancelIdleTimer();
} else {
// exitIdleMode() 可能在 inUseStateAggregator.handleNotInUse() 之外被调用,此时isInUse() == false
// 在这种情况下我们依然需要安排 timer
rescheduleIdleTimer();
}
if (loadBalancer != null) {
return;
}
log.log(Level.FINE, "[{0}] Exiting idle mode", getLogId());
// 1. 创建新的 loadBalancer
LbHelperImpl helper = new LbHelperImpl(nameResolver);
helper.lb = loadBalancerFactory.newLoadBalancer(helper);
this.loadBalancer = helper.lb;
// 2. 创建新的 NameResolverListener
NameResolverListenerImpl listener = new NameResolverListenerImpl(helper);
try {
nameResolver.start(listener);
} catch (Throwable t) {
listener.onError(Status.fromThrowable(t));
}
}
cancelIdleTimer()的实现
private void cancelIdleTimer() {
if (idleModeTimerFuture != null) {
// 取消feture
idleModeTimerFuture.cancel(false);
// 设置timer为取消
idleModeTimer.cancelled = true;
// 然后都设置为null
idleModeTimerFuture = null;
idleModeTimer = null;
}
}
rescheduleIdleTimer()的实现
private void rescheduleIdleTimer() {
if (idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) {
// 这个检查控制着 空闲模式 的开启和关闭,具体分析见后面
return;
}
// 取消现有的可能的timer,主要这个方法执行后 idleModeTimer 会被设置为null
cancelIdleTimer();
// 重新再构造一个timer
idleModeTimer = new IdleModeTimer();
// 重新再安排这个timer的执行
idleModeTimerFuture = scheduledExecutor.schedule(
new LogExceptionRunnable(new Runnable() {
@Override
public void run() {
channelExecutor.executeLater(idleModeTimer).drain();
}
}),
idleTimeoutMillis, TimeUnit.MILLISECONDS);
}
控制空闲模式的开启
上面可以看到空闲模式的开启由属性 idleTimeoutMillis 控制,具体看这个属性的使用:
static final long IDLE_TIMEOUT_MILLIS_DISABLE = -1;
// final属性
private final long idleTimeoutMillis;
ManagedChannelImpl(String target, ......, long idleTimeoutMillis, ......) {
......
if (idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) {
this.idleTimeoutMillis = idleTimeoutMillis;
} else {
checkArgument(idleTimeoutMillis >= AbstractManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS, "invalid idleTimeoutMillis %s", idleTimeoutMillis);
this.idleTimeoutMillis = idleTimeoutMillis;
}
// 有效性检查,要开启就必须 > 0,要关闭就设置为 IDLE_TIMEOUT_MILLIS_DISABLE
checkArgument(idleTimeoutMillis > 0 || idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE, "invalid idleTimeoutMillis %s", idleTimeoutMillis);
// 构造函数中赋值
this.idleTimeoutMillis = idleTimeoutMillis;
}
再往上追,控制权在构造 ManagedChannelImpl 时,只有一个调用的地方, AbstractManagedChannelImplBuilder 中的build()方法:
public ManagedChannelImpl build() {
......
return new ManagedChannelImpl(......,idleTimeoutMillis,......);
}
属性 idleTimeoutMillis 在 build() 时传入,而这个属性的设置是通过 idleTimeout() 方法:
// 默认值为 IDLE_TIMEOUT_MILLIS_DISABLE
// 因此如果不调用 idleTimeout() , 默认是不会开启空闲模式的
private long idleTimeoutMillis = IDLE_TIMEOUT_MILLIS_DISABLE;
// 最大的空闲超时时间,比这个还大则将禁用空闲模式
// 最大空闲30天,也够夸张的
static final long IDLE_MODE_MAX_TIMEOUT_DAYS = 30;
// 默认超时时间
static final long IDLE_MODE_DEFAULT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(30);
// 最小空闲时间为1秒,如果设置的比这个还短则会设置为1秒
static final long IDLE_MODE_MIN_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
public final T idleTimeout(long value, TimeUnit unit) {
// 检查必须 > 0,
checkArgument(value > 0, "idle timeout is %s, but must be positive", value);
// We convert to the largest unit to avoid overflow
if (unit.toDays(value) >= IDLE_MODE_MAX_TIMEOUT_DAYS) {
// 检查如果超过最大值,则禁用空闲模式
this.idleTimeoutMillis = ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE;
} else {
// 转为毫秒单位,如果比最小容许值(1秒)还小则取最小容许值
this.idleTimeoutMillis = Math.max(unit.toMillis(value), IDLE_MODE_MIN_TIMEOUT_MILLIS);
}
return thisT();
}
工作方式(深入)
前面发现空闲模式的进入和退出是由 InUseStateAggregator 来进行控制的,而 InUseStateAggregator 的工作方式请见下一节。