上一篇【第46篇】OAP集群通信协议:节点间对话的暗语
下一篇【第48篇】OAP集群规划实战:从理论到生产的容量设计


一、开篇:协调器——集群的"居委会"

如果你住在一个小区里,居委会负责记录"谁住在哪栋楼"、“谁搬走了”、“新搬来的住户”。如果没有居委会,你就无法知道小区里到底住了多少人,更不可能找到邻居借东西。

OAP集群的协调器就是这样的"居委会"。它解决的核心问题只有两个:

  1. 服务发现:集群里现在有哪些OAP节点是活跃的?
  2. 健康管理:哪个节点宕机了?节点加入/离开时如何通知其他节点?
   +----------------------------------------------------------------+
   |                   集群协调器的职责                               |
   |                                                                 |
   |  [OAP-1] ──┐                                                    |
   |            ├──> 注册到协调器                                      |
   |  [OAP-2] ──┤    1. 节点发现                                      |
   |            ├──> 2. 健康检查                                      |
   |  [OAP-3] ──┘    3. 变更通知                                      |
   |                                                                  |
   |  协调器提供:                                                      |
   |  - "现在集群有哪些节点?" → ClusterNodesQuery                     |
   |  - "把我注册到集群" → ClusterRegister                            |
   |  - "把工单发给某个节点" → ClusterModuleClient                     |
   |                                                                  |
   +----------------------------------------------------------------+

           图1:协调器是集群节点的"居委会"

二、方案一:Standalone —— 孤胆英雄

2.1 适用场景

Standalone不是真正的"集群",它适合:

  • 本地开发环境
  • 功能验证测试
  • 对高可用无要求的POC项目

2.2 实现原理

// Standalone实现——代码简洁到令人发指
public class StandaloneClusterRegister implements ClusterRegister {
    
    @Override
    public void registerRemote(RemoteInstance instance) {
        // 单机模式:唯一节点就是自己
        selfInstance = instance;
        logger.info("Standalone模式启动,本节点为唯一集群节点");
    }
}

public class StandaloneClusterNodesQuery implements ClusterNodesQuery {
    
    @Override
    public List<RemoteInstance> queryRemoteNodes() {
        // 永远只返回空列表(只有一个节点)
        return Collections.emptyList();
    }
}

三、方案二:ZooKeeper —— 分布式协调的老兵

3.1 ZK方案的实现架构

   ZK集群(3节点)
   ==============
   
   +----------+     +----------+     +----------+
   | ZK Node1 |<--->| ZK Node2 |<--->| ZK Node3 |
   | (Leader) |     |(Follower)|     |(Follower)|
   +----------+     +----------+     +----------+
        ^                                  ^
        |                                  |
   ZNode: /skywalking/oap-cluster/         |
        |                                  |
   +----+-------------+                   |
   |                   |                   |
   /instance-001       /instance-002       /instance-003
   (EPHEMERAL)        (EPHEMERAL)        (EPHEMERAL)
   data: {            data: {            data: {
     host: "10.0.1.1", host: "10.0.1.2", host: "10.0.1.3",
     port: 11800        port: 11800        port: 11800
   }                  }                  }
        ^                   ^                   ^
        |                   |                   |
   +----+----+         +----+----+         +----+----+
   | OAP-1   |         | OAP-2   |         | OAP-3   |
   +---------+         +---------+         +---------+
   
        图2:ZooKeeper协调方案的节点注册机制

3.2 完整配置

