SpringCloud gateway监听 nacos服务列表变更事件 刷新负载均衡缓存 服务平滑上下线
一、报错
io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused:
二、原因
系k8s缩容,集群中一个服务下线,而gateway感知存在延迟,访问了已经下线的服务ip导致报错。
三、分析
- 原理是在gateway服务中添加一个监听器,监听nacos服务列表变更事件,当服务列表变更时调用负载均衡缓存刷新接口即可。
- gateway有两种负载均衡模式(
ribbon
,bl
),由于没有配置
spring.cloud.loadbalancer.ribbon.enabled=false
默认使用的是ribbon
。
解决
- 确保gateway项目引入了nacos客户端依赖,因为需要用到
com.alibaba.nacos.client.naming.event.InstancesChangeEvent
,注意版本问题,低版本的客户端没有这个类。
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.1</version>
</dependency>
- 添加监听器
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Optional;
@Component
@Slf4j
public class NacosInstancesChangeEventListener extends Subscriber<InstancesChangeEvent> {
@Resource
private SpringClientFactory springClientFactory;
@PostConstruct
public void init() {
// 注册当前自定义的订阅者以获取通知
NotifyCenter.registerSubscriber(this);
}
@Override
public void onEvent(InstancesChangeEvent event) {
log.info("nacos实例变更事件:{}", JSONUtil.toJsonStr(event));
String serviceName = event.getServiceName();
// serviceName 格式为 groupName@@name
String split = Constants.SERVICE_INFO_SPLITER;
if (serviceName.contains(split)) {
serviceName = serviceName.substring(serviceName.indexOf(split) + split.length());
}
log.info("{}服务实例变更", serviceName);
ILoadBalancer lb = springClientFactory.getLoadBalancer(serviceName);
if(ObjectUtil.isNotEmpty(lb)){
log.info("当前负载均衡缓存服务列表:{}",JSONUtil.toJsonStr(lb.getAllServers()));
log.info("======更新服务列表开始=====");
// 手动更新服务列表
// 如果自定义负载均衡方式则将默认的 ZoneAwareLoadBalancer 替换为自己的实现即可
Optional.ofNullable(lb)
.ifPresent(loadBalancer -> ((ZoneAwareLoadBalancer<?>) loadBalancer).updateListOfServers());
log.info("======更新服务列表结束=====");
log.info("更新后的负载均衡缓存列表:{}",JSONUtil.toJsonStr(lb.getAllServers()));
}
}
@Override
public Class<? extends Event> subscribeType() {
return InstancesChangeEvent.class;
}
}