优雅摘除服务
说明
我觉得这可以说是 rpc 最需要的一个功能。
因为他的实际意义非常重大,就是可以任意时间发布应用,而对业务无损。
以前发布应用都要等到半夜,很累也容易出问题。
思路
结合 java 的 ShutdownHook 以及 linux 的 kill PID 来达到这个目的。
服务提供方
停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。
然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。
服务消费方
停止时,不再发起新的调用请求,所有新的调用在客户端即报错。
然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。
代码实现
接口定义
package com.github.houbb.rpc.common.support.hook;
/**
* rpc 关闭 hook
* (1)可以添加对应的 hook 管理类
* @since 0.1.3
*/
public interface RpcShutdownHook {
/**
* 钩子函数实现
* @since 0.1.3
*/
void hook();
}
抽象实现
为了便于拓展,我们实现一个基本的抽象类。
package com.github.houbb.rpc.common.support.hook;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
/**
* rpc 关闭 hook
* (1)可以添加对应的 hook 管理类
*
* @since 0.1.3
*/
public abstract class AbstractShutdownHook implements RpcShutdownHook {
/**
* AbstractShutdownHook logger
*/
private static final Log LOG = LogFactory.getLog(AbstractShutdownHook.class);
@Override
public void hook() {
LOG.info("[Shutdown Hook] start");
this.doHook();
LOG.info("[Shutdown Hook] end");
}
/**
* 执行 hook 操作
* @since 0.1.3
*/
protected void doHook() {
//do nothing
}
}
默认实现
package com.github.houbb.rpc.common.support.hook;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.common.support.invoke.InvokeManager;
import com.github.houbb.rpc.common.support.resource.ResourceManager;
import com.github.houbb.rpc.common.support.status.enums.StatusEnum;
import com.github.houbb.rpc.common.support.status.service.StatusManager;
import com.github.houbb.rpc.common.util.Waits;
/**
* 默认的 hook 实现
* <p> project: rpc-ClientShutdownHook </p>
* <p> create on 2019/10/30 20:26 </p>
*
* @author Administrator
* @since 0.1.3
*/
public class DefaultShutdownHook extends AbstractShutdownHook {
/**
* DefaultShutdownHook logger
*/
private static final Log LOG = LogFactory.getLog(DefaultShutdownHook.class);
/**
* 状态管理类
* @since 0.1.3
*/
private final StatusManager statusManager;
/**
* 调用管理类
* @since 0.1.3
*/
private final InvokeManager invokeManager;
/**
* 资源管理类
* @since 0.1.3
*/
private final ResourceManager resourceManager;
public DefaultShutdownHook(StatusManager statusManager, InvokeManager invokeManager, ResourceManager resourceManager) {
this.statusManager = statusManager;
this.invokeManager = invokeManager;
this.resourceManager = resourceManager;
}
/**
* (1)设置 status 状态为等待关闭
* (2)查看是否 {@link InvokeManager#remainsRequest()} 是否包含请求
* (3)超时检测-可以不添加,如果难以关闭成功,直接强制关闭即可。
* (4)关闭所有线程池资源信息
* (5)设置状态为成功关闭
*/
@Override
protected void doHook() {
// 设置状态为等待关闭
statusManager.status(StatusEnum.WAIT_SHUTDOWN.code());
LOG.info("[Shutdown] set status to wait for shutdown.");
// 循环等待当前执行的请求执行完成
while (invokeManager.remainsRequest()) {
LOG.info("[Shutdown] still remains request, wait for a while.");
Waits.waits(10);
}
// 销毁所有资源
LOG.info("[Shutdown] resourceManager start destroy all resources.");
this.resourceManager.destroyAll();
LOG.info("[Shutdown] resourceManager finish destroy all resources.");
// 设置状态为关闭成功
statusManager.status(StatusEnum.SHUTDOWN_SUCCESS.code());
LOG.info("[Shutdown] set status to shutdown success.");
}
}
StatusManager/invokeManager/resourceManager 用于对状态、调用、资源的统一管理。
服务端调整
添加钩子函数:
/**
* 默认服务端注册类
*
* @author binbin.hou
* @since 0.0.6
*/
public class ServiceBs implements ServiceRegistry {
// 不变
/**
* 状态管理类
* @since 0.1.3
*/
private StatusManager statusManager;
/**
* 资源管理类
* @since 0.1.3
*/
private ResourceManager resourceManager;
/**
* 调用管理类
* @since 0.1.3
*/
private InvokeManager invokeManager;
private ServiceBs() {
// 初始化默认参数
this.serviceConfigList = new ArrayList<>();
this.rpcPort = 9527;
this.registerCenterList = Guavas.newArrayList();
// manager 初始化
this.statusManager = new DefaultStatusManager();
this.resourceManager = new DefaultResourceManager();
this.invokeManager = new DefaultInvokeManager();
}
// 不变
@Override
public ServiceRegistry expose() {
// 不变
// 4. 添加服务端钩子函数
statusManager.status(StatusEnum.ENABLE.code());
final RpcShutdownHook rpcShutdownHook = new DefaultShutdownHook(statusManager, invokeManager, resourceManager);
ShutdownHooks.rpcShutdownHook(rpcShutdownHook);
return this;
}
// 不变
}
客户端引导类
客户端添加对应的钩子函数。
/**
* 引用配置类
*
* @param <T> 接口泛型
* @author binbin.hou
* @since 0.0.6
*/
public class ClientBs<T> implements ReferenceConfig<T> {
// 不变
/**
* 状态管理类
* @since 0.1.3
*/
private StatusManager statusManager;
/**
* 资源管理类
* @since 0.1.3
*/
private ResourceManager resourceManager;
// 不变
/**
* 获取对应的引用实现
* (1)处理所有的反射代理信息-方法可以抽离,启动各自独立即可。
* (2)启动对应的长连接
*
* @return 引用代理类
* @since 0.0.6
*/
@Override
@SuppressWarnings("unchecked")
public T reference() {
// 不变
//4. 添加客户端钩子
// 设置状态为可用
proxyContext.statusManager().status(StatusEnum.ENABLE.code());
final RpcShutdownHook rpcShutdownHook = new DefaultShutdownHook(statusManager, invokeManager, resourceManager);
ShutdownHooks.rpcShutdownHook(rpcShutdownHook);
return reference;
}
// 不变
}
测试代码
注册中心
正常启动
服务端
正常启动
客户端
正常启动
shutdown
- 客户端
[INFO] [2019-11-01 23:10:51.709] [Thread-1] [c.g.h.r.c.s.h.AbstractShutdownHook.hook] - [Shutdown Hook] start
[INFO] [2019-11-01 23:10:51.710] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] set status to wait for shutdown.
[INFO] [2019-11-01 23:10:51.711] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] resourceManager start destroy all resources.
[INFO] [2019-11-01 23:10:51.711] [Thread-1] [c.g.h.r.c.s.r.i.DefaultResourceManager.destroyAll] - [Resource] destroyableList.size(): 1
[INFO] [2019-11-01 23:10:51.712] [Thread-1] [c.g.h.r.c.s.r.i.DefaultResourceManager.destroyAll] - [Resource] destroy destroyable: com.github.houbb.rpc.common.remote.netty.impl.DefaultNettyClient@10736d9
[INFO] [2019-11-01 23:10:51.713] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyClient.destroy] - [Netty Client] start close future.
[INFO] [2019-11-01 23:10:51.715] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyClient.destroy] - [Netty Client] 关闭完成
[INFO] [2019-11-01 23:10:51.727] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyClient.destroy] - [Netty Client] 线程池关闭完成
[INFO] [2019-11-01 23:10:51.728] [Thread-1] [c.g.h.r.c.s.r.i.DefaultResourceManager.destroyAll] - [Resource] clear destroyableList
[INFO] [2019-11-01 23:10:51.729] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] resourceManager finish destroy all resources.
[INFO] [2019-11-01 23:10:51.729] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] set status to shutdown success.
[INFO] [2019-11-01 23:10:51.730] [Thread-1] [c.g.h.r.c.s.h.AbstractShutdownHook.hook] - [Shutdown Hook] end
- 服务端
[INFO] [2019-11-01 23:11:11.433] [Thread-1] [c.g.h.r.c.s.h.AbstractShutdownHook.hook] - [Shutdown Hook] start
[INFO] [2019-11-01 23:11:11.434] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] set status to wait for shutdown.
[INFO] [2019-11-01 23:11:11.435] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] resourceManager start destroy all resources.
[INFO] [2019-11-01 23:11:11.436] [Thread-1] [c.g.h.r.c.s.r.i.DefaultResourceManager.destroyAll] - [Resource] destroyableList.size(): 2
[INFO] [2019-11-01 23:11:11.437] [Thread-1] [c.g.h.r.c.s.r.i.DefaultResourceManager.destroyAll] - [Resource] destroy destroyable: com.github.houbb.rpc.common.remote.netty.impl.DefaultNettyServer@2c41f4
[INFO] [2019-11-01 23:11:11.437] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyServer.destroy] - [Netty Server] 开始关闭
[INFO] [2019-11-01 23:11:11.439] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyServer.destroy] - [Netty Server] 完成关闭
[INFO] [2019-11-01 23:11:11.469] [Thread-1] [c.g.h.r.c.s.r.i.DefaultResourceManager.destroyAll] - [Resource] destroy destroyable: com.github.houbb.rpc.common.remote.netty.impl.DefaultNettyClient@12cbfa
[INFO] [2019-11-01 23:11:11.470] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyClient.destroy] - [Netty Client] start close future.
[INFO] [2019-11-01 23:11:11.471] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyClient.destroy] - [Netty Client] 关闭完成
[INFO] [2019-11-01 23:11:11.481] [Thread-1] [c.g.h.r.c.r.n.i.DefaultNettyClient.destroy] - [Netty Client] 线程池关闭完成
[INFO] [2019-11-01 23:11:11.481] [Thread-1] [c.g.h.r.c.s.r.i.DefaultResourceManager.destroyAll] - [Resource] clear destroyableList
[INFO] [2019-11-01 23:11:11.481] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] resourceManager finish destroy all resources.
[INFO] [2019-11-01 23:11:11.482] [Thread-1] [c.g.h.r.c.s.h.DefaultShutdownHook.doHook] - [Shutdown] set status to shutdown success.
[INFO] [2019-11-01 23:11:11.482] [Thread-1] [c.g.h.r.c.s.h.AbstractShutdownHook.hook] - [Shutdown Hook] end
小结
为了便于大家学习,以上源码已经开源:
我是老马,期待与你的下次重逢。