Nacos源码—6.Nacos升级gRPC分析二

Nacos升级gRPC及服务注册订阅源码分析

大纲

1.Nacos 2.x版本的一些变化

2.客户端升级gRPC发起服务注册

3.服务端进行服务注册时的处理

4.客户端服务发现和服务端处理服务订阅的源码分析

4.客户端服务发现和服务端处理服务订阅的源码分析

(1)Nacos客户端进行服务发现的源码

(2)Nacos服务端处理服务订阅请求的源码

(1)Nacos客户端进行服务发现的源码

一.nacos-discovery引入Ribbon实现服务调用时的负载均衡

二.nacos-discovery如何整合Ribbon实现服务调用时的负载均衡

三.nacos-client如何进行服务发现

一.nacos-discovery引入Ribbon实现服务调用时的负载均衡

Nacos客户端就是引入了nacos-discovery + nacos-client依赖的项目。由于nacos-discovery整合了Ribbon,所以Ribbon可以调用Nacos服务端的服务实例查询列表接口。于是Nacos客户端便借助Ribbon实现了服务调用时的负载均衡,即Ribbon会从服务实例列表中选择一个服务实例给客户端进行服务调用。

在nacos-discovery的pom.xml中,可以看到它引入了Ribbon依赖:

二.nacos-discovery如何整合Ribbon实现服务调用时的负载均衡

在Ribbon中会有一个ServerList接口,如下所示:ServerList就是一个扩展接口,这个接口的作用就是获取Server列表。然后nacos-discovery会针对这个接口进行实现,从而整合Ribbon。

从引入的包来看,loadbalancer是属于Ribbon源码包下的。而LoadBalancer则是Ribbon中的负载均衡器。负载均衡器会结合IRule负载均衡策略,从服务实例列表中选择一个实例。

package com.netflix.loadbalancer;

import java.util.List;

//Interface that defines the methods sed to obtain the List of Servers
public interface ServerList<T extends Server> {
    public List<T> getInitialListOfServers();

    //Return updated list of servers. This is called say every 30 secs
    public List<T> getUpdatedListOfServers();
}

当Nacos客户端进行微服务调用时,会通过Ribbon来选出一个服务实例,此时Ribbon会调用NacosServerList的getUpdatedListOfServers()方法获取服务实例列表。

nacos-discovery的NacosServerList类继承了AbstractServerList类,而且实现了Ribbon的ServerList接口的两个方法,如下所示:

public abstract class AbstractServerList<T extends Server> implements ServerList<T>, IClientConfigAware {
    ...
    ...
}

public class NacosServerList extends AbstractServerList<NacosServer> {
    private NacosDiscoveryProperties discoveryProperties;
    private String serviceId;

    public NacosServerList(NacosDiscoveryProperties discoveryProperties) {
        this.discoveryProperties = discoveryProperties;
    }

    @Override
    public List<NacosServer> getInitialListOfServers() {
        return getServers();
    }

    @Override
    public List<NacosServer> getUpdatedListOfServers() {
        return getServers();
    }

    private List<NacosServer> getServers() {
        try {
            //读取分组
            String group = discoveryProperties.getGroup();
            //通过服务名称、分组、true(表示只需要健康实例),
            //调用NacosNamingService.selectInstances()方法来查询服务实例列表
            List<Instance> instances = discoveryProperties.namingServiceInstance().selectInstances(serviceId, group, true);
            //把Instance转换成NacosServer类型
            return instancesToServerList(instances);
        } catch (Exception e) {
            throw new IllegalStateException("Can not get service instances from nacos, serviceId=" + serviceId, e);
        }
    }

    private List<NacosServer> instancesToServerList(List<Instance> instances) {
        List<NacosServer> result = new ArrayList<>();
        if (CollectionUtils.isEmpty(instances)) {
            return result;
        }
        for (Instance instance : instances) {
            result.add(new NacosServer(instance));
        }
        return result;
    }

    public String getServiceId() {
        return serviceId;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        this.serviceId = iClientConfig.getClientName();
    }
}

NacosServerList的核心方法是getServers(),因为nacos-discovery实现Ribbon的两个接口都调用到了该方法。

在nacos-discovery的NacosServerList的getServers()方法中,会调用nacos-client的NacosNamingService的selectInstances()方法,来获取服务实例列表。

三.nacos-client如何进行服务发现

在nacos-client的NacosNamingService的selectInstances()方法中,首先会调用ServiceInfoHolder的getServiceInfo()方法从本地缓存获取数据。ServiceInfoHolder的serviceInfoMap中的value是一个ServiceInfo对象,在ServiceInfo对象中会有一个Listhosts属性来存放实例数据。

