这是坚持技术写作计划(含翻译)的第54篇,定个小目标999,每周最少2篇。
网上一些大佬主要集中在如何从负载均衡里安全的把实例摘除(🔥Serverless 微服务优雅关机实践| 🏆 技术专题第七期征文)
但是实际上你会使用_shutdown断点去停掉实例么?
使用 apollo/naocs/eureka 等注册中心来操作节点上下线会更优雅一些
对比 Shutdown Hook 来看,shutdown hook的初衷是关机之前做一些善后工作(从注册中心/slb摘除自己,关掉消息队列等),但是假如我只是想切换流量而不关机(比如发版时只切流量,另外版本继续运行待命,有问题流量再切回去),使用shutdown hook钩子就不合适了
本文主要讲解 shutdown hook + nacos 上下线事件实现的真正的优雅停机。
以nacos为例,项目启动时,监听nacos 服务实例变更事件
需要判断当前实例有没有发生变化(主要是是否上下线,如果需要判断权重或者元数据,需要自行修改,此处不提供),如果没有变化认为与己无关,丢弃就可以了
如果发生变化后,使用 SpringUtil.publishEvent
广播Spring Event,这样项目内其他地方,包括但不限于消息队列,定时任务(比如xxljob)等就可以自行 @EventListener(NacosEvent.class)
监听即可
import com.alibaba.nacos.api.naming.pojo.Instance;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.context.ApplicationEvent;
import java.util.Map;
/**
* Nacos事件对象
*
* @author AnJia
*/
public class NacosEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public NacosEvent(NacosModel source) {
super(source);
}
/**
* Nacos事件对象
*
* @author AnJia
*/
@Data
@Accessors(chain = true)
public static class NacosModel {
/**
* 当前服务可用列表
*/
Map<String, Instance> instanceMap;
/**
* 本实例的实例id
*/
private String instanceId;
/**
* 本实例可用还是不可用
*/
private Boolean enable;
/**
* 是否有变更
*/
private boolean changed;
}
}
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.shunzhongkeji.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
public class NacosConfiguration {
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final String serviceName;
private static boolean ENABLE = Boolean.FALSE;
public NacosConfiguration(NacosDiscoveryProperties nacosDiscoveryProperties,
@Value("${spring.application.name}") String serviceName) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.serviceName = serviceName;
}
/**
* 项目初始化时监听nacos上下线
*
* @throws NacosException nacos报错
*/
@PostConstruct
public void init() throws NacosException {
nacosDiscoveryProperties.namingServiceInstance().subscribe(serviceName, event -> {
Map<String, Instance> instanceMap = new HashMap<>(16);
if (event instanceof NamingEvent) {
((NamingEvent) event).getInstances()
.forEach(instance -> instanceMap.put(instance.getInstanceId(), instance));
}
// nacos 实例id,示例 192.168.40.65#9090#DEFAULT#DEFAULT_GROUP@@oms
String instanceId =
nacosDiscoveryProperties.getIp() + "#"
+ nacosDiscoveryProperties.getPort() + "#"
+ nacosDiscoveryProperties.getClusterName() + "#"
+ nacosDiscoveryProperties.getGroup() + "@@"
+ nacosDiscoveryProperties.getService();
boolean changed = (ENABLE != instanceMap.containsKey(instanceId));
ENABLE = instanceMap.containsKey(instanceId);
NacosEvent.NacosModel nacosEvent = new NacosEvent.NacosModel()
.setEnable(ENABLE)
.setInstanceId(instanceId)
.setInstanceMap(instanceMap)
.setChanged(changed);
SpringUtil.publishEvent(new NacosEvent(nacosEvent));
});
}
}
@Slf4j
@Configuration
public class RabbitConfiguration {
private final RabbitListenerEndpointRegistry registry;
public RabbitConfiguration(RabbitListenerEndpointRegistry registry) {
this.registry = registry;
}
/**
* MQ 监听nacos上下线事件
*
* @param event nacos上下线事件
*/
@Async
@EventListener(NacosEvent.class)
public void healthEventChange(NacosEvent event) {
NacosModel model = (NacosModel) event.getSource();
if (model.isChanged()) {
if (Boolean.TRUE.equals(model.getEnable())) {
registry.start();
log.info("{}实例上线,启用rabbit监听", model.getInstanceId());
} else {
registry.stop();
log.info("{}实例下线,禁用rabbit监听", model.getInstanceId());
}
}
}
}
关于 ShutDownHook部分就不写了,别人写的太多了,可以参考 Spring优雅关闭之:ShutDownHook
这样就可以结合jenkins/gitlab ci等CI/CD工具实现,优雅切流量/停机了,比如发布新版本后,可以默认让新版本是下线状态,这时一点流量没有(定时任务,外部流量,消息队列都没有),扩容等操作完成后(ready状态),再上线,此时流量会同时到达新老版本,发现新版本有问题,可以把新版本下线,修复后重新发版,如果新版本没问题,可以把老版本下掉,并行运行一段时间(比如1小时),确定没问题后,老版本直接断电就行了 。
招聘小广告
山东济南的小伙伴欢迎投简历啊 加入我们 , 一起搞事情。
长期招聘,Java程序员,大数据工程师,运维工程师,前端工程师。