2.nacos-client源码及实践

nacos-client.2.2.1-RC. SDK查看源码及使用

官网JAVA SDK 链接

  • 主要内容
    <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>${version}</version> </dependency>
    问题:
    1. 获取配置api 是获取快照还是远程配置中心?
    2. 快照文件默认存储在哪?然后配置?
    3. 如何去监听是否动态修改,新文本存储在哪?
    4. springboot 实践配置及结合的代码,如何灵活使用配置中心?

配置管理

  • 获取配置
    描述
    用于服务启动的时候从 Nacos 获取配置。
    public String getConfig(String dataId, String group, long timeoutMs) throws NacosException

  • 监听配置
    描述
    如果希望 Nacos 推送配置变更,可以使用 Nacos 动态监听配置接口来实现。
    public void addListener(String dataId, String group, Listener listener)

  • 删除监听
    描述
    取消监听配置,取消监听后配置不会再推送。
    public void removeListener(String dataId, String group, Listener listener)

  • 发布配置
    描述
    用于通过程序自动发布 Nacos 配置,以便通过自动化手段降低运维成本。

    注意:创建和修改配置时使用的同一个发布接口,当配置不存在时会创建配置,当配置已存在时会更新配置。

    点击查看代码
      public boolean publishConfig(String dataId, String group, String content) throws NacosException;
      @Since 1.4.1
      public boolean publishConfig(String dataId, String group, String content, String type) throws NacosException;
    
    
  • 删除配置
    描述
    用于通过程序自动删除 Nacos 配置,以便通过自动化手段降低运维成本。

    注意: 当配置已存在时会删除该配置,当配置不存在时会直接返回成功消息。
    public boolean removeConfig(String dataId, String group) throws NacosException

服务发现SDK

  • 注册实例
    描述注册一个实例到服务。
    void registerInstance(String serviceName, String ip, int port) throws NacosException;
    void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException;
    void registerInstance(String serviceName, Instance instance) throws NacosException;

  • 注销实例
    描述
    删除服务下的一个实例。
    void deregisterInstance(String serviceName, String ip, int port) throws NacosException;
    void deregisterInstance(String serviceName, String ip, int port, String clusterName) throws NacosException;

  • 获取全部实例
    描述
    获取服务下的所有实例。
    List<Instance> getAllInstances(String serviceName) throws NacosException;
    List<Instance> getAllInstances(String serviceName, List<String> clusters) throws NacosException;

  • 监听服务
    描述
    监听服务下的实例列表变化。
    void subscribe(String serviceName, EventListener listener) throws NacosException;
    void subscribe(String serviceName, List<String> clusters, EventListener listener) throws NacosException;


源码实现

配置中心

下载源码包,在api包下config文件下,粗略浏览一下我们发现了 com.alibaba.nacos.api.config.ConfigService 接口:

点击查看代码
public interface ConfigService {
    
    /**
     * Get config.
     *
     * @param dataId    dataId
     * @param group     group
     * @param timeoutMs read timeout
     * @return config value
     * @throws NacosException NacosException
     */
    String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
    
    /**
     * Get config and register Listener.
     *
     * <p>If you want to pull it yourself when the program starts to get the configuration for the first time, and the
     * registered Listener is used for future configuration updates, you can keep the original code unchanged, just add
     * the system parameter: enableRemoteSyncConfig = "true" ( But there is network overhead); therefore we recommend
     * that you use this interface directly
     *
     * @param dataId    dataId
     * @param group     group
     * @param timeoutMs read timeout
     * @param listener  {@link Listener}
     * @return config value
     * @throws NacosException NacosException
     */
    String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)
            throws NacosException;
    
    /**
     * Add a listener to the configuration, after the server modified the configuration, the client will use the
     * incoming listener callback. Recommended asynchronous processing, the application can implement the getExecutor
     * method in the ManagerListener, provide a thread pool of execution. If not provided, use the main thread callback, May
     * block other configurations or be blocked by other configurations.
     *
     * @param dataId   dataId
     * @param group    group
     * @param listener listener
     * @throws NacosException NacosException
     */
    void addListener(String dataId, String group, Listener listener) throws NacosException;
    
    /**
     * Publish config.
     *
     * @param dataId  dataId
     * @param group   group
     * @param content content
     * @return Whether publish
     * @throws NacosException NacosException
     */
    boolean publishConfig(String dataId, String group, String content) throws NacosException;
    
    
    /**
     * Publish config.
     *
     * @param dataId  dataId
     * @param group   group
     * @param content content
     * @param type    config type {@link ConfigType}
     * @return Whether publish
     * @throws NacosException NacosException
     */
    boolean publishConfig(String dataId, String group, String content, String type) throws NacosException;
    
    /**
     * Cas Publish config.
     *
     * @param dataId  dataId
     * @param group   group
     * @param content content
     * @param casMd5  casMd5 prev content's md5 to cas.
     * @return Whether publish
     * @throws NacosException NacosException
     */
    boolean publishConfigCas(String dataId, String group, String content, String casMd5) throws NacosException;
    
    /**
     * Cas Publish config.
     *
     * @param dataId  dataId
     * @param group   group
     * @param content content
     * @param casMd5  casMd5 prev content's md5 to cas.
     * @param type    config type {@link ConfigType}
     * @return Whether publish
     * @throws NacosException NacosException
     */
    boolean publishConfigCas(String dataId, String group, String content, String casMd5, String type)
            throws NacosException;
    
    /**
     * Remove config.
     *
     * @param dataId dataId
     * @param group  group
     * @return whether remove
     * @throws NacosException NacosException
     */
    boolean removeConfig(String dataId, String group) throws NacosException;
    
    /**
     * Remove listener.
     *
     * @param dataId   dataId
     * @param group    group
     * @param listener listener
     */
    void removeListener(String dataId, String group, Listener listener);
    
    /**
     * Get server status.
     *
     * @return whether health
     */
    String getServerStatus();

    /**
     * add config filter.
     * It is recommended to use {@link com.alibaba.nacos.api.config.filter.AbstractConfigFilter} to expand the filter.
     *
     * @param configFilter filter
     * @since 2.3.0
     */
    void addConfigFilter(IConfigFilter configFilter);
    
    /**
     * Shutdown the resource service.
     *
     * @throws NacosException exception.
     */
    void shutDown() throws NacosException;
}

获取配置信息getConfig

查看getConfig(String dataId, String group, long timeoutMs) 的实现:调用了

getConfigInner(namespace, dataId, group, timeoutMs);

在参数处理中:
grop处理, 未设置返回默认分组DEFAULT_GROUP:

return (StringUtils.isBlank(group)) ? Constants.DEFAULT_GROUP : group.trim();

接下来我看到先去nacos服务器故障本地缓存取:

点击查看代码
        // We first try to use local failover content if exists.
        // A config content for failover is not created by client program automatically,
        // but is maintained by user.
        // This is designed for certain scenario like client emergency reboot,
        // changing config needed in the same time, while nacos server is down.
        String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
        if (content != null) {
            LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}",
                    worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
            cr.setContent(content);
            String encryptedDataKey = LocalEncryptedDataKeyProcessor
                    .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
            cr.setEncryptedDataKey(encryptedDataKey);
            configFilterChainManager.doFilter(null, cr);
            content = cr.getContent();
            return content;
        }
查看getFailover,他就是读取本地文件,我们也发现了目录的默认读取地址是 user\home\nacos\config (若想修改配置变量:JM.SNAPSHOT.PATH即可替换); 在该目录下读取{serverName}_nacos\data目录, 没有设置元空间,及config-data-tenant\{group}\{dataId},既user\home\nacos\config\{serverName}_nacos\data\config-data-tenant\{group}\{dataId} 若本地有缓存,读取到了内容直接返回内容 后面一段代码 是读取加密的文本 ecnrypted ,封装的 ConfigResponse 传给了configFilterChainManager

若本地缓存没有,则远程读取

点击查看代码
try {
            ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
            cr.setContent(response.getContent());
            cr.setEncryptedDataKey(response.getEncryptedDataKey());
            configFilterChainManager.doFilter(null, cr);
            content = cr.getContent();
            
            return content;
        } catch (NacosException ioe) {
            if (NacosException.NO_RIGHT == ioe.getErrCode()) {
                throw ioe;
            }
            LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
                    worker.getAgentName(), dataId, group, tenant, ioe.toString());
        }

        content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), dataId, group, tenant);
        if (content != null) {
            LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}",
                    worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
        }
        cr.setContent(content);
        String encryptedDataKey = LocalEncryptedDataKeyProcessor
                .getEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant);
        cr.setEncryptedDataKey(encryptedDataKey);
        configFilterChainManager.doFilter(null, cr);
        content = cr.getContent();
        return content;
上面代码主要做两个事:
  • 远程获取文本
  • 本地生成文本快照

远程获取文本 worker 是在构造参数实例化的时候
this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, clientProperties);
继续查看ClientWorker ,发现是由 ConfigRpcTransportClient agent 远程调用
this.agent.queryConfig(dataId, group, tenant, readTimeout, notify);
至此就获取到了远程配置文本信息

获取文本信息并添加监听getConfigAndSignListener

该方法就是获取远程文本,并监听信息的变化;

点击查看代码
public String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)
            throws NacosException {
        group = StringUtils.isBlank(group) ? Constants.DEFAULT_GROUP : group.trim();
        ConfigResponse configResponse = worker.getAgent()
                .queryConfig(dataId, group, worker.getAgent().getTenant(), timeoutMs, false);
        String content = configResponse.getContent();
        String encryptedDataKey = configResponse.getEncryptedDataKey();
        worker.addTenantListenersWithContent(dataId, group, content, encryptedDataKey, Arrays.asList(listener));
        
        // get a decryptContent, fix https://github.com/alibaba/nacos/issues/7039
        ConfigResponse cr = new ConfigResponse();
        cr.setDataId(dataId);
        cr.setGroup(group);
        cr.setContent(content);
        cr.setEncryptedDataKey(encryptedDataKey);
        configFilterChainManager.doFilter(null, cr);
        return cr.getContent();
    }
查看代码可知 直接远程调用

ConfigResponse configResponse = worker.getAgent() .queryConfig(dataId, group, worker.getAgent().getTenant(), timeoutMs, false);
并添加一个监听
worker.addTenantListenersWithContent(dataId, group, content, encryptedDataKey, Arrays.asList(listener));
监听既把信息塞入一个缓存对象CacheData中用于监听管理

添加监听addListener

void addListener(String dataId, String group, Listener listener) throws NacosException;

点击查看代码
public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners)
            throws NacosException {
        group = blank2defaultGroup(group);
        String tenant = agent.getTenant();
        CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
        synchronized (cache) {
            for (Listener listener : listeners) {
                cache.addListener(listener);
            }
            cache.setDiscard(false);
            cache.setSyncWithServer(false);
            agent.notifyListenConfig();
        }
        
    }

其他接口实现也都是类似的,如发布配置
boolean publishConfig(String dataId, String group, String content) throws NacosException;
boolean publishConfig(String dataId, String group, String content, String type) throws NacosException;

与spingboot使用

springboot 应用配置

设置Nacos开机自启

  • 添加nacos.service文件命令:

      Description=nacos
    	After=network.target
    	[Service]
    	Type=forking
    	ExecStart=/usr/local/nacos/bin/startup.sh -m standalone
    	ExecReload=/usr/local/nacos/bin/shutdown.sh
    	ExecStop=/usr/local/nacos/bin/shutdown.sh
    	PrivateTmp=true
    	[Install]
    	WantedBy=multi-user.target"  > /lib/systemd/system/nacos.service``` 
    
    
  • 修改nacos的startup.sh

vim /usr/local/nacos/bin/startup.sh

  • 修改JAVA_HOME路径并注销之后的3行配置,如下:
 [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/local/jdk1.8.0_191
#[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
#[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/opt/taobao/java
#[ ! -e "$JAVA_HOME/bin/java" ] && unset JAVA_HOME
  • 设置开机自启
systemctl daemon-reload       	 #重新加载服务配置
systemctl enable nacos.service	 #设置为开机启动
systemctl start nacos.service 	 #启动nacos服务
systemctl stop nacos.service   	 #停止nacos服务
systemctl status nacos.service   #查看nacos服务状态
posted @ 2023-06-25 11:24  千里送e毛  阅读(261)  评论(0编辑  收藏  举报