如果ServiceInfoHolder中的本地缓存没有对应的ServiceInfo对象,那么就会调用NamingClientProxyDelegate的subscribe()方法。该方法首先会开启一个查询服务实例列表的延时执行的任务,然后通过Client对象发送订阅请求,去服务端实时获取服务实例数据。

具体来说就是先调用ServiceInfoUpdateService的scheduleUpdateIfAbsent()方法,开启一个延迟执行查询服务实例列表的UpdateTask任务,然后再次调用ServiceInfoHolder的getServiceInfoMap()方法查询本地缓存。如果本地缓存为空,则向服务端发起gRPC请求获取服务实例数据,也就是通过调用NamingGrpcClientProxy的subscribe()方法,触发调用NamingGrpcClientProxy的doSubscribe()方法,再触发调用NamingGrpcClientProxy的requestToServer()方法,接着调用RpcClient的request()方法发送gRPC请求给服务端,最后调用ServiceInfoHolder的processServiceInfo()方法更新本地缓存。

public class NacosNamingService implements NamingService {
    private ServiceInfoHolder serviceInfoHolder;
    private NamingClientProxy clientProxy;
    ...

    private void init(Properties properties) throws NacosException {
        ...
        this.serviceInfoHolder = new ServiceInfoHolder(namespace, properties);
        this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
    }

    @Override
    public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {
        ServiceInfo serviceInfo;
        String clusterString = StringUtils.join(clusters, ",");
        //判断是否需要订阅,默认为true
        if (subscribe) {
            //查询Nacos本地缓存数据
            serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString);
            if (null == serviceInfo) {
                //如果本地缓存数据为空,则调用Client代理NamingClientProxyDelegate的订阅方法subscribe(),通过Client对象请求服务端获取数据
                serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);
            }
        } else {
            serviceInfo = clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, 0, false);
        }
        //返回数据
        return selectInstances(serviceInfo, healthy);
    }
    ...
}

//Naming client service information holder.
public class ServiceInfoHolder implements Closeable {
    private final ConcurrentMap<String, ServiceInfo> serviceInfoMap;
    ...

