Contents
  1. 1. 八卦协议:一个简单的服务发现方案
    1. 1.1. 背景
    2. 1.2. 设计目标
    3. 1.3. 核心思想:无需时钟同步
    4. 1.4. 流程设计
    5. 1.5. status 接口设计
    6. 1.6. 为什么只传 1/3 的 peers?
    7. 1.7. 缓存文件设计
    8. 1.8. 同步脚本核心逻辑
    9. 1.9. 优化策略
    10. 1.10. 实际效果
    11. 1.11. 优点总结
    12. 1.12. 总结

八卦协议:一个简单的服务发现方案

背景

在微服务架构中,服务发现是一个常见需求。市面上有 Consul、Etcd、Nacos 等成熟方案,但对于小型集群(3~10 节点),这些方案显得有点”重”。

本文介绍一个基于八卦协议(Gossip Protocol)的轻量级服务发现方案:定时探测服务 status 接口,自动更新 Nginx upstream

设计目标

  • 简单、够用、清晰
  • 无需引入额外组件
  • 支持自动剔除故障节点
  • 支持手动触发(紧急情况)

核心思想:无需时钟同步

分布式系统中,时钟同步是个老大难问题。本方案巧妙地绕过了它:

  • 源头时间戳(src_ts):数据产生时的时间戳,用于判断数据新旧
  • 本地收到时间(rcv_ts):本机收到数据的时间,用于判断是否过期
  • 过期判断当前本地时间 - rcv_ts > TTL 就认为过期

Round 1

Round 2

这样,即使各节点时钟不同步,也能正确判断数据新旧和过期。

流程设计

定时脚本(每5分钟)
    ↓
curl 各服务器的 /status 接口
    ↓
获取:版本号、时间戳、peers 列表
    ↓
更新本地缓存(cache.json)
    ↓
生成 upstream 配置(剔除过期节点)
    ↓
配置有变化 → nginx reload

status 接口设计

每个服务提供一个 /status 接口:

{
  "status": "ok",
  "service": "sleep",
  "version": "1.2.3",
  "timestamp": 1770088942,
  "peers": ["10.0.0.5"]
}

关键字段:

  • timestamp:源头时间戳
  • peers:随机返回 1/3 的已知节点(八卦协议精髓)
  • version:服务版本,便于追踪

为什么只传 1/3 的 peers?

这是八卦协议的精髓:

  1. 数据量小:每次传输的数据量恒定
  2. 多轮覆盖:几轮下来自然传播完整
  3. 持续通信:吊胃口,下次还得来问

这种”不完整传播”的设计,让节点间保持持续的通信,同时避免了广播风暴。

缓存文件设计

使用一个 JSON 文件存储所有状态:

{
  "services": {
    "api": {
      "port": 8861,
      "instances": {
        "10.0.0.3": {"version": "1.2.3", "src_ts": 1000, "rcv_ts": 2000},
        "10.0.0.5": {"version": "1.2.3", "src_ts": 1000, "rcv_ts": 2001}
      }
    }
  },
  "peers": {
    "10.0.0.3": {"src_ts": 1000, "rcv_ts": 2000},
    "10.0.0.5": {"src_ts": 1000, "rcv_ts": 2001}
  }
}

同步脚本核心逻辑

#!/bin/bash
CACHE_FILE="/etc/gossip/cache.json"
UPSTREAM_DIR="/etc/nginx/upstream.d"
TTL=600
NOW=$(date +%s)

# 探测所有服务
probe_all() {
    local PEERS=$(jq -r ".peers | keys[]" $CACHE_FILE | shuf)
    for IP in $PEERS; do
        RESP=$(curl -s --connect-timeout 2 "http://$IP:$PORT/status")
        # 解析响应,更新缓存...
    done
}

# 生成 upstream 配置
generate_all() {
    # 只保留未过期的节点
    jq -r ".services.\"$SERVICE\".instances | to_entries[] |
        select(($NOW - .value.rcv_ts) < $TTL) | ..."
}

优化策略

1. 增量探测

全量探测在大集群时效率低。可以只探测”快过期”的节点:

# 只探测剩余时间 < TTL/3 的节点
remaining = TTL - (NOW - rcv_ts)
if remaining < TTL/3:
    probe(node)

2. 推模式

拉模式本质是轮询。对于大集群(>10 节点),可以加入推模式:

服务启动/停止 → 推给几个邻居 → 邻居再推给它的邻居 → 八卦扩散

实际效果

在我们的 3 节点集群中部署后:

$ /etc/gossip/sync-upstream.sh

[2026-02-03 19:27:01] ===== 开始同步 =====
[2026-02-03 19:27:01] 探测 api 10.0.0.2:8861 成功 version=20260203... ts=1770118021
[2026-02-03 19:27:01] upstream api 配置已变更
[2026-02-03 19:27:01] openresty 已重载
[2026-02-03 19:27:01] ===== 同步完成 =====

优点总结

特点 说明
简单 一个 Shell 脚本 + cron,无需额外组件
自愈 服务挂了自动剔除,恢复后自动加回
零侵入 只需服务提供 /status 接口
可扩展 小集群用拉模式,大集群加推模式

总结

对于小型集群,不一定需要重量级的服务发现组件。一个简单的八卦协议实现,配合 Nginx upstream 动态更新,就能满足大部分场景。

核心理念:简单、够用、清晰

Contents
  1. 1. 八卦协议:一个简单的服务发现方案
    1. 1.1. 背景
    2. 1.2. 设计目标
    3. 1.3. 核心思想:无需时钟同步
    4. 1.4. 流程设计
    5. 1.5. status 接口设计
    6. 1.6. 为什么只传 1/3 的 peers?
    7. 1.7. 缓存文件设计
    8. 1.8. 同步脚本核心逻辑
    9. 1.9. 优化策略
    10. 1.10. 实际效果
    11. 1.11. 优点总结
    12. 1.12. 总结