dubbo优雅停机的实现,首先主要依赖于jvm的ShutdownHook钩子函数,例如dubbo 2.5.x版本,在AbstractConfig中定义了:
static {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
if (logger.isInfoEnabled()) {
logger.info("Run shutdown hook now.");
}
ProtocolConfig.destroyAll();
}
}, "DubboShutdownHook"));
}
在静态块里面注册了一个关闭钩子,当jvm准备关闭时(tomcat shutdown命令、kill pid等),会自动触发注册好的关闭钩子,执行ProtocolConfig.destroyAll()方法,此方法主要做了从注册中心(比如zookeeper)解注册、关闭provider、关闭consumer三件事情。
1、第一步解注册,consumer和provider都会在zookeeper注册临时节点,在停机时首先调用了AbstractRegistryFactory的destroyAll():
public static void destroyAll() {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Close all registries " + getRegistries());
}
// Lock up the registry shutdown process
LOCK.lock();
try {
for (Registry registry : getRegistries()) {
try {
registry.destroy();
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
}
REGISTRIES.clear();
} finally {
// Release the lock
LOCK.unlock();
}
}
在该方法中首先拿到本机所有的registry(dubbo官方的角色说明:registry是服务注册与发现的注册中心。为了便于理解,registry可以不严谨地看做本机整体服务注册到注册中心的信息),然后调用registry的destroy()方法执行解注册。值得一提的是,dubbo里面使用了很多设计模式,整个注册中心的逻辑部分使用了模板模式:
比如我们使用的zookeeper,那么会来到ZookeeperRegistry.destroy(),
@Override
public void destroy() {
super.destroy();
try {
zkClient.close();
} catch (Exception e) {
logger.warn("Failed to close zookeeper client " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
里面依次调用其父类FailbackRegistry及其祖父类AbstractRegistry.destroy(),在其中会执行
for (URL url : new HashSet<>(getRegistered())) {
if (url.getParameter(DYNAMIC_KEY, true)) {
try {
unregister(url);
if (logger.isInfoEnabled()) {
logger.info("Destroy unregister url " + url);
}
} catch (Throwable t) {
logger.warn("Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
}
}
}
这么做是为了在虚基类中执行一些公共处理,不依赖于具体注册中心,比如将 private final Set
,注册URL Set中对应的URL(provider和consumer)删掉。
以及在ZookeeperRegistry中:
@Override
public void doUnregister(URL url) {
try {
zkClient.delete(toUrlPath(url));
} catch (Throwable e) {
throw new RpcException("Failed to unregister " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
删掉在zookeeper中添加的节点。以及前面提到的zkClient.close();关闭zk客户端。
解注册的过程基本如此。
2、从注册中心解注册之后,本机跟注册中心之间的连接就断开了,接下来销毁所有的protocol。protocol属于远程调用层,封装 RPC 调用。
private void destroyProtocols() {
//获取Protocol的扩展点实现类
ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
for (String protocolName : loader.getLoadedExtensions()) {
try {
Protocol protocol = loader.getLoadedExtension(protocolName);
if (protocol != null) {
protocol.destroy();
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
通过循环遍历得到所有protocol扩展点实现,包括实际外部通信协议(这里是dubbo)、injvm(本机通信)、registry protocol,这里只重点关注dubbo protocol,来到DubboProtocol.destroy():
public void destroy() {
for (String key : new ArrayList<String>(serverMap.keySet())) {
ExchangeServer server = serverMap.remove(key);
if (server != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo server: " + server.getLocalAddress());
}
server.close(getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
for (String key : new ArrayList<String>(referenceClientMap.keySet())) {
ExchangeClient client = referenceClientMap.remove(key);
if (client != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress());
}
client.close(getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
for (String key : new ArrayList<String>(ghostClientMap.keySet())) {
ExchangeClient client = ghostClientMap.remove(key);
if (client != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress());
}
client.close(getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
stubServiceMethodsMap.clear();
super.destroy();
}
这里首先做的服务端的关闭,调用ExchangeServer接口的实现类HeaderExchangeServer
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)) {
logger.info("============getUrl():" + getUrl());
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);
}
如果设置了发送readonly事件,则先执行sendChannelReadOnlyEvent();这是一个单向请求,由服务端发给消费端,告诉consumer我要关闭channel了,只能从channle中读未读取完的内容。
HeaderExchangeServer.this.isRunning()判断是否还有客户端持有连接,如果有的话sleep 10ms然后再一直尝试直到能够关闭(此处2.5.x版本存在bug,导致不能正确判断客户端持有连接,因此2.5版本里面需要在解注册后sleep 10s后才执行protocol的关闭)。
在 doClose()里面:
private void doClose() {
if (!closed.compareAndSet(false, true)) {
return;
}
stopHeartbeatTimer();
try {
scheduled.shutdown();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
执行关闭服务端客户端之间TCP长连接的心跳检测。
在server.close(timeout);方法:
仍然使用模板模式,首先AbstractServer关闭服务线程池:
public void close(int timeout) {
ExecutorUtil.gracefulShutdown(executor, timeout);
close();
}
public void close() {
if (logger.isInfoEnabled()) {
logger.info("Close " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
ExecutorUtil.shutdownNow(executor, 100);
try {
super.close();
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
try {
doClose();
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}
然后doClose();到实际的实现类(比如NettyServer)关闭了实际建立的连接。
到这里服务端的关闭完成,接下来关闭客户端,由于客户端与服务端关闭十分类似,不再赘述。
整个dubbo服务关闭的过程,可大致归纳为:
待续:dubbo注册jvm关闭钩子时存在的问题。