一、序言
在分布式系统中,服务的启动和关闭是一个关键环节。特别是在生产环境中,如何优雅地关闭服务,确保正在处理的请求能够完成,避免数据丢失和用户体验的恶化,是每个开发者都需要关注的问题。
Dubbo作为阿里巴巴开源的高性能RPC框架,在优雅关闭方面做了精心的设计。它不仅要处理Provider端正在执行的请求,还要处理Consumer端正在发送的请求,同时还要考虑注册中心的注销、线程池的关闭等复杂场景。
二、应用场景
为什么需要优雅关闭?
在实际生产环境中,服务重启、升级、扩缩容都是常见操作。如果简单粗暴地kill进程,会带来以下问题:
java
// 问题场景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));
}
}
java
// 问题场景2:Consumer端正在等待响应
public class OrderController {
@Autowired
private 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("系统繁忙,请稍后重试");
}
}
}
三、源码解析
调用链路总览
XML
// 场景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()
XML
JVM关闭信号
↓
DubboShutdownHook.run()
↓
DubboBootstrap.destroy()
↓
ExtensionLoader.destroyAll()
↓
Protocol实现类.destroy()
↓
ExecutorRepository.destroyAll()
↓
ExecutorUtil.gracefulShutdown() ← 目标方法
3.1 第一层:JVM Shutdown Hook触发
java
// DubboShutdownHook.java
public class DubboShutdownHook extends Thread {
@Override
public void run() {
if (logger.isInfoEnabled()) {
logger.info("Run shutdown hook now.");
}
destroyAll(); // 开始销毁所有资源
}
public void destroyAll() {
DubboBootstrap.getInstance().destroy();
}
}
3.2 第二层:DubboBootstrap销毁
java
// DubboBootstrap.java
public class DubboBootstrap {
public void destroy() {
// 销毁所有扩展加载器
ExtensionLoader.destroyAll();
// 销毁其他组件...
destroyRegistries();
destroyProtocols();
}
}
3.3 第三层:ExtensionLoader销毁
java
// 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实现类销毁
java
// DubboProtocol.java
public class DubboProtocol extends AbstractProtocol {
@Override
public 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 {
@Override
public void destroy() {
// 销毁所有线程池
ExecutorRepository.getInstance().destroyAll();
}
}
3.5 第五层:ExecutorRepository销毁
java
// 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 第六层:线程池销毁
java
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 submitted
es.shutdown();
} catch (SecurityException ex2) {
return;
} catch (NullPointerException ex2) {
return;
}
try {
// Wait a while for existing tasks to terminate
if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
es.shutdownNow();
}
} catch (InterruptedException ex) {
es.shutdownNow();
Thread.currentThread().interrupt();
}
if (!isTerminated(es)) {
newThreadToCloseExecutor(es);
}
}
这段代码其实就是经常说到的:等待任务完成之后再销毁线程池,实现任务处理的优雅策略。
四、总结
本文介绍了经常用到的优雅关闭功能,那么有同学可能还有疑惑?那我不关闭、不优雅处理最直接的影响是啥?一句话概括就是不关闭资源可能不回收,不优雅业务可能受到影响。所以我们在实现一些功能的时候一定要做到流程闭环,在用的时候也要考虑到不用情况下的处理策略。
欢迎关注、一起交流、一起进步。