# application.yml - ZooKeeper模式
cluster:
  selector: ${SW_CLUSTER:zookeeper}
  zookeeper:
    # ZK集群地址(逗号分隔)
    hostPort: ${SW_CLUSTER_ZK_HOST_PORT:zk1:2181,zk2:2181,zk3:2181}
    
    # 会话超时(毫秒)
    # OAP节点掉线后,ZK最长等待这个时间才会删除临时节点
    sessionTimeout: ${SW_CLUSTER_ZK_SESSION_TIMEOUT:40000}
    
    # ZK命名空间(路径前缀)
    # 用于在同一个ZK集群中隔离不同环境
    namespace: ${SW_NAMESPACE:skywalking}
    
    # 重试策略:基础睡眠时间
    baseSleepTimeMs: ${SW_CLUSTER_ZK_SLEEP_TIME:1000}
    
    # 重试策略:最大重试次数
    maxRetries: ${SW_CLUSTER_ZK_MAX_RETRIES:3}
    
    # 是否启用ACL认证
    enableACL: ${SW_CLUSTER_ZK_ENABLE_ACL:false}
    # ACL Schema
    schema: ${SW_CLUSTER_ZK_SCHEMA:digest}
    # ACL表达式
    expression: ${SW_CLUSTER_ZK_EXPRESSION:skywalking:skywalking}
    
    # 用于内部通信的host和port(可选,自动探测)
    internalComHost: ${SW_CLUSTER_INTERNAL_COM_HOST:""}
    internalComPort: ${SW_CLUSTER_INTERNAL_COM_PORT:-1}

3.3 ZK方案的优缺点

维度 评价 说明
成熟度 ★★★★★ 十余年生产验证,Apache顶级项目
一致性 ★★★★★ CP系统(ZAB协议),数据绝对一致
性能 ★★★☆☆ 写操作有性能瓶颈(Leader瓶颈)
运维复杂度 ★★★☆☆ 需要单独部署和维护ZK集群
管理界面 ★★☆☆☆ 无内置UI,依赖第三方工具(如zkCli)
Java依赖 需要Curator框架

四、方案三:Nacos —— 云原生的后起之秀

4.1 Nacos方案的实现架构

   Nacos集群(3节点)
   =================
   
   +-----------+     +-----------+     +-----------+
   | Nacos-1   |<--->| Nacos-2   |<--->| Nacos-3   |
   +-----------+     +-----------+     +-----------+
         |
         |
   Service: SkyWalking_OAP_Cluster
   +--------------------------------------+
   |  Instances:                          |
   |  - 10.0.1.1:11800 (healthy)         |
   |  - 10.0.1.2:11800 (healthy)         |
   |  - 10.0.1.3:11800 (healthy)         |
   +--------------------------------------+
         ^            ^            ^
         |            |            |
   +-----+----+  +----+-----+  +----+-----+
   |  OAP-1   |  |  OAP-2   |  |  OAP-3   |
   +----------+  +----------+  +----------+
   
        图3:Nacos基于服务模型的节点注册

4.2 完整配置

# application.yml - Nacos模式
cluster:
  selector: ${SW_CLUSTER:nacos}
  nacos:
    # 服务名(在Nacos中注册的名称)
    serviceName: ${SW_SERVICE_NAME:"SkyWalking_OAP_Cluster"}
    
    # Nacos集群地址
    hostPort: ${SW_CLUSTER_NACOS_HOST_PORT:nacos1:8848,nacos2:8848,nacos3:8848}
    
    # Nacos命名空间(多环境隔离)
    namespace: ${SW_CLUSTER_NACOS_NAMESPACE:""}
    
    # 认证信息
    username: ${SW_CLUSTER_NACOS_USERNAME:""}
    password: ${SW_CLUSTER_NACOS_PASSWORD:""}
    
    # AccessKey认证(阿里云MSE Nacos)
    accessKey: ${SW_CLUSTER_NACOS_ACCESS_KEY:""}
    
    # 分组名
    group: ${SW_CLUSTER_NACOS_GROUP:"DEFAULT_GROUP"}
    
    # 集群名(Nacos内的集群划分)
    clusterName: ${SW_CLUSTER_NACOS_CLUSTER_NAME:"DEFAULT"}

4.3 Nacos方案的优缺点