    public ServiceInfo getServiceInfo(final String serviceName, final String groupName, final String clusters) {
        NAMING_LOGGER.debug("failover-mode: {}", failoverReactor.isFailoverSwitch(
<think>我们正在处理一个关于Nacos客户端日志配置的问题。用户报告的错误是: "Unable to locate appender ASYNC-REMOTE for logger config com.alibaba.nacos.shaded.io.grpc" 这个错误通常发生在Nacos客户端尝试使用名为"ASYNC-REMOTE"的appender记录日志时,但在当前的日志配置中找不到该appender。 ### 问题分析 1. **日志框架配置问题**:Nacos客户端内部使用了SLF4J作为日志门面,实际日志实现可能是Logback、Log4j等。错误信息表明,在日志配置中,没有为名为"com.alibaba.nacos.shaded.io.grpc"的logger配置一个叫做"ASYNC-REMOTE"的appender。 2. **Nacos客户端的日志配置**:Nacos客户端在启动时会尝试加载默认的日志配置,但用户可能自定义了日志配置,覆盖了Nacos的默认配置。 3. **依赖冲突**:如果项目中存在多个日志框架的依赖,可能会导致配置混乱。 ### 解决方案 #### 1. 检查日志配置文件 确保日志配置文件(如logback.xml、log4j2.xml等)中定义了名为"ASYNC-REMOTE"的appender,并且该appender被关联到logger"com.alibaba.nacos.shaded.io.grpc"。 例如,在Logback中,配置可能如下: ```xml <appender name="ASYNC-REMOTE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 配置略 --> </appender> <logger name="com.alibaba.nacos.shaded.io.grpc" level="INFO"> <appender-ref ref="ASYNC-REMOTE"/> </logger> ``` #### 2. 检查默认日志配置 如果用户没有提供自定义日志配置,Nacos客户端会使用默认配置。但某些情况下,默认配置可能被覆盖。可以尝试在项目中添加一个简单的日志配置文件(如logback.xml)并配置所需的appender。 #### 3. 排除日志依赖冲突 检查项目的依赖树,确保没有引入多个日志框架的冲突版本。可以使用Maven的依赖分析工具: ```bash mvn dependency:tree ``` 或Gradle的依赖分析: ```bash gradle dependencies ``` 然后排除不必要的日志依赖。 例如,在Maven中排除冲突的日志依赖: ```xml <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> </exclusions> ``` #### 4. 使用Nacos默认日志配置 如果问题仍然存在,可以尝试使用Nacos自带的默认日志配置。在classpath下添加Nacos的默认日志配置文件(例如,从Nacos客户端jar包中提取logback.xml文件并放入项目的resources目录下)。但请注意,这可能会覆盖你的自定义配置。 ### 补充说明 - 如果问题仅在特定环境(如生产环境)出现,请检查该环境下的日志配置是否正确部署。 - 确保日志配置文件的名称和位置正确,并且被正确加载。 ### 示例配置(Logback) 以下是一个简单的Logback配置示例,定义了一个控制台输出和一个文件输出的appender,并将文件输出appender命名为"ASYNC-REMOTE"(注意:实际中"ASYNC-REMOTE"可能是一个异步appender,这里仅作示例): ```xml <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <appender name="ASYNC-REMOTE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>nacos.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>nacos.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>1GB</totalSizeCap> </rollingPolicy> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <logger name="com.alibaba.nacos.shaded.io.grpc" level="INFO"> <appender-ref ref="ASYNC-REMOTE"/> </logger> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration> ``` 请根据实际需求调整。 ### 相关问题 1. 如何为Nacos客户端配置日志级别? 2. 如何解决SLF4J绑定多个日志框架的问题? 3. Nacos客户端在Spring Cloud项目中日志配置的优先级是怎样的?</think>### Nacos客户端日志配置问题分析 您遇到的错误信息 `Nacos client ERROR Unable to locate appender ASYNC-REMOTE for logger config com.alibaba.nacos.shaded.io.grpc` 表明Nacos客户端在尝试使用名为"ASYNC-REMOTE"的日志追加器(appender)时失败。以下是系统性的解决方案: --- ### 问题原因分析 1. **日志配置缺失** Nacos客户端内部使用`com.alibaba.nacos.shaded.io.grpc`包下的GRPC组件,该组件默认配置了名为`ASYNC-REMOTE`的appender。当您的项目日志配置中缺少此appender定义时,会触发此错误[^1]。 2. **日志框架冲突** 项目可能存在多个日志框架(如Logback、Log4j2、SLF4J)的依赖冲突,导致配置加载异常。 3. **配置覆盖问题** 自定义日志配置文件(如`logback.xml`)未正确继承Nacos默认配置,或错误覆盖了关键配置项。 --- ### 解决方案 #### 方案1:添加缺失的appender配置(推荐) 在您的日志配置文件(如`logback.xml`)中明确定义`ASYNC-REMOTE` appender: ```xml <!-- logback.xml 示例 --> <configuration> <!-- 1. 定义ASYNC-REMOTE appender --> <appender name="ASYNC-REMOTE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/nacos-remote.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>logs/nacos-remote.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 2. 关联到Nacos GRPC日志器 --> <logger name="com.alibaba.nacos.shaded.io.grpc" level="INFO"> <appender-ref ref="ASYNC-REMOTE"/> <!-- 关键配置 --> </logger> </configuration> ``` #### 方案2:排除冲突日志依赖 检查并排除重复的日志框架依赖(Maven示例): ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <exclusions> <!-- 排除冲突的日志绑定 --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> ``` #### 方案3:启用Nacos默认配置 在`application.properties`中强制使用Nacos内置日志配置: ```properties # 强制加载Nacos默认日志配置 nacos.logging.config=classpath:nacos-logback.xml ``` --- ### 验证步骤 1. **检查配置生效** 启动时添加JVM参数验证配置加载: ```bash -Dlogging.config=classpath:logback.xml -Dnacos.logging.default.config.enabled=true ``` 2. **查看日志输出** 检查`logs/nacos-remote.log`是否生成,并包含GRPC相关日志。 3. **依赖树分析** 使用命令检查冲突: ```bash mvn dependency:tree | grep -E 'log4j|logback|slf4j' ``` --- ### 技术原理 Nacos客户端通过`nacos-client-logging`模块初始化日志系统,其默认配置要求存在`ASYNC-REMOTE` appender用于GRPC通信日志[^2]。当自定义配置覆盖默认配置但未保留此appender时,会导致组件级日志记录失败。 --- ### 相关问题 1. 如何为Nacos客户端配置不同的日志级别? 2. Spring Boot项目中如何统一管理Nacos与其他组件的日志配置? 3. Nacos客户端日志配置不生效的常见排查方法有哪些? [^1]: Nacos官方文档 - 日志配置说明 [^2]: Nacos GitHub源码 - nacos-client-logging模块
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值