标签:this red ... vat logs header get rabl des
Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题
主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题,同时最近又看了一下 Dubbo 的源码,想重新写一下 Dubbo 相关的文章。
对于一个 java 应用,如果想在关闭应用时,执行一些释放资源的操作一般是通过注册一个 ShutDownHook ,当关闭应用时,不是调用 kill -9 命令来直接终止应用,而是通过调用 kill -15 命令来触发这个 ShutDownHook 进行停机前的释放资源操作。
对于 Dubbo 来说,需要停机前执行的操作包括两部分:
而何为优雅停机呢?就是在集群环境下,有一个应用停机,并不会出现异常。下面来看一下 Dubbo 是怎么做的。
Duubo 在 AbstractConfig 的静态构造函数中注册了 JVM 的 ShutDownHook,而 ShutdownHook 主要是调用 ProtocolConfig.destroyAll() ,源码如下:
static {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
if (logger.isInfoEnabled()) {
logger.info("Run shutdown hook now.");
}
ProtocolConfig.destroyAll();
}
}, "DubboShutdownHook"));
}
先看一下 ProtocolConfig.destroyAll() 源码:
public static void destroyAll() {
if (!destroyed.compareAndSet(false, true)) {
return;
}
AbstractRegistryFactory.destroyAll(); //1.
// Wait for registry notification
try {
Thread.sleep(ConfigUtils.getServerShutdownTimeout()); //2.
} catch (InterruptedException e) {
logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
}
ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
for (String protocolName : loader.getLoadedExtensions()) {
try {
Protocol protocol = loader.getLoadedExtension(protocolName);
if (protocol != null) {
protocol.destroy(); //3.
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
ProtocolConfig.destroyAll() 有三个比较重要的操作:
而在第3步会在 Exchange 层 对所有打开的连接进行判断其有没有正在执行的请求,如果有会自旋 Sleep 直到设置的 ServerShutdownTimeout 时间或者已经没有正在执行的请求了才会关闭连接,源码如下:
public void close(final int timeout) {
startClose();
if (timeout > 0) {
final long max = (long) timeout;
final long start = System.currentTimeMillis();
if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
sendChannelReadOnlyEvent();
}
while (HeaderExchangeServer.this.isRunning() //判断是否还有正在处理的请求
&& System.currentTimeMillis() - start < max) { //判断是否超时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
}
doClose();
server.close(timeout); //正在的关闭连接
}
## 在 SpringBoot 应用中存在的问题
简单的描述一下问题:就是在应用停机时,瞬间会产生大量的报错,比如拿到的数据库连接已经关闭等问题。 其实一看就知道是在停机时还存在正在处理的请求,而这些请求所需要的资源被 Spring 容器所关闭导致的。原来在SpringBoot 启动时会在 refreshContext 操作也注册一个 ShotdownHook 来关闭Spring容器。
private void refreshContext(ConfigurableApplicationContext context) {
this.refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
}
}
}
而要解决这个问题就需要取消掉这个 ShutDownHook ,然后再 Dubbo 优雅停机执行后关闭 Spring 容器。具体的修改如下:
public static void main(String[] args) {
SpringApplication app = new SpringApplication(XxxApplication.class);
app.setRegisterShutdownHook(false);
app.run(args);
}
public class SpringShutdownHook {
private static final Logger logger = LoggerFactory.getLogger(SpringShutdownHook.class);
@Autowired
private ConfigurableApplicationContext configurableApplicationContext;
public SpringShutdownHook() {
}
@PostConstruct
public void registerShutdownHook() {
logger.info("[SpringShutdownHook] Register ShutdownHook....");
Thread shutdownHook = new Thread() {
public void run() {
try {
int timeOut = ConfigUtils.getServerShutdownTimeout();
SpringShutdownHook.logger.info("[SpringShutdownHook] Application need sleep {} seconds to wait Dubbo shutdown", (double)timeOut / 1000.0D);
Thread.sleep((long)timeOut);
SpringShutdownHook.this.configurableApplicationContext.close();
SpringShutdownHook.logger.info("[SpringShutdownHook] ApplicationContext closed, Application shutdown");
} catch (InterruptedException var2) {
SpringShutdownHook.logger.error(var2.getMessage(), var2);
}
}
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
}
Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题
标签:this red ... vat logs header get rabl des
原文地址:https://www.cnblogs.com/javanoob/p/dubbo_graceful_shutdown.html