维度 评价 说明
成熟度 ★★★★☆ 生产可用,持续活跃开发
一致性 ★★★★☆ AP+CP可选(Distro + Raft)
性能 ★★★★☆ 性能优于ZK,支持大规模
运维复杂度 ★★★☆☆ 需要单独部署
管理界面 ★★★★★ 内置Web控制台,可视化操作
额外功能 ★★★★★ 同时支持配置中心(一举两得)
Java依赖 需要nacos-client

五、方案四:Kubernetes —— 生于云,用于云

5.1 K8s方案的精妙之处

如果你已经把OAP部署在Kubernetes上,K8s本身就提供了服务发现能力——根本不需要额外的协调器!

# k8s-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: skywalking-oap-cluster
spec:
  clusterIP: None  # Headless Service
  selector:
    app: skywalking-oap
  ports:
  - port: 11800
    name: grpc
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: skywalking-oap
spec:
  serviceName: skywalking-oap-cluster
  replicas: 3
  selector:
    matchLabels:
      app: skywalking-oap
  template:
    metadata:
      labels:
        app: skywalking-oap
    spec:
      containers:
      - name: oap
        image: apache/skywalking-oap-server:9.0.0
        env:
        - name: SW_CLUSTER
          value: "kubernetes"
        - name: SW_CLUSTER_K8S_NAMESPACE
          value: "skywalking"
        - name: SW_CLUSTER_K8S_LABEL
          value: "app=skywalking-oap"
        - name: SW_CLUSTER_K8S_UID
          valueFrom:
            fieldRef:
              fieldPath: metadata.uid

5.2 K8s Provider实现

public class ClusterModuleKubernetesProvider extends ModuleProvider {
    
    private KubernetesClient k8sClient;
    
    @Override
    public void prepare() throws ModuleStartException {
        ClusterModuleKubernetesConfig config = 
            (ClusterModuleKubernetesConfig) getModuleConfig();
        
        // 使用Kubernetes官方Java客户端
        this.k8sClient = new DefaultKubernetesClient(
            new ConfigBuilder()
                .withNamespace(config.getNamespace())
                .build()
        );
        
        // 注册服务
        this.registerServiceImplementation(
            ClusterNodesQuery.class,
            new K8sClusterNodesQuery(k8sClient, config)
        );
    }
    
    @Override
    public void start() throws ModuleStartException {
        ClusterModuleKubernetesConfig config = 
            (ClusterModuleKubernetesConfig) getModuleConfig();
        
        // 将自己注册为集群节点
        // K8s中,Pod本身就是"注册"的——通过Label Selector发现
        String podUID = System.getenv(config.getUidEnvName());
        logger.info("K8s模式启动,Pod UID: {}", podUID);
        
        // 监听Pod变化(通过Watch API)
        k8sClient.pods()
            .withLabelSelector(config.getLabelSelector())
            .watch(new Watcher<Pod>() {
                @Override
                public void eventReceived(Action action, Pod pod) {
                    logger.info("K8s Pod事件: {} -> {}", 
                        action, pod.getMetadata().getName());
                }
            });
    }
}

5.3 K8s模式的配置

# application.yml
cluster:
  selector: ${SW_CLUSTER:kubernetes}
  kubernetes:
    # K8s命名空间
    namespace: ${SW_CLUSTER_K8S_NAMESPACE:default}
    
    # Pod标签选择器(用于发现同集群的OAP Pod)
    labelSelector: ${SW_CLUSTER_K8S_LABEL:app=skywalking-oap}
    
    # Pod UID环境变量名
    uidEnvName: ${SW_CLUSTER_K8S_UID:SKYWALKING_OAP_INSTANCE_UID}
    
    # 如何获取当前Pod的地址
    # 可选值: pod_ip (Pod IP), service_fqdn (Service DNS)
    howToGetSelfAddress: ${SW_CLUSTER_K8S_SELF_ADDRESS:pod_ip}

5.4 K8s方案的优缺点

