面试题答案
一键面试实现思路
- 依赖关系梳理:
- 首先要清晰地梳理出各个微服务之间的依赖关系。可以通过架构文档、代码分析(例如查看Feign客户端调用等依赖方式)等手段,明确哪些服务依赖哪些其他服务,以及依赖的强弱程度。
- 绘制依赖关系图,直观展示微服务间的调用链路,以便后续制定销毁策略。
- 制定销毁顺序:
- 根据依赖关系,确定一个合理的服务销毁顺序。一般原则是先销毁被依赖少的服务,最后销毁核心的、被广泛依赖的服务。
- 对于强依赖关系,要确保依赖的服务先稳定关闭,再关闭依赖它的服务。
- 通知与协调:
- 在服务销毁时,引入一个协调机制。可以通过消息队列(如RabbitMQ、Kafka等)或者分布式协调工具(如Zookeeper)来通知相关服务即将进行销毁操作。
- 接收到通知的服务可以根据自身情况进行相应的准备工作,比如停止接收新的请求,处理完当前队列中的任务等。
- 优雅关闭:
- 利用Spring Cloud提供的优雅关闭机制。Spring Boot 2.3及以上版本支持优雅关闭,通过设置
server.shutdown=graceful
,可以在应用程序关闭时,给正在处理的请求一个完成的机会,而不是直接中断。 - 对于使用了线程池的服务,要确保线程池中的任务在关闭时被正确处理,例如设置合适的
ThreadPoolExecutor
的关闭策略,如shutdown()
和awaitTermination()
方法结合使用,等待所有任务执行完毕或者超时。
- 利用Spring Cloud提供的优雅关闭机制。Spring Boot 2.3及以上版本支持优雅关闭,通过设置
可能用到的技术手段
- Spring Boot优雅关闭:
- 在
application.properties
或application.yml
中配置server.shutdown=graceful
。 - 实现
DisposableBean
接口,在destroy
方法中编写服务销毁时的自定义逻辑,如关闭数据库连接、释放资源等。
import org.springframework.beans.factory.DisposableBean; import org.springframework.stereotype.Component; @Component public class CustomShutdownBean implements DisposableBean { @Override public void destroy() throws Exception { // 自定义销毁逻辑 } }
- 在
- 消息队列通知:
- 以RabbitMQ为例,在即将销毁的服务中向指定队列发送一条包含服务标识和销毁通知的消息。
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ShutdownNotificationService { @Autowired private RabbitTemplate rabbitTemplate; public void sendShutdownNotification(String serviceName) { rabbitTemplate.convertAndSend("shutdown-notification-queue", serviceName); } }
- 依赖该服务的其他服务监听此队列,接收到消息后进行相应处理。
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class ShutdownNotificationListener { @RabbitListener(queues = "shutdown-notification-queue") public void handleShutdownNotification(String serviceName) { // 根据serviceName判断是否依赖该服务,并做相应处理 } }
- Zookeeper协调:
- 使用Curator框架与Zookeeper交互。在服务启动时,在Zookeeper上创建一个临时节点表示服务存活。
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class ZookeeperRegistration { private final CuratorFramework curatorFramework; public ZookeeperRegistration(@Value("${zookeeper.connectString}") String connectString) { curatorFramework = CuratorFrameworkFactory.builder() .connectString(connectString) .retryPolicy(new ExponentialBackoffRetry(1000, 3)) .build(); curatorFramework.start(); } public void registerService(String servicePath) throws Exception { curatorFramework.create() .creatingParentsIfNeeded() .withMode(CreateMode.EPHEMERAL) .forPath(servicePath); } }
- 在服务销毁时,删除该节点。依赖该服务的其他服务监听Zookeeper上该节点的变化,当节点被删除时,进行相应处理。
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class ZookeeperServiceWatcher { @Autowired private CuratorFramework curatorFramework; public void watchService(String servicePath) throws Exception { PathChildrenCache cache = new PathChildrenCache(curatorFramework, servicePath, true); cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); cache.getListenable().addListener(new PathChildrenCacheListener() { @Override public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { if (event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) { // 处理服务下线逻辑 } } }); } }