alibaba / spring-cloud-alibaba

Spring Cloud Alibaba provides a one-stop solution for application development for the distributed solutions of Alibaba middleware.
https://sca.aliyun.com
Apache License 2.0
27.97k stars 8.34k forks source link

是否提供ribbon基于事件机制的ServerListUpdater #1554

Open joyuce opened 4 years ago

joyuce commented 4 years ago

使用feign调用服务时,ribbon默认使用PollingServerListUpdater定时任务更新服务,eureka有对应的EurekaNotificationServerListUpdater基于事件的更新,目前nacos是否有提供对应的基于事件的ServerListUpdater,可以更快的感知到服务的上下线。

fangjian0423 commented 4 years ago

welcome to contribute it!

RainElohim commented 4 years ago

这个功能确实很急需啊

phantomedc commented 3 years ago

is there any progress?

HaojunRen commented 3 years ago

如果仅仅基于Ribbon,可以自行扩展,可以参考https://gitee.com/nepxion/Discovery/blob/6.x.x/discovery-plugin-register-center/discovery-plugin-register-center-starter-nacos/src/main/java/com/nepxion/discovery/plugin/registercenter/nacos/decorator/NacosServerListDecorator.java

getUpdatedListOfServers上触发出一个事件即可,仅供参考

phantomedc commented 3 years ago

最终我基于namingService subscribe API重写了ServerListUpdater接口,基本实现基于事件机制的ServerListUpdater,但是在实测中发现,nacos的事件推送很不稳定,在服务节点下线后基本需要5-10秒以后client才能收到NamingEvent,作为事件订阅本身来讲,这个延迟是否可以接受?是否还有优化空间?


顺着eventListener的源码往上扒了扒,发现Nacos的事件订阅实际上是基于pull模式的“伪订阅”…,然后去阿里云看了一下MSE的服务下线优势…大概明白为啥Eureka有基于事件监听机制的ServerListUpdater而Nacos没有了…

woshishitou commented 3 years ago

最终我基于namingService subscribe API重写了ServerListUpdater接口,基本实现基于事件机制的ServerListUpdater,但是在实测中发现,nacos的事件推送很不稳定,在服务节点下线后基本需要5-10秒以后client才能收到NamingEvent,作为事件订阅本身来讲,这个延迟是否可以接受?是否还有优化空间?

顺着eventListener的源码往上扒了扒,发现Nacos的事件订阅实际上是基于pull模式的“伪订阅”…,然后去阿里云看了一下MSE的服务下线优势…大概明白为啥Eureka有基于事件监听机制的ServerListUpdater而Nacos没有了…

您好,可以麻烦您提供下基于事件机制的ServerListUpdater的源码吗?谢谢。

ruansheng8 commented 2 years ago

监听器实现参考

/**
 * @author: ruansheng
 * @date: 2022-08-08
 */
@RequiredArgsConstructor
public class NacosInstancesChangeNotifier extends Subscriber<InstancesChangeEvent> implements SmartInitializingSingleton {

    private final SpringClientFactory springClientFactory;
    private final String applicationName;

    @Override

    public void onEvent(InstancesChangeEvent event) {
        ILoadBalancer loadBalancer = this.springClientFactory.getLoadBalancer(event.getServiceName());
        if (this.applicationName.equals(event.getServiceName()) || !(loadBalancer instanceof DynamicServerListLoadBalancer)) {
            return;
        }
        ((DynamicServerListLoadBalancer<?>) loadBalancer).updateListOfServers();
    }

    @Override
    public Class<? extends Event> subscribeType() {
        return InstancesChangeEvent.class;
    }

    @Override
    public void afterSingletonsInstantiated() {
        NotifyCenter.registerSubscriber(this);
    }
}

定义监听器

    @Bean
    public NacosInstancesChangeNotifier nacosInstancesChangeNotifier(SpringClientFactory springClientFactory, Environment environment) {
        String applicationName = environment.getProperty("spring.application.name", "");
        return new NacosInstancesChangeNotifier(springClientFactory, applicationName);
    }
zhaoxilingcheng commented 7 months ago

监听器实现参考

/**
 * @author: ruansheng
 * @date: 2022-08-08
 */
@RequiredArgsConstructor
public class NacosInstancesChangeNotifier extends Subscriber<InstancesChangeEvent> implements SmartInitializingSingleton {

    private final SpringClientFactory springClientFactory;
    private final String applicationName;

    @Override

    public void onEvent(InstancesChangeEvent event) {
        ILoadBalancer loadBalancer = this.springClientFactory.getLoadBalancer(event.getServiceName());
        if (this.applicationName.equals(event.getServiceName()) || !(loadBalancer instanceof DynamicServerListLoadBalancer)) {
            return;
        }
        ((DynamicServerListLoadBalancer<?>) loadBalancer).updateListOfServers();
    }

    @Override
    public Class<? extends Event> subscribeType() {
        return InstancesChangeEvent.class;
    }

    @Override
    public void afterSingletonsInstantiated() {
        NotifyCenter.registerSubscriber(this);
    }
}

定义监听器

    @Bean
    public NacosInstancesChangeNotifier nacosInstancesChangeNotifier(SpringClientFactory springClientFactory, Environment environment) {
        String applicationName = environment.getProperty("spring.application.name", "");
        return new NacosInstancesChangeNotifier(springClientFactory, applicationName);
    }

nacos的事件推送是不稳定的, 所以这样也不能很好的解决

ruansheng8 commented 7 months ago

@zhaoxilingcheng 长期的使用验证下来,nacos 的推送事件稳定性还是很高的。

当然,还可以同时配合其他机制来进一步保证稳定性,例如:当前服务实例下线时,基于消息订阅等相关方式将当前下线的实例通知给其他服务,然后其他服务将“下线的服务实例”缓存到当前服务的“黑名单”中,然后在负载均衡选择实例的时候跳过“黑名单”中的实例即可。

shenhuaxx commented 3 months ago
InstancesChangeEvent

监听这个事件 只有自身服务实例变化的时候才会触发,nacos中其它服务上下线根本不会触发到网关增加的这段代码里面来。是我版本不对,还是我理解的不对?

ruansheng8 commented 3 months ago

@shenhuaxx 服务实例变化、上下线均会触发这个事件

ruansheng8 commented 2 months ago
InstancesChangeEvent

监听这个事件 只有自身服务实例变化的时候才会触发,nacos中其它服务上下线根本不会触发到网关增加的这段代码里面来。是我版本不对,还是我理解的不对?

这里补充一点,如果没有收到来自Nacos的实例上下线事件通知,需要先触发一次对应服务的远程调用,这是因为无论是 Spring Cloud Gateway 还是 OpenFeign ,LoadBalancerClient 对象默认都是懒加载的,每个不同的服务都会对应创建一个 LoadBalancerClient 对象,所以只有当 LoadBalancerClient 对象被创建后,才会拥有对应服务的监听能力。

Spring Cloud Gateway 相关源码参考:

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
        ......
}
shenhuaxx commented 2 months ago

@shenhuaxx 服务实例变化、上下线均会触发这个事件

测试后发现2.2.x版本收不到这个事件,高版本可以正常收到事件。