维度 评价 说明
成熟度 ★★★★☆ K8s生态成熟,稳定可靠
部署复杂度 ★☆☆☆☆ 不需要额外部署协调器
运维负担 ★☆☆☆☆ 完全由K8s托管
可移植性 ★☆☆☆☆ 强绑定K8s,离开K8s不可用
功能丰富度 ★★★☆☆ 基础服务发现,高级功能有限
适用场景 纯K8s环境 非K8s环境无法使用

六、三(四)大方案对比矩阵

+==========================================================================+
|                     集群协调器方案对比                                    |
+==========================================================================+
|                     Standalone | ZooKeeper  | Nacos     | Kubernetes     |
|---------------------+-----------+------------+-----------+---------------|
| 高可用              |     ✗     |     ✓      |     ✓     |      ✓        |
| 自动故障检测        |     ✗     |     ✓      |     ✓     |      ✓        |
| 动态扩缩容          |     ✗     |     ✓      |     ✓     |      ✓        |
| 额外部署依赖        |     无    |  ZK集群     | Nacos集群  |   无需额外     |
| 配置管理集成        |     ✗     |     ✗      |     ✓     |      ✗        |
| 管理界面            |     ✗     | 第三方工具  |  内置UI    |  kubectl      |
| 学习曲线            |     低    |     中     |    中     |     低(云原生) |
| 社区活跃度          |    N/A    |     高     |    高     |      高       |
|=====================|===========|============|===========|===============|
| 推荐场景            |            |            |           |              |
|---------------------+-----------+------------+-----------+---------------|
| 开发/测试           |   ★★★★★   |   ★★★☆☆   |  ★★★★☆   |   ★★★★☆     |
| 中小规模生产         |   ☆☆☆☆☆   |   ★★★★☆   |  ★★★★★   |   ★★★★★     |
| 大规模生产           |   ☆☆☆☆☆   |   ★★★★☆   |  ★★★★★   |   ★★★★★     |
| 混合云/多云          |   ☆☆☆☆☆   |   ★★★★☆   |  ★★☆☆☆   |   ★★★★☆     |
| 纯K8s环境            |   ☆☆☆☆☆   |   ★★☆☆☆   |  ★★★☆☆   |   ★★★★★     |
+==========================================================================+

         图4:四大方案的全方位对比矩阵

七、选型决策树

                      你的部署环境是什么?
                              |
              +---------------+---------------+
              |                               |
           纯K8s环境                        非K8s环境
              |                               |
              v                               v
    "需要额外的配置中心吗?"           "已有哪些基础设施?"
              |                               |
      +-------+-------+               +-------+-------+
      |               |               |               |
     是              否              有ZK            有Nacos
      |               |               |               |
      v               v               v               v
    Nacos         Kubernetes        ZK             Nacos
  (配置+K8s)     (零依赖方案)    (复用现有)      (复用现有)
      |               |               |               |
      v               v               v               v
  +-------+       +-------+       +-------+       +-------+
  | 需要  |       | 不需要 |       | ZK版本|       | Nacos |
  | 部署  |       | 额外   |       | 要3.4+ |       | 1.x+ |
  | Nacos |       | 部署   |       +-------+        +-------+
  +-------+       +-------+

        图5:集群协调器选型决策树

八、总结

选型建议一句话总结:

  • 开发环境:Standalone,零配置
  • 已有ZooKeeper:复用ZK,减少基础设施
  • 云原生用户:Nacos,配置+服务发现一体
  • 纯K8s部署:Kubernetes模式,零额外依赖
  • 新项目推荐:Nacos > ZK(运维友好度更高)

下一篇,我们将讨论OAP集群的容量规划——多少个节点合适?内存和CPU怎么配?


上一篇【第46篇】OAP集群通信协议:节点间对话的暗语
下一篇【第48篇】OAP集群规划实战:从理论到生产的容量设计


更多推荐