空闲模式

在最新的版本(1.0.0-pre1)中,gRPC 引入了 Channel 的空闲模式(idle mode)。

工作方式(初步)

在 InUseStateAggregator 中,控制 Channel (准备)进入空闲模式和退出空闲模式:

  1. final InUseStateAggregator<Object> inUseStateAggregator = new InUseStateAggregator<Object>() {
  2. @Override
  3. void handleInUse() {
  4. // 被使用时,就退出空闲模式
  5. exitIdleMode();
  6. }
  7. @Override
  8. void handleNotInUse() {
  9. if (shutdown) {
  10. return;
  11. }
  12. // 如果不被使用,就开始安排定时器准备进入空闲模式
  13. rescheduleIdleTimer();
  14. }
  15. };

idle 相关属性

  1. static final long IDLE_TIMEOUT_MILLIS_DISABLE = -1;
  2. /** 进入空闲模式的超时时间 **/
  3. private final long idleTimeoutMillis;

idle timer

  1. // 不能为null,一定会被 channelExecutor 使用
  2. @Nullable
  3. private ScheduledFuture< ?> idleModeTimerFuture;
  4. // 不能为null,一定会被 channelExecutor 使用
  5. @Nullable
  6. private IdleModeTimer idleModeTimer;

类 IdleModeTimer 实际是一个 Runnable (按说取名应该是IdleModeTimerTask才对),用于使 Channel 进入空闲模式,并关闭以下内容:

  1. Name Resolver
  2. Load Balancer
  3. subchannelPicker
  1. // 由 channelExecutor 运行
  2. private class IdleModeTimer implements Runnable {
  3. // 仅仅由 channelExecutor 修改
  4. boolean cancelled;
  5. @Override
  6. public void run() {
  7. if (cancelled) {
  8. // 检测到竞争: 这个任务在 channelExecutor 中安排在 cancelIdleTimer() 之前
  9. // 需要取消timer
  10. return;
  11. }
  12. log.log(Level.FINE, "[{0}] Entering idle mode", getLogId());
  13. // nameResolver 和 loadBalancer 保证不为null。如果他们中的任何一个是 null ,
  14. // 要不是 idleModeTimer 在没有退出空闲模式的情况下运行了两次,
  15. // 就是 shutdown() 中的任务没有取消 idleModeTimer,两者都是bug
  16. // 1. 关闭当前的 nameResolver, 并重新创建一个新的 nameResolver
  17. nameResolver.shutdown();
  18. nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
  19. // 2. 关闭当前的 loadBalancer 并置为 null
  20. loadBalancer.shutdown();
  21. loadBalancer = null;
  22. // 3. 当前 subchannelPicker 置为 null
  23. subchannelPicker = null;
  24. }
  25. }

注:没有看懂,为什么要调用 getNameResolver()方法来创建新的 nameResolver?

退出空闲模式

让 Channel 退出空闲模式,如果它处于空闲模式中。返回一个新的可以用于处理新请求的 LoadBalancer。如果 Channel 被关闭则返回null。

  1. //让 channel 退出空闲模式,如果它处于空闲模式
  2. //必须由 channelExecutor 调用
  3. void exitIdleMode() {
  4. if (shutdown.get()) {
  5. return;
  6. }
  7. if (inUseStateAggregator.isInUse()) {
  8. // 立即取消 timer,这样由于 timer 导致的竞争不会将 channel 设置为空闲
  9. // 注: 如果正在使用中,则后台会有一个idle timer,需要取消这个timer
  10. cancelIdleTimer();
  11. } else {
  12. // exitIdleMode() 可能在 inUseStateAggregator.handleNotInUse() 之外被调用,此时isInUse() == false
  13. // 在这种情况下我们依然需要安排 timer
  14. rescheduleIdleTimer();
  15. }
  16. if (loadBalancer != null) {
  17. return;
  18. }
  19. log.log(Level.FINE, "[{0}] Exiting idle mode", getLogId());
  20. // 1. 创建新的 loadBalancer
  21. LbHelperImpl helper = new LbHelperImpl(nameResolver);
  22. helper.lb = loadBalancerFactory.newLoadBalancer(helper);
  23. this.loadBalancer = helper.lb;
  24. // 2. 创建新的 NameResolverListener
  25. NameResolverListenerImpl listener = new NameResolverListenerImpl(helper);
  26. try {
  27. nameResolver.start(listener);
  28. } catch (Throwable t) {
  29. listener.onError(Status.fromThrowable(t));
  30. }
  31. }

cancelIdleTimer()的实现

  1. private void cancelIdleTimer() {
  2. if (idleModeTimerFuture != null) {
  3. // 取消feture
  4. idleModeTimerFuture.cancel(false);
  5. // 设置timer为取消
  6. idleModeTimer.cancelled = true;
  7. // 然后都设置为null
  8. idleModeTimerFuture = null;
  9. idleModeTimer = null;
  10. }
  11. }

rescheduleIdleTimer()的实现

  1. private void rescheduleIdleTimer() {
  2. if (idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) {
  3. // 这个检查控制着 空闲模式 的开启和关闭,具体分析见后面
  4. return;
  5. }
  6. // 取消现有的可能的timer,主要这个方法执行后 idleModeTimer 会被设置为null
  7. cancelIdleTimer();
  8. // 重新再构造一个timer
  9. idleModeTimer = new IdleModeTimer();
  10. // 重新再安排这个timer的执行
  11. idleModeTimerFuture = scheduledExecutor.schedule(
  12. new LogExceptionRunnable(new Runnable() {
  13. @Override
  14. public void run() {
  15. channelExecutor.executeLater(idleModeTimer).drain();
  16. }
  17. }),
  18. idleTimeoutMillis, TimeUnit.MILLISECONDS);
  19. }

控制空闲模式的开启

上面可以看到空闲模式的开启由属性 idleTimeoutMillis 控制,具体看这个属性的使用:

  1. static final long IDLE_TIMEOUT_MILLIS_DISABLE = -1;
  2. // final属性
  3. private final long idleTimeoutMillis;
  4. ManagedChannelImpl(String target, ......, long idleTimeoutMillis, ......) {
  5. ......
  6. if (idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) {
  7. this.idleTimeoutMillis = idleTimeoutMillis;
  8. } else {
  9. checkArgument(idleTimeoutMillis >= AbstractManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS, "invalid idleTimeoutMillis %s", idleTimeoutMillis);
  10. this.idleTimeoutMillis = idleTimeoutMillis;
  11. }
  12. // 有效性检查,要开启就必须 > 0,要关闭就设置为 IDLE_TIMEOUT_MILLIS_DISABLE
  13. checkArgument(idleTimeoutMillis > 0 || idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE, "invalid idleTimeoutMillis %s", idleTimeoutMillis);
  14. // 构造函数中赋值
  15. this.idleTimeoutMillis = idleTimeoutMillis;
  16. }

再往上追,控制权在构造 ManagedChannelImpl 时,只有一个调用的地方, AbstractManagedChannelImplBuilder 中的build()方法:

  1. public ManagedChannelImpl build() {
  2. ......
  3. return new ManagedChannelImpl(......,idleTimeoutMillis,......);
  4. }

属性 idleTimeoutMillis 在 build() 时传入,而这个属性的设置是通过 idleTimeout() 方法:

  1. // 默认值为 IDLE_TIMEOUT_MILLIS_DISABLE
  2. // 因此如果不调用 idleTimeout() , 默认是不会开启空闲模式的
  3. private long idleTimeoutMillis = IDLE_TIMEOUT_MILLIS_DISABLE;
  4. // 最大的空闲超时时间,比这个还大则将禁用空闲模式
  5. // 最大空闲30天,也够夸张的
  6. static final long IDLE_MODE_MAX_TIMEOUT_DAYS = 30;
  7. // 默认超时时间
  8. static final long IDLE_MODE_DEFAULT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(30);
  9. // 最小空闲时间为1秒,如果设置的比这个还短则会设置为1秒
  10. static final long IDLE_MODE_MIN_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
  11. public final T idleTimeout(long value, TimeUnit unit) {
  12. // 检查必须 > 0,
  13. checkArgument(value > 0, "idle timeout is %s, but must be positive", value);
  14. // We convert to the largest unit to avoid overflow
  15. if (unit.toDays(value) >= IDLE_MODE_MAX_TIMEOUT_DAYS) {
  16. // 检查如果超过最大值,则禁用空闲模式
  17. this.idleTimeoutMillis = ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE;
  18. } else {
  19. // 转为毫秒单位,如果比最小容许值(1秒)还小则取最小容许值
  20. this.idleTimeoutMillis = Math.max(unit.toMillis(value), IDLE_MODE_MIN_TIMEOUT_MILLIS);
  21. }
  22. return thisT();
  23. }

工作方式(深入)

前面发现空闲模式的进入和退出是由 InUseStateAggregator 来进行控制的,而 InUseStateAggregator 的工作方式请见下一节。