dubbo源码之优雅关闭
一、序言
在分布式系统中,服务的启动和关闭是一个关键环节。特别是在生产环境中,如何优雅地关闭服务,确保正在处理的请求能够完成,避免数据丢失和用户体验的恶化,是每个开发者都需要关注的问题。
Dubbo作为阿里巴巴开源的高性能RPC框架,在优雅关闭方面做了精心的设计。它不仅要处理Provider端正在执行的请求,还要处理Consumer端正在发送的请求,同时还要考虑注册中心的注销、线程池的关闭等复杂场景。
二、应用场景
为什么需要优雅关闭?
在实际生产环境中,服务重启、升级、扩缩容都是常见操作。如果简单粗暴地kill进程,会带来以下问题:
// 问题场景1:Provider端正在处理请求
public class OrderService {public void createOrder(OrderRequest request) {// 1. 参数校验validateRequest(request);// 2. 库存扣减 (如果这时进程被kill...)inventoryService.deduct(request.getProductId(), request.getQuantity());// 3. 创建订单 (这步就无法执行,导致库存扣了但订单没创建)orderRepository.save(buildOrder(request));// 4. 发送消息messageProducer.send(new OrderCreatedEvent(order));}
}
// 问题场景2:Consumer端正在等待响应
public class OrderController {@Autowiredprivate OrderService orderService;public ResponseEntity<String> createOrder(OrderRequest request) {try {// 如果Provider此时被强制关闭,会抛出异常orderService.createOrder(request);return ResponseEntity.ok("订单创建成功");} catch (Exception e) {// 用户会收到500错误return ResponseEntity.status(500).body("系统繁忙,请稍后重试");}}
}
三、源码解析
调用链路总览
// 场景1:应用正常关闭
kill -15 <pid>↓
JVM接收SIGTERM信号↓
自动执行shutdown hook↓
DubboShutdownHook.run()↓
最终调用gracefulShutdown()// 场景2:Spring Boot应用停止
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);// 应用停止时Spring会触发ApplicationContext.close()// 进而触发Dubbo的关闭流程}
}// 场景3:容器环境关闭
# Docker容器停止
docker stop <container_id>↓
发送SIGTERM信号给主进程↓
触发gracefulShutdown()
JVM关闭信号↓
DubboShutdownHook.run()↓
DubboBootstrap.destroy()↓
ExtensionLoader.destroyAll()↓
Protocol实现类.destroy()↓
ExecutorRepository.destroyAll()↓
ExecutorUtil.gracefulShutdown() ← 目标方法
3.1 第一层:JVM Shutdown Hook触发
// DubboShutdownHook.java
public class DubboShutdownHook extends Thread {@Overridepublic void run() {if (logger.isInfoEnabled()) {logger.info("Run shutdown hook now.");}destroyAll(); // 开始销毁所有资源}public void destroyAll() {DubboBootstrap.getInstance().destroy();}
}
3.2 第二层:DubboBootstrap销毁
// DubboBootstrap.java
public class DubboBootstrap {public void destroy() {// 销毁所有扩展加载器ExtensionLoader.destroyAll();// 销毁其他组件...destroyRegistries();destroyProtocols();}
}
3.3 第三层:ExtensionLoader销毁
// ExtensionLoader.java
public class ExtensionLoader<T> {public static void destroyAll() {// 销毁所有已加载的扩展实例for (ExtensionLoader<?> loader : EXTENSION_LOADERS.values()) {loader.destroy();}// 清理缓存EXTENSION_LOADERS.clear();}public void destroy() {// 销毁所有扩展实例for (Map.Entry<String, Holder<Object>> entry : cachedInstances.entrySet()) {Object instance = entry.getValue().get();if (instance != null) {try {// 如果实例实现了destroy方法,则调用destroyInstance(instance);} catch (Exception e) {logger.error("Error destroying extension " + instance, e);}}}}
}
3.4 第四层:Protocol实现类销毁
// DubboProtocol.java
public class DubboProtocol extends AbstractProtocol {@Overridepublic void destroy() {// 1. 关闭所有服务端for (String key : new ArrayList<>(serverMap.keySet())) {ProtocolServer protocolServer = serverMap.remove(key);if (protocolServer != null) {try {protocolServer.close();} catch (Throwable t) {logger.warn(t.getMessage(), t);}}}// 2. 关闭所有客户端for (String key : new ArrayList<>(referenceClientMap.keySet())) {ExchangeClient client = referenceClientMap.remove(key);if (client != null) {try {client.close();} catch (Throwable t) {logger.warn(t.getMessage(), t);}}}// 3. 销毁线程池 ← 关键调用点super.destroy();}
}// AbstractProtocol.java
public abstract class AbstractProtocol implements Protocol {@Overridepublic void destroy() {// 销毁所有线程池ExecutorRepository.getInstance().destroyAll();}
}
3.5 第五层:ExecutorRepository销毁
// ExecutorRepository.java
public class ExecutorRepository {private final Map<String, ExecutorService> data = new ConcurrentHashMap<>();public void destroyAll() {if (logger.isInfoEnabled()) {logger.info("Destroying all executors...");}// 遍历所有线程池进行销毁for (Map.Entry<String, ExecutorService> entry : data.entrySet()) {ExecutorService executor = entry.getValue();if (executor != null && !executor.isShutdown()) {try {// ← 调用目标方法ExecutorUtil.gracefulShutdown(executor, DEFAULT_SHUTDOWN_TIMEOUT);} catch (Exception e) {logger.warn("Error when destroy executor " + entry.getKey(), e);}}}// 清理缓存data.clear();}
}
3.6 第六层:线程池销毁
public static void gracefulShutdown(Executor executor, int timeout) {if (!(executor instanceof ExecutorService) || isTerminated(executor)) {return;}final ExecutorService es = (ExecutorService) executor;try {// Disable new tasks from being submittedes.shutdown();} catch (SecurityException ex2) {return;} catch (NullPointerException ex2) {return;}try {// Wait a while for existing tasks to terminateif (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {es.shutdownNow();}} catch (InterruptedException ex) {es.shutdownNow();Thread.currentThread().interrupt();}if (!isTerminated(es)) {newThreadToCloseExecutor(es);}}
这段代码其实就是经常说到的:等待任务完成之后再销毁线程池,实现任务处理的优雅策略。
四、总结
本文介绍了经常用到的优雅关闭功能,那么有同学可能还有疑惑?那我不关闭、不优雅处理最直接的影响是啥?一句话概括就是不关闭资源可能不回收,不优雅业务可能受到影响。所以我们在实现一些功能的时候一定要做到流程闭环,在用的时候也要考虑到不用情况下的处理策略。
欢迎关注、一起交流、一起进步。