一个Spring Boot Admin 监控多个Nacos集群
作者:mmseoamin日期:2024-02-20

背景

我们有多个系统,每个系统一个集群,每个集群都部署了自己的Spring Boot Admin(以下简称Admin),用起来不仅不方便,私有化部署的时候还得多部署几个服务,为了解决这个问题,我想到了是否可以用一个Admin同时监控多个集群,这里集群指监控Nacos集群。

实现

通过查看Nacos的服务注册源码、Admin监控的服务发现源码,最终得出结论:重写NacosServiceManager、NamingService类,即可实现。

  • 为了监控多个Namespace,nacos的服务发现配置通过分号分割即可
  • 为了区别与原来只能订阅单个Namespace,将所有的重写类定义为Multixxx
  • 将自定义的MultiNacosServiceManager类定义为主要Bean

    MultiNacosServiceManager

    这个类用来管理NamingService,包括创建NamingService,NamingMaintainService。

    import com.alibaba.cloud.nacos.NacosServiceManager;
    import com.alibaba.nacos.api.NacosFactory;
    import com.alibaba.nacos.api.exception.NacosException;
    import com.alibaba.nacos.api.naming.NamingMaintainService;
    import com.alibaba.nacos.api.naming.NamingService;
    import com.alibaba.nacos.client.naming.NacosNamingService;
    import org.apache.commons.lang3.SerializationUtils;
    import java.util.*;
    import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE;
    public class MultiNacosServiceManager extends NacosServiceManager {
        //namespace分隔符
        public static final String SEMICOLON = ";";
        private MultiNacosNamingService multiNacosNamingService;
        @Override
        public NamingService getNamingService(Properties properties) {
            if (Objects.isNull(this.multiNacosNamingService)) {
                multiNacosNamingService = buildNamingService(properties);
            }
            return multiNacosNamingService;
        }
        //这个服务就只取第一个了,简单点
        @Override
        public NamingMaintainService getNamingMaintainService(Properties properties) {
            String namespace = properties.getProperty(NAMESPACE);
            if (namespace.contains(SEMICOLON)) {
                String[] namespaces = namespace.split(";");
                properties.setProperty(NAMESPACE, namespaces[0]);
            }
            return super.getNamingMaintainService(properties);
        }
        private MultiNacosNamingService buildNamingService(Properties properties) {
            if (Objects.isNull(multiNacosNamingService)) {
                synchronized (MultiNacosServiceManager.class) {
                    if (Objects.isNull(multiNacosNamingService)) {
                        try {
                            String namespace = properties.getProperty(NAMESPACE);
                            if (namespace.contains(SEMICOLON)) {
                                List multiNacosNamingService = new ArrayList<>();
                                //每个namespace创建一个namingService
                                for (String ns : namespace.split(SEMICOLON)) {
                                    Properties newProperties = SerializationUtils.clone(properties);
                                    newProperties.setProperty(NAMESPACE, ns);
                                    NacosNamingService namingService = (NacosNamingService) NacosFactory.createNamingService(newProperties);
                                    multiNacosNamingService.add(namingService);
                                }
                                return new MultiNacosNamingService(multiNacosNamingService);
                            } else {
                                NacosNamingService namingService = (NacosNamingService) NacosFactory.createNamingService(properties);
                                return new MultiNacosNamingService(Collections.singletonList(namingService));
                            }
                        } catch (NacosException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
            return multiNacosNamingService;
        }
    }
    

    MultiNacosNamingService

    将多个 nacosNamingService 组合为一个对外提供服务,原有的NamingService 只支持单个namespace,将原来有NamingService方法都重写为支持多个namespace。

    import com.alibaba.nacos.api.exception.NacosException;
    import com.alibaba.nacos.api.naming.NamingService;
    import com.alibaba.nacos.api.naming.listener.EventListener;
    import com.alibaba.nacos.api.naming.pojo.Instance;
    import com.alibaba.nacos.api.naming.pojo.ListView;
    import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
    import com.alibaba.nacos.api.selector.AbstractSelector;
    import com.alibaba.nacos.client.naming.NacosNamingService;
    import java.util.ArrayList;
    import java.util.List;
    import static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_DOWN;
    import static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_UP;
    /**
     * 将多个 nacosNamingService 组合为一个对外提供服务
     */
    public class MultiNacosNamingService implements NamingService {
        private List nacosNamingServices;
        public MultiNacosNamingService(List nacosNamingServices) {
            this.nacosNamingServices = nacosNamingServices;
        }
        @Override
        public void registerInstance(String serviceName, String ip, int port) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.registerInstance(serviceName, ip, port);
            }
        }
        @Override
        public void registerInstance(String serviceName, String groupName, String ip, int port) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.registerInstance(serviceName, groupName, ip, port);
            }
        }
        @Override
        public void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.registerInstance(serviceName, ip, port, clusterName);
            }
        }
        @Override
        public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.registerInstance(serviceName, groupName, ip, port, clusterName);
            }
        }
        @Override
        public void registerInstance(String serviceName, Instance instance) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.registerInstance(serviceName, instance);
            }
        }
        @Override
        public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.registerInstance(serviceName, groupName, instance);
            }
        }
        @Override
        public void deregisterInstance(String serviceName, String ip, int port) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.deregisterInstance(serviceName, ip, port);
            }
        }
        @Override
        public void deregisterInstance(String serviceName, String groupName, String ip, int port) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.deregisterInstance(serviceName, groupName, ip, port);
            }
        }
        @Override
        public void deregisterInstance(String serviceName, String ip, int port, String clusterName) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.deregisterInstance(serviceName, ip, port, clusterName);
            }
        }
        @Override
        public void deregisterInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.deregisterInstance(serviceName, groupName, ip, port, clusterName);
            }
        }
        @Override
        public void deregisterInstance(String serviceName, Instance instance) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.deregisterInstance(serviceName, instance);
            }
        }
        @Override
        public void deregisterInstance(String serviceName, String groupName, Instance instance) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.deregisterInstance(serviceName, groupName, instance);
            }
        }
        @Override
        public List getAllInstances(String serviceName) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName));
            }
            return instances;
        }
        @Override
        public List getAllInstances(String serviceName, String groupName) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName, groupName));
            }
            return instances;
        }
        @Override
        public List getAllInstances(String serviceName, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName, subscribe));
            }
            return instances;
        }
        @Override
        public List getAllInstances(String serviceName, String groupName, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName, groupName, subscribe));
            }
            return instances;
        }
        @Override
        public List getAllInstances(String serviceName, List clusters) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName, clusters));
            }
            return instances;
        }
        @Override
        public List getAllInstances(String serviceName, String groupName, List clusters) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName, groupName, clusters));
            }
            return instances;
        }
        @Override
        public List getAllInstances(String serviceName, List clusters, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName, clusters, subscribe));
            }
            return instances;
        }
        @Override
        public List getAllInstances(String serviceName, String groupName, List clusters, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.getAllInstances(serviceName, groupName, clusters, subscribe));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, boolean healthy) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, healthy));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, groupName, healthy));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, boolean healthy, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, healthy, subscribe));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, String groupName, boolean healthy, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, groupName, healthy, subscribe));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, List clusters, boolean healthy) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, clusters, healthy));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, String groupName, List clusters, boolean healthy) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, groupName, clusters, healthy));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, List clusters, boolean healthy, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, clusters, healthy, subscribe));
            }
            return instances;
        }
        @Override
        public List selectInstances(String serviceName, String groupName, List clusters, boolean healthy, boolean subscribe) throws NacosException {
            List instances = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                instances.addAll(nacosNamingService.selectInstances(serviceName, groupName, clusters, healthy, subscribe));
            }
            return instances;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName, String groupName) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName, groupName);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName, boolean subscribe) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName, subscribe);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName, String groupName, boolean subscribe) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName, groupName, subscribe);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName, List clusters) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName, clusters);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName, String groupName, List clusters) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName, groupName, clusters);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName, List clusters, boolean subscribe) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName, clusters, subscribe);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public Instance selectOneHealthyInstance(String serviceName, String groupName, List clusters, boolean subscribe) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                Instance instance = nacosNamingService.selectOneHealthyInstance(serviceName, groupName, clusters, subscribe);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        }
        @Override
        public void subscribe(String serviceName, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.subscribe(serviceName, listener);
            }
        }
        @Override
        public void subscribe(String serviceName, String groupName, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.subscribe(serviceName, groupName, listener);
            }
        }
        @Override
        public void subscribe(String serviceName, List clusters, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.subscribe(serviceName, clusters, listener);
            }
        }
        @Override
        public void subscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.subscribe(serviceName, groupName, clusters, listener);
            }
        }
        @Override
        public void unsubscribe(String serviceName, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.unsubscribe(serviceName, listener);
            }
        }
        @Override
        public void unsubscribe(String serviceName, String groupName, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.unsubscribe(serviceName, groupName, listener);
            }
        }
        @Override
        public void unsubscribe(String serviceName, List clusters, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.unsubscribe(serviceName, clusters, listener);
            }
        }
        @Override
        public void unsubscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.unsubscribe(serviceName, groupName, clusters, listener);
            }
        }
        @Override
        public ListView getServicesOfServer(int pageNo, int pageSize) throws NacosException {
            ListView listView = new ListView<>();
            List data = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                data.addAll(nacosNamingService.getServicesOfServer(pageNo, pageSize).getData());
            }
            listView.setData(data);
            return listView;
        }
        @Override
        public ListView getServicesOfServer(int pageNo, int pageSize, String groupName) throws NacosException {
            ListView listView = new ListView<>();
            List data = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                data.addAll(nacosNamingService.getServicesOfServer(pageNo, pageSize, groupName).getData());
            }
            listView.setData(data);
            return listView;
        }
        @Override
        public ListView getServicesOfServer(int pageNo, int pageSize, AbstractSelector selector) throws NacosException {
            ListView listView = new ListView<>();
            List data = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                data.addAll(nacosNamingService.getServicesOfServer(pageNo, pageSize, selector).getData());
            }
            listView.setData(data);
            return listView;
        }
        @Override
        public ListView getServicesOfServer(int pageNo, int pageSize, String groupName, AbstractSelector selector) throws NacosException {
            ListView listView = new ListView<>();
            List data = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                data.addAll(nacosNamingService.getServicesOfServer(pageNo, pageSize, groupName, selector).getData());
            }
            listView.setData(data);
            return listView;
        }
        @Override
        public List getSubscribeServices() {
            List data = new ArrayList<>();
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                data.addAll(nacosNamingService.getSubscribeServices());
            }
            return data;
        }
        @Override
        public String getServerStatus() {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                String serverStatus = nacosNamingService.getServerStatus();
                if (STATUS_DOWN.equals(serverStatus)) {
                    return STATUS_DOWN;
                }
            }
            return STATUS_UP;
        }
        @Override
        public void shutDown() throws NacosException {
            for (NacosNamingService nacosNamingService : nacosNamingServices) {
                nacosNamingService.shutDown();
            }
        }
    }
    

    MultiNacosServiceAutoConfiguration

    将MultiNacosServiceManager 设置为自动加载Bean,激活为主要的Bean。

    import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
    import com.alibaba.cloud.nacos.NacosServiceManager;
    import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnDiscoveryEnabled
    @ConditionalOnNacosDiscoveryEnabled
    public class MultiNacosServiceAutoConfiguration {
        @Bean
        @Primary
        public NacosServiceManager multiNacosServiceManager() {
            return new MultiNacosServiceManager();
        }
    }
    

    总结

    最终你能发现admin监控会同时注册到多个集群中,admin服务列表能看到多个集群的服务。另外要注意的是,要适当调整admin监控服务的内存,毕竟监控的服务变多了。

    通过一个月的运行,目前admin监控运行稳定,相关功能一切正常。


    其他企业级监控:

    Prometheus 系列文章

    1. Prometheus 的介绍和安装
    2. 直观感受PromQL及其数据类型
    3. PromQL之选择器和运算符
    4. PromQL之函数
    5. Prometheus 告警机制介绍及命令解读
    6. Prometheus 告警模块配置深度解析
    7. Prometheus 配置身份认证
    8. Prometheus 动态拉取监控服务
    9. Prometheus 监控云Mysql和自建Mysql

    Grafana 系列文章,版本:OOS v9.3.1

    1. Grafana 的介绍和安装
    2. Grafana监控大屏配置参数介绍(一)
    3. Grafana监控大屏配置参数介绍(二)
    4. Grafana监控大屏可视化图表
    5. Grafana 查询数据和转换数据
    6. Grafana 告警模块介绍
    7. Grafana 告警接入飞书通知