在自研RPC的框架中实现了服务的自动注册和发现,但是当你启动服务端后再把服务端关闭,Nacos中注册的服务信息并不会自动注销,这样就会导致客户端向Nacos请求服务时会获取已经关闭的服务端信息,最终导致连接不到服务器而调用失败。
那么就需要一种办法,在服务端关闭之前自动向 Nacos 注销服务。但是有一个问题,不知道什么时候服务器会关闭,也就不知道这个方法调用的时机,就没有办法手工去调用。这时,我们就需要钩子,由此引出本文。
Hook概念
Hook翻译成中文就是钩子的意思,在java中它表示在事件到达终点前进行监控和拦截的一种行为,Java提供注册钩子线程,在JVM进程关闭之前,会自动执行这个钩子线程。
应用场景:
- 应用程序正常退出,在退出时执行特定的业务逻辑,或者关闭资源等操作。
- 虚拟机非正常退出,比如用户按下ctrl+c、OutofMemory宕机、操作系统关闭等。在退出时执行必要的挽救措施。
Hook基本用法
这个功能利用Runtime类实现,例子代码
public class HookTest {
public static void main(String args[]){
System.out.println("程序开始执行");
try{
Thread.sleep(100);
}
catch(Exception ex){
}
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("执行钩子函数");
}
});
System.out.println("程序执行完毕,退出main");
}
}
运行结果:
程序开始执行
程序执行完毕,退出main
执行钩子函数
项目解决方法
利用钩子函数的特性,可以把项目中注销服务的方法写到关闭系统的钩子方法就可以了。
新建一个类,ShutdownHook:
public class ShutdownHook {
private final ExecutorService threadPool = ThreadPoolFactory.createDefaultThreadPool("shutdown-hook");
private static final ShutdownHook shutdownHook = new ShutdownHook();
public static ShutdownHook getShutdownHook() {
return shutdownHook;
}
public void addClearAllHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
//服务注销函数
NacosUtil.clearRegistry();
threadPool.shutdown();
}));
}
}
使用了单例模式创建其对象,在 addClearAllHook
中,Runtime
对象是 JVM 虚拟机的运行时环境,调用其 addShutdownHook
方法增加一个钩子函数,创建一个新线程调用 clearRegistry
方法完成注销工作。这个钩子函数会在 JVM 关闭之前被调用。
这样在RpcServer启动之前只需要调用 addClearAllHook
就可以注册这个钩子了,启动服务端后再关闭,就会发现 Nacos 中的注册信息都被注销了。