由于遇到服务重启导致的业务中断等异常,所以计划通过kafka+eureka实现服务下线通知,来尽可能规避这类问题。
如果可以升级spring,则可以考虑nacos等更为方便的方案;
程序优化:
1.默认启用的为 PollingServerListUpdater,所以需要手动启用EurekaNotificationServerListUpdater
@Configuration
//重要,否则无法关联配置,按默认注入配置只能识别第一个接入的服务
@RibbonClients(defaultConfiguration = ConsumerRibbonClientConfig.class)
public class ConsumerRibbonClientConfig {
@Bean
public ServerListUpdater ribbonServerListUpdater() {
return new EurekaNotificationServerListUpdater();
}
}
2.需要触发PollingServerListUpdater中的更新,则需要先触发DiscoveryClient中的refreshRegistry
@Slf4j
@Component
public class EurekaRefreshUpdater {
public void refresh() {
try {
log.info("EurekaRefreshUpdater-begin");
Method method = DiscoveryClient.class.getDeclaredMethod("refreshRegistry");
method.setAccessible(true);
method.invoke(SpringUtil.getBean(DiscoveryClient.class));
log.info("EurekaRefreshUpdater-end");
} catch (Exception e) {
log.error("EurekaRefreshUpdater"+e.getMessage(), e);
e.printStackTrace();
}
}
3.服务关机listener
@Component
@KafkaListener(topics = GracefulShutdownConfigConstant.KAFKA_TOPIC)
@Slf4j
public class ServiceDowntimeListener {
@Autowired
EurekaRefreshUpdater eurekaRefreshUpdater;
@KafkaHandler
public void onMessage(@Payload String message, Acknowledgment acknowledgment) {
log.info("服务关机-接收到其他服务关机信息,message:{}", JSON.toJSONString(message));
eurekaRefreshUpdater.refresh();
acknowledgment.acknowledge();
}
}
4.自己关机发送消息通知
@Slf4j
@Component
public class GracefulShutdown {
@Value("${server.graceful.shutdown.seconds:30}")
private Integer serverGracefulShutdownSeconds;
@Autowired
EurekaClient eurekaClient;
@Value("${spring.application.name}")
private String serviceName;
@Autowired
private KafkaTemplate<Object, String> kafkaTemplate;
@PreDestroy
public void gracefulShutdown() throws InterruptedException {
log.info("gracefulShutdown wait {} seconds -- begin", serverGracefulShutdownSeconds);
eurekaClient.shutdown();
new Thread(() -> {
kafkaTemplate.send(GracefulShutdownConfigConstant.KAFKA_TOPIC,1,serviceName);
kafkaTemplate.send(GracefulShutdownConfigConstant.KAFKA_TOPIC,0,serviceName);
}).start();
Thread.sleep(serverGracefulShutdownSeconds * 1000);
log.info("gracefulShutdown shutdown");
}
}
脚本优化
在服务启动脚本中,要注意不可使用kill -9 结束服务进程,需要使用kill -15 让服务有一定的存活时间。来处理完成已有的请求。
问题
1.kafka通过group分组,如果同一组则只能收到一条信息。如果同一服务部署两个节点,则不能很好的都通知到位,所以在创建kafka通知的时候,根据服务的部署情况,利用分区+多条通知,来变相实现全广播。
./kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 3 --partitions 2 --topic shutdown_service
2.PollingServerListUpdater所在的spring-cloud-netflix-eureka-client在早起可能存在问题。具体详见:
EurekaNotificationServerListUpdater启用后出现 Connection refused (Connection refused)
3.一个服务可以正常上下线,其他服务无法通知下线,检查是否加入该注解
@RibbonClients(defaultConfiguration = ConsumerRibbonClientConfig.class)
因为EurekaNotificationServerListUpdater是与单个服务绑定,所以每次创建ribbonclient都需要重新创建一个clients。否则无法正常通知所有服务下线。
ps:
需要注意下程序版本以及kafka版本,防止某些方法不适用。
如果高版本kafka 是否可以通过指定不同的groupid来变相实现多服务通知呢?