KBEngine 文档KBEngine 文档
首页
源码学习
架构
API
资料
指南
GitHub
首页
源码学习
架构
API
资料
指南
GitHub
  • Part I 为什么长这样

    • 源码学习首页
    • 1. 导读与阅读方法
    • 2. BigWorld:问题、模型与核心概念
    • 3. KBEngine 系统全景
  • Part II 运行骨架

    • 4. 启动流程与进程模型
    • 5. EntityDef 与实体定义系统
    • 6. Python 运行时与脚本桥接
  • Part III 基础设施层

    • 7. 并发模型、线程与内存基础设施
    • 8. 网络基础设施:I/O 模型与进程间通信
    • 9. 分布式基础:ID、发现、注册与一致性
  • Part IV 通信与协作

    • 10. 序列化、Bundle 与网络消息
    • 11. RPC、EntityCall 与通信模式
    • 12. 属性同步与数据包广播
    • 13. 数据库、DBMgr 与持久化
  • Part V 空间、运动与拓扑

    • 14. Space、AOI 与视野系统
    • 15. 空间拓扑与动态扩容
    • 16. 移动、寻路与导航
    • 17. Ghost 系统
  • Part VI 脚本层行为

    • 18. 钩子、回调、定时器与事件
  • Part VII 前后端交互

    • 19. 客户端协议与前后端交互
  • Part VIII 运维、调试与稳定性

    • Ch20 可观测性:监控、性能分析与调试
    • Ch21 热更新、容错与运维工具
  • Part IX 串联与实战

    • Ch22 玩家完整生命周期
    • Ch23 BigWorld 与 KBEngine 对照
    • Ch24 实战源码走读
  • 阅读辅助

    • 全部目录
  • Appendix

    • 附录 A 源码阅读地图与下一步
    • 附录 B 关键算法速查
    • 附录 C 外部参考系统速查
    • 附录 D 专业术语速查
    • 附录 E 引擎适用场景与游戏类型选型指南
    • 附录 F 坐标系约定:BigWorld 与 KBEngine
    • 附录 G 服务器时间管理与世界时钟

Ch20 可观测性:监控、性能分析与调试

核心问题:在一个运行中的 MMO 集群里,你怎么知道系统现在健康不健康?出了问题怎么定位?

20.1 可观测性三支柱在游戏服务器中的映射

业界将可观测性(Observability)拆解为三根支柱:Metrics(指标)、Tracing(链路追踪)、Logging(日志)。在 BigWorld / KBEngine 中,这三根支柱的对应关系如下:

支柱业界标准BigWorld / KBEngine 对应完备度
MetricsPrometheus + GrafanaWatcher + ProfileVal / SendingStats有基础指标,无时序存储和告警
TracingOpenTelemetry Tracing无分布式链路追踪缺失
LoggingELK / LokiLogger / message_logger有集中日志,无结构化查询

两套项目的可观测性体系是面向开发调试设计的,而非面向生产运维。本章逐一剖析每个子系统的实现,并在最后讨论如何接入现代可观测性栈。


20.2 Watcher 系统:游戏服务器的"metrics 端点"

20.2.1 设计理念

Watcher 的核心思想极其简洁:任意 C++ 变量或函数返回值,都可以注册到一棵路径式的观察树中,通过远程连接实时查询。

components/
├── cellapp1/
│   ├── entities/            → 实体总数
│   ├── tickProfile/         → tick 耗时统计
│   └── aoIUpdates/          → AOI 更新次数
├── baseapp1/
│   └── ...
└── dbmgr/
    └── ...

这与 Prometheus 的 /metrics 端点本质上是同一模式——拉取式(查询时取值)。但 Watcher 没有时序存储、没有聚合、没有告警——只有当前值快照。

20.2.2 KBEngine Watcher 实现

KBEngine 的 Watcher 体系由以下四个模板类构成:

// kbe/src/lib/helper/watcher.h

// 基类:路径式标识 + 序列化接口
class WatcherObject {
public:
    WatcherObject(std::string path);
    virtual void addToInitStream(MemoryStream* s) {};
    virtual void addToStream(MemoryStream* s) {};
    const char* path() { return path_.c_str(); }
    const char* name() { return name_.c_str(); }
    virtual WATCHER_VALUE_TYPE getType() { return WATCHER_VALUE_TYPE_UNKNOWN; }
protected:
    std::string path_, name_, strval_;
    WATCHER_ID id_;
    int32 numWitness_;   // 当前观察者数量
};

// 监视一个值(引用绑定)
template <class T>
class WatcherValue : public WatcherObject {
public:
    WatcherValue(std::string path, const T& pVal)
      : WatcherObject(path), watchVal_(pVal) {}
    void addToStream(MemoryStream* s) { (*s) << id_ << watchVal_; }
    T getValue() { return watchVal_; }
protected:
    const T& watchVal_;  // 绑定到被监视变量的引用
};

// 监视一个自由函数的返回值
template <class RETURN_TYPE>
class WatcherFunction : public WatcherObject {
    typedef RETURN_TYPE(*FUNC)();
    void addToStream(MemoryStream* s) { (*s) << id_ << (*func_)(); }
protected:
    FUNC func_;
};

// 监视一个成员函数的返回值
template <class RETURN_TYPE, class OBJ_TYPE>
class WatcherMethod : public WatcherObject {
    typedef RETURN_TYPE(OBJ_TYPE::*FUNC)();
    void addToStream(MemoryStream* s) { (*s) << id_ << (obj_->*func_)(); }
protected:
    FUNC func_;
    OBJ_TYPE* obj_;
};

这四层抽象的含义:

类用途典型场景
WatcherObject基类,定义序列化接口—
WatcherValue<T>绑定一个 C++ 变量的引用监视实体数量 numEntities_
WatcherFunction<R>绑定一个自由函数监视全局统计函数返回值
WatcherMethod<R, OBJ>绑定一个成员函数监视 Component::tickTime()

所有 Watcher 注册到 Watchers 容器中,由 WatcherPaths 提供路径式的层次管理:

// kbe/src/lib/helper/watcher.h

class Watchers {
public:
    static Watchers& rootWatchers();
    typedef KBEUnordered_map<std::string, KBEShared_ptr<WatcherObject>> WATCHER_MAP;
    bool addWatcher(const std::string& path, WatcherObject* pwo);
    KBEShared_ptr<WatcherObject> getWatcher(const std::string& name);
    void addToStream(MemoryStream* s);  // 序列化所有 watcher 值
};

class WatcherPaths {
public:
    static WatcherPaths& root();
    typedef KBEUnordered_map<std::string, KBEShared_ptr<WatcherPaths>> WATCHER_PATHS;
    bool addWatcher(std::string path, WatcherObject* pwo);
    void dirPath(std::string path, std::vector<std::string>& vec);
};

WatcherPaths 构成一棵路径树——root/components/cellapp1/entities 这种结构在内部被逐级拆解为嵌套的 WatcherPaths 节点。

值类型系统:KBEngine 定义了 14 种 watcher 值类型(WATCHER_VALUE_TYPE_*),从 UINT8 到 COMPONENT_TYPE,覆盖了所有基础 C++ 类型。序列化时值类型随数据一起传递,客户端据此解析。

20.2.3 BigWorld Watcher 实现(更完整)

BigWorld 的 Watcher 体系更为成熟,增加了网络接入层和分布式转发两个关键能力。

WatcherNub——网络接入层:

// BigWorld: lib/network/watcher_nub.hpp

enum WatcherMsg {
    WATCHER_MSG_GET = 16,          // 查询 watcher 值
    WATCHER_MSG_SET = 17,          // 设置 watcher 值
    WATCHER_MSG_TELL = 18,         // 返回 watcher 值
    WATCHER_MSG_GET2 = 26,         // V2 协议
    WATCHER_MSG_SET2 = 27,
    WATCHER_MSG_TELL2 = 28,
    WATCHER_MSG_SET2_TELL2 = 29,
    WATCHER_MSG_EXTENSION_START = 107
};

struct WatcherRegistrationMsg {
    int  version;
    int  uid;
    int  message;     // WATCHER_...
    int  id;          // e.g. 14
    char abrv[32];    // e.g. "cell14"
    char name[64];    // e.g. "Cell 14"
};

WatcherNub 同时支持 TCP 和 UDP 监听。每个组件启动时通过 WatcherRegistrationMsg 向 Mgr 注册自己的 watcher 端口,Mgr 即可通过 GET/SET 消息远程查询和修改任意组件的 watcher 值。

ForwardingWatcher——分布式转发:

这是 BigWorld Watcher 最强大的特性:

// BigWorld: lib/server/watcher_forwarding.hpp

class ForwardingWatcher : public Watcher {
public:
    enum ExposeHints {
        WITH_ENTITY = 0,     // 组件拥有特定实体
        CELL_APPS,           // 所有 CellApp
        WITH_SPACE,          // 特定 Space 的所有组件
        LEAST_LOADED,        // 负载最低的组件
        LOCAL_ONLY,          // 仅本地
        BASE_APPS,           // 所有 BaseApp
        SERVICE_APPS,        // 所有 ServiceApp
        BASE_SERVICE_APPS,   // 所有 Base + Service App
    };

    virtual ForwardingCollector* newCollector(
        WatcherPathRequestV2& pathRequest,
        const BW::string& destWatcher,
        const BW::string& targetInfo) = 0;
};

ForwardingWatcher 挂载在 CellAppMgr / BaseAppMgr 上。当 Mgr 收到 watcher 查询请求时:

  1. 解析路径中的 ExposeHints(如 CELL_APPS 表示查所有 CellApp)
  2. 创建 ForwardingCollector
  3. ForwardingCollector 通过 Mercury 向所有目标组件广播 GET 请求
  4. 收集所有回复,汇总后返回给调用者
查询者 ──GET──→ CellAppMgr ──GET──→ CellApp1
                              ├──GET──→ CellApp2
                              ├──GET──→ CellApp3
                ←──TELL──  (汇总)  ←──TELL── ...

这意味着运维人员可以从 CellAppMgr 一站式看到所有 CellApp 的状态,无需逐台登录。 这是 KBEngine 所不具备的能力。

20.2.4 与 Prometheus 的对比

维度WatcherPrometheus
模式拉取式(查询时取值)拉取式(定期 scrape)
时序存储无内置 TSDB
聚合无PromQL 聚合查询
告警无AlertManager
分布式BigWorld 有转发;KBEngine 无Service Discovery + Federation
定位开发调试工具生产监控系统

如果要将 Watcher 接入 Prometheus,只需写一个 exporter 定期拉取 Watcher 值推给 pushgateway,或者在组件上暴露 HTTP /metrics 端点。


20.3 Profiler:函数级性能剖析

20.3.1 KBEngine Profile / ProfileVal

KBEngine 的 Profiler 采用栈式嵌套设计,能精确测量函数间的耗时关系:

// kbe/src/lib/helper/profile.h

class ProfileVal {
public:
    void start() {
        TimeStamp now = timestamp();
        if (inProgress_++ == 0)
            lastTime_ = now;          // 记录第一次进入的时间

        ProfileGroup::PROFILEVALS& stack = pProfileGroup_->stack();

        // 如果栈中有对象,说明从上一个函数进入了本函数
        // 取上一个函数到本函数之间耗时的"内部时间"
        if (!stack.empty()) {
            ProfileVal& profile = *stack.back();
            profile.lastIntTime_ = now - profile.lastIntTime_;
            profile.sumIntTime_ += profile.lastIntTime_;
        }
        stack.push_back(this);
        lastIntTime_ = now;
    }

    void stop(uint32 qty = 0) {
        TimeStamp now = timestamp();
        // inProgress_ 为 0 时,计算本函数的总耗时
        if (--inProgress_ == 0) {
            lastTime_ = now - lastTime_;
            sumTime_ += lastTime_;
        }
        lastQuantity_ = qty;
        sumQuantity_ += qty;
        ++count_;

        ProfileGroup::PROFILEVALS& stack = pProfileGroup_->stack();
        stack.pop_back();
        lastIntTime_ = now - lastIntTime_;
        sumIntTime_ += lastIntTime_;

        // 恢复上层函数的内部时间起点
        if (!stack.empty())
            stack.back()->lastIntTime_ = now;
    }

    // 关键字段
    std::string  name_;
    TimeStamp    lastTime_;       // 最近一次总耗时
    TimeStamp    sumTime_;        // 累计总耗时
    TimeStamp    lastIntTime_;    // 最近一次内部耗时
    TimeStamp    sumIntTime_;     // 累计内部耗时
    uint32       count_;          // 调用次数
    int          inProgress_;     // 重入计数
};

这里有两组时间度量:

  • 总耗时(lastTime_ / sumTime_):从 start() 到 stop() 的完整时间,包含所有子调用
  • 内部耗时(lastIntTime_ / sumIntTime_):仅本函数自身消耗的时间,排除子 ProfileVal 的耗时

这个区分非常重要——它能告诉你一个函数本身花了多少时间 vs 它调用的子函数花了多少时间。

ScopedProfile RAII 宏:

// kbe/src/lib/helper/profile.h

class ScopedProfile {
public:
    ScopedProfile(ProfileVal& profile, const char* filename, int lineNum)
      : profile_(profile), filename_(filename), lineNum_(lineNum) {
        profile_.start();
    }
    ~ScopedProfile() { profile_.stop(filename_, lineNum_); }
};

// 使用方式
#define AUTO_SCOPED_PROFILE(NAME)                   \
    static ProfileVal _localProfile(NAME);          \
    ScopedProfile _autoScopedProfile(_localProfile, __FILE__, __LINE__);

#define START_PROFILE(PROFILE)  PROFILE.start();
#define STOP_PROFILE(PROFILE)   PROFILE.stop(__FILE__, __LINE__);

AUTO_SCOPED_PROFILE 在函数开头声明即可,离开作用域时自动 stop。static 保证 ProfileVal 只初始化一次,后续调用复用同一对象累积统计。

CellApp 组件级 Profile 组:

// kbe/src/server/cellapp/profile.h(示例,各组件独立定义)

// cellapp 中定义的关键 ProfileVal:
extern ProfileVal ON_MOVE_PROFILE;
extern ProfileVal ON_NAVIGATE_PROFILE;
extern ProfileVal CLIENT_UPDATE_PROFILE;
extern ProfileVal AOI_UPDATE_PROFILE;
// ... 更多

每个组件在 profile.h 中声明一组全局 ProfileVal,在关键路径上用 START_PROFILE / STOP_PROFILE 包裹,运行时通过 Watcher 查询。

20.3.2 BigWorld Profiler(更细致)

BigWorld 的 Profiler 提供了多层粒度的性能追踪:

核心 Profiler(内存环形缓冲区):

// BigWorld: lib/cstdmf/profiler.hpp

class Profiler {
public:
    enum ProfileMode {
        PROFILE_OFF = 0,
        HIERARCHICAL,       // 层次化显示
        SORT_BY_TIME,       // 按耗时排序
        SORT_BY_NUMCALLS,   // 按调用次数排序
        SORT_BY_NAME,       // 按名称排序
        CPU_GPU,            // CPU/GPU 分离显示
        GRAPHS,             // 图形化
        CORES,              // 按核心显示
    };

    enum EventType {
        EVENT_START,        // 计时开始
        EVENT_START_IDLE,   // 空闲计时开始
        EVENT_END,          // 计时结束
        EVENT_COUNTER       // 计数器事件
    };

    enum EventCategory {
        CATEGORY_CPP    = (1 << 0),
        CATEGORY_GPU    = (1 << 1),
        CATEGORY_PYTHON = (1 << 2),
        CATEGORY_ALL    = (1 << 3) - 1
    };

    void addEntry(const char* text, EventType e,
                  int32 value, EventCategory category);
    void tick();
    void setProfileMode(ProfileMode mode, bool inclusive);
    void getStatistics(BW::vector<ProfileLine>* profileOutput);
};

BigWorld 的 Profiler 使用内存环形缓冲区记录事件,支持按 C++ / GPU / Python 分类,有层次化、按耗时排序等多种输出模式。

EntityProfiler——单个实体的性能追踪:

// BigWorld: lib/server/entity_profiler.hpp

class EntityProfiler {
public:
    void start() const;
    void stop() const;
    void tick(uint64 tickDtInStamps,
              float smoothingFactor,
              EntityTypeProfiler& typeProfiler);

    float load() const { return currAdjustedLoad_; }
    float rawLoad() const { return currRawLoad_; }
    float maxRawLoad() const { return maxRawLoad_; }

    // RAII 自动作用域追踪
    template <class ENTITY>
    class AutoScopedHelper {
    public:
        AutoScopedHelper(const ENTITY* pEntity) : pEntity_() {
            if (pEntity) {
                pEntity_ = pEntity;
                pEntity->profiler().start();
            }
        }
        ~AutoScopedHelper() {
            if (pEntity_) pEntity_->profiler().stop();
        }
    private:
        ConstSmartPointer<ENTITY> pEntity_;
    };

private:
    mutable uint64 elapsedTickTime_;
    float currAdjustedLoad_;   // 指数平滑后的负载
    float currRawLoad_;        // 原始负载
    float maxRawLoad_;         // 最大负载
};

// 使用宏
#define AUTO_SCOPED_THIS_ENTITY_PROFILE  \
    EntityProfiler::AutoScopedHelper<BaseOrEntity> _autoEntityProfile(this)

EntityProfiler 是实体粒度的负载追踪。每个 tick 开始时 start(),实体处理完成时 stop(),tick() 方法将本 tick 的耗时转化为负载值并进行指数平滑(smoothing),然后上报给 EntityTypeProfiler。

EntityTypeProfiler——按实体类型聚合:

// BigWorld: lib/server/entity_type_profiler.hpp

class EntityTypeProfiler {
public:
    void addEntityLoad(float entitySmoothedLoad,
                       float entityRawLoad,
                       float entityAddedLoad);
    void tick();

    float load() const { return currSmoothedLoad_; }
    float rawLoad() const { return currRawLoad_; }
    int numEntities() const { return numEntities_; }

private:
    float currSmoothedLoad_;  // 该类型的平均平滑负载
    float currRawLoad_;
    float currAddedLoad_;
    int numEntities_;         // 该类型的实体数量
};

CellProfiler——Cell 级负载:

// BigWorld: server/cellapp/cell_profiler.hpp

class CellProfiler {
public:
    void addEntityLoad(float entitySmoothedLoad, float entityRawLoad);
    void tick();
    float load() const { return currSmoothedLoad_; }
    float rawLoad() const { return currRawLoad_; }
    float maxRawLoad() const { return maxRawLoad_; }

private:
    float currSmoothedLoad_;
    float currRawLoad_;
    float maxRawLoad_;
    float accSmoothedLoad_;  // 一个 tick 内的累积值
    float accRawLoad_;
};

三级 Profiler 的数据流向:

EntityProfiler (per-entity)
    │ tick() → addEntityLoad()
    ↓
EntityTypeProfiler (per-type)     ← 聚合同类型的实体负载
    │
    ↓
CellProfiler (per-cell)           ← 汇总所有类型 → Cell 负载
    │
    ↓
doBalance()                       ← 基于 Cell 负载做动态均衡

这个三级体系让运维可以精确定位性能瓶颈在哪个实体的哪个类型上——是 NPC 太多了?还是 AOI 触发器太密了? entityTypeProfiler 直接告诉你答案。

20.3.3 与 pprof / perf 的关系

维度引擎内置 Profilerpprof / perf
粒度逻辑级(哪个函数/实体类型慢)系统级(CPU 火焰图)
语言感知知道实体类型、Watcher 路径只知道 C++ 函数符号
开销极低(纳秒级时间戳)采样开销可控
输出Watcher 树 + ProfileVal 值火焰图 / top / callgraph

两者互补:引擎 Profiler 知道"哪个实体类型 tick 慢",pprof 知道"哪个系统调用慢"。


20.4 NetworkStats:网络层统计

20.4.1 KBEngine NetworkStats

// kbe/src/lib/network/network_stats.h

class NetworkStats : public Singleton<NetworkStats> {
public:
    enum S_OP { SEND, RECV };

    struct Stats {
        std::string name;
        uint32 send_size;    // 发送总字节数
        uint32 send_count;   // 发送次数
        uint32 recv_size;    // 接收总字节数
        uint32 recv_count;   // 接收次数
    };

    typedef KBEUnordered_map<std::string, Stats> STATS;

    void trackMessage(S_OP op, const MessageHandler& msgHandler, uint32 size);
    STATS& stats() { return stats_; }

private:
    STATS stats_;
};

每条网络消息经过 trackMessage() 时,按消息名聚合 send_count / send_size / recv_count / recv_size。这是一个消息级的统计,能告诉你哪种消息占了多少带宽。

20.4.2 BigWorld SendingStats

// BigWorld: lib/network/sending_stats.hpp

class SendingStats : public TimerHandler {
public:
    double bitsPerSecond() const;
    double packetsPerSecond() const;
    double messagesPerSecond() const;

    uint numPacketsSent() const;
    uint numBundlesSent() const;
    uint numPacketsResent() const;     // 重传统计
    uint numPiggybacks() const;        // 捎带统计
    uint numFailedBundleSend() const;  // 发送失败统计
    uint numFailedPacketSend() const;

private:
    Stat numBytesSent_;
    Stat numBytesResent_;
    Stat numPacketsSent_;
    Stat numPacketsResent_;
    Stat numPiggybacks_;
    Stat numBytesPiggybacked_;
    Stat numBundlesSent_;
    Stat numMessagesSent_;
    Stat numReliableMessagesSent_;
    Stat numFailedPacketSend_;
    Stat numFailedBundleSend_;
};

BigWorld 的 SendingStats 提供了更丰富的网络指标,包括每秒比特率(bitsPerSecond)、每秒包数(packetsPerSecond)、每秒消息数(messagesPerSecond),以及可靠传输的重传统计和 piggyback 统计。这些是定位网络瓶颈的关键指标。


20.5 Telnet 调试控制台

20.5.1 KBEngine Telnet 实现

// kbe/src/lib/server/telnet_handler.h

class TelnetHandler : public Network::InputNotificationHandler {
public:
    enum TELNET_STATE {
        TELNET_STATE_PASSWD,    // 密码认证
        TELNET_STATE_ROOT,      // 根命令模式
        TELNET_STATE_PYTHON,    // Python 解释器模式
        TELNET_STATE_READONLY,  // 只读模式
        TELNET_STATE_QUIT       // 退出
    };

    TelnetHandler(Network::EndPoint* pEndPoint,
                  TelnetServer* pTelnetServer,
                  Network::NetworkInterface* pNetworkInterface,
                  TELNET_STATE defstate = TELNET_STATE_ROOT);
};

Telnet 状态机的工作流程:

  1. TELNET_STATE_PASSWD:输入密码认证
  2. TELNET_STATE_ROOT:可执行 C++ 层命令(查看 watcher、查看 profile 等)
  3. TELNET_STATE_PYTHON:进入 Python 解释器,可直接操作实体、调用脚本方法
  4. TELNET_STATE_READONLY:会话中可切换的只读模式,仅能查询不能修改

每个组件(CellApp、BaseApp、DBMgr 等)启动时都会创建 TelnetServer:

// kbe/src/server/cellapp/cellapp.cpp

pTelnetServer_ = new TelnetServer(&this->dispatcher(), &this->networkInterface());
pTelnetServer_->pScript(&this->getScript());
pTelnetServer_->start(g_kbeSrvConfig.getCellApp().telnet_passwd,
                       g_kbeSrvConfig.getCellApp().telnet_deflayer,
                       g_kbeSrvConfig.getCellApp().telnet_port);

telnet_deflayer 在 TelnetServer::start() 里实际只识别 "python" 和默认的 root。readonly/quit 是会话内状态,不是启动默认层。它的价值主要是在线排查和临时 profile,不是对外的长期运维 API。

20.5.2 Telnet 使用指南

启动说明

Telnet 服务随组件自动启动,无需手动开启。

每个组件(CellApp、BaseApp、LoginApp、DBMgr、Logger 等)在启动时会自动创建并启动 TelnetServer:

// 组件初始化时自动执行
pTelnetServer_ = new TelnetServer(...);
pTelnetServer_->start(passwd, deflayer, port);
特性说明
自动启动组件启动时 Telnet 同时启动
自动关闭组件关闭时 Telnet 同时关闭
无需手动操作任何时候都可以直接连接
独立端口每个组件有自己的 Telnet 端口
端口自动递增多实例时端口冲突自动递增

日志中会显示 Telnet 启动信息:

[INFO]: TelnetServer server is running on port 20005

多实例端口分配机制

问题:启动多个 BaseApp/CellApp 实例时,它们都配置相同的 telnet_port,会冲突吗?

答案:不会冲突。KBEngine 有端口自动递增机制。

源码实现:

// kbe/src/lib/server/telnet_server.cpp:44-66
while(true)
{
    if (listener_.bind(port, ip) == -1)
    {
        port++;        // 绑定失败,端口 +1
        continue;      // 继续尝试
    }
    else
    {
        break;         // 绑定成功
    }
}

实际分配示例(配置 telnet_port=20000):

实例配置端口绑定结果实际端口
BaseApp #120000✅ 成功20000
BaseApp #220000❌ 冲突 → 2000120001
BaseApp #320000❌ 冲突 → 2000220002
CellApp #130000✅ 成功30000
CellApp #230000❌ 冲突 → 3000130001

查看实际端口:启动日志会显示每个组件的实际监听端口。

快速连接

# 连接到 BaseApp(默认端口 20000+)
telnet localhost 20000    # 第1个 BaseApp
telnet localhost 20001    # 第2个 BaseApp(自动递增)

# 连接到 CellApp(默认端口 30000+)
telnet localhost 30000    # 第1个 CellApp
telnet localhost 30001    # 第2个 CellApp(自动递增)

# 连接到 LoginApp(通常只有1个)
telnet localhost <配置的端口>

不确定端口时:查看启动日志中的 TelnetServer server is running on port XXXXX 信息。

连接后会提示输入密码(telnet_passwd,在 kbengine.xml 中配置)。

命令列表

进入系统后输入 :help 查看所有可用命令:

命令说明用法示例
:help显示命令列表:help
:quit退出连接:quit
:python进入 Python 解释器模式:python
:root返回根命令模式:root
:cprofileC++ 性能剖析:cprofile 30
:pyprofilePython 性能剖析:pyprofile 30
:eventprofile事件剖析:eventprofile 30
:networkprofile网络剖析:networkprofile 30
:pytickprofilePython tick 剖析:pytickprofile 30

状态模式

Telnet 有四种工作状态:

PASSWD(输入密码)
    ↓
ROOT(根命令模式,可执行 C++ 命令)
    ↓ [:python]
PYTHON(Python 解释器模式)
    ↓ [:root]
ROOT
    ↓ [:quit]
QUIT(退出)

Python 模式使用

输入 :python 进入 Python 模式后,可以直接执行 Python 代码:

# 查看所有实体
>>> KBEngine.entities
{'1001': <Avatar object at 0x...>, '1002': <Monster object at 0x...>}

# 查看具体实体
>>> entity = KBEngine.entities['1001']
>>> entity.position
(100.0, 200.0, 300.0)

# 调用实体方法
>>> entity.level
10
>>> entity.level = 20  # 修改属性

常用操作:

# 统计实体数量
>>> len(KBEngine.entities)
1500

# 按类型过滤
>>> [e for e in KBEngine.entities.values() if e.__class__.__name__ == 'Avatar']
[<Avatar ...>, <Avatar ...>, ...]

# 查看所有 Avatar 等级
>>> [(e.id, e.level) for e in KBEngine.entities.values() if hasattr(e, 'level')]
[(1001, 10), (1002, 25), ...]

# 踢出指定玩家
>>> KBEngine.entities['1001'].logout()

性能剖析

C++ 性能剖析(:cprofile):

:cprofile 30
# 会采集 30 秒的 C++ 函数调用数据
# 完成后显示各函数耗时排序

Python 性能剖析(:pyprofile):

:pyprofile 30
# 采集 30 秒的 Python 函数调用数据
# 用于定位 Python 脚本性能瓶颈

网络剖析(:networkprofile):

:networkprofile 60
# 分析网络消息的发送/接收情况
# 查看带宽使用最高的消息类型

配置说明

在 kbengine.xml 或 kbengine_defaults.xml 中配置:

<!-- CellApp 配置 -->
<CellApp>
    <telnet_port>30000</telnet_port>
    <telnet_passwd>xxxxxx</telnet_passwd>
    <telnet_deflayer>root</telnet_deflayer>  <!-- 或 python -->
</CellApp>

<!-- BaseApp 配置 -->
<BaseApp>
    <telnet_port>20000</telnet_port>
    <telnet_passwd>xxxxxx</telnet_passwd>
    <telnet_deflayer>root</telnet_deflayer>
</BaseApp>

多实例配置说明:

  • 所有 BaseApp/CellApp 实例使用相同的 telnet_port 配置
  • 实际运行时,第 2 个及之后的实例会自动使用 配置端口 + 1、配置端口 + 2...
  • 无需为每个实例单独配置端口

安全注意事项

  1. 生产环境务必修改默认密码
  2. Telnet 连接未加密,不应通过公网访问
  3. Python 模式可执行任意代码,权限控制至关重要
  4. 建议通过 VPN 或内网访问 Telnet 端口

与 GUIConsole 的关系

维度TelnetGUIConsole
接入方式命令行 telnet/ncWindows 桌面程序
使用场景Linux 服务器远程调试本地开发调试
功能完全一致完全一致
多进程管理需分别连接各端口统一界面管理

GUIConsole 本质上是 Telnet 协议的图形化前端,底层通信协议完全相同。

Python 调试进阶

Telnet 的 Python 模式是一个交互式 shell,但缺乏断点、单步执行等调试功能。以下介绍几种更强大的调试方法。

方法一:Telnet Python 模式的能力和限制

能做什么:

  • 执行任意 Python 表达式
  • 查看和修改实体属性
  • 调用实体方法
  • 运行简单的诊断脚本

不能做什么:

  • 设置断点
  • 单步执行代码
  • 查看调用栈
  • 查看局部变量(除非在当前作用域)

Telnet 调试示例:

# 在 Telnet Python 模式中
>>> # 找到问题实体
>>> entity = KBEngine.entities['1001']
>>>
>>> # 查看状态
>>> entity.hp
100
>>> entity.maxHp
500
>>>
>>> # 模拟调用
>>> entity.takeDamage(100)
>>>
>>> # 检查结果
>>> entity.hp
0
>>> entity.isDead()
True
方法二:使用 pdb 内置断点调试

在需要调试的代码位置插入 pdb.set_trace(),当执行到该位置时会进入交互式调试。

步骤:

  1. 在代码中插入断点:
# file: base/scripts/Avatar.py
import KBEngine
import pdb  # 导入 pdb

class Avatar(KBEngine.Entity):
    def takeDamage(self, damage):
        currentHp = self.hp
        pdb.set_trace()  # 设置断点

        # 执行会在这里暂停,进入 pdb 交互模式
        if currentHp > damage:
            self.hp -= damage
        else:
            self.hp = 0
            self.onDeath()
  1. 运行时触发断点:

当代码执行到 pdb.set_trace() 时,会在终端(或日志)中进入 pdb 模式:

(Pdb)
  1. pdb 常用命令:
命令说明
h 或 help显示帮助
l 或 list显示当前代码位置
n 或 next执行下一行(不进入函数)
s 或 step执行下一行(进入函数)
c 或 continue继续执行直到下一个断点
p 变量名打印变量值
pp 变量名美化打印变量值
w 或 where显示调用栈
q 或 quit退出调试器

pdb 调试示例:

>>> # 在 pdb 模式中
(Pdb) p damage        # 查看变量
100
(Pdb) p self.hp       # 查看属性
500
(Pdb) l               # 查看代码
234         currentHp = self.hp
235         pdb.set_trace()
236  ->     if currentHp > damage:
237             self.hp -= damage
238         else:
239             self.hp = 0
(Pdb) n               # 单步执行
240         else:
241  ->     self.hp = 0
(Pdb) c               # 继续执行
方法三:使用 debugpy + VSCode 远程调试

这是最强大的调试方式,可以在 VSCode 中设置断点、查看变量、单步执行。

原理:在 KBEngine Python 环境中启动 debugpy 服务,VSCode 作为客户端连接。

⚠️ 重要:KBEngine 使用嵌入式 Python

KBEngine 的 Python 环境与系统 Python 是隔离的:

  • Py_NoSiteFlag = 1 → 禁止导入 site.py
  • Py_IgnoreEnvironmentFlag = 1 → 忽略 PYTHONPATH 环境变量
  • 模块搜索路径仅限于 kbe/res/scripts/ 和用户脚本目录

这意味着:你在系统 Python 上安装的 debugpy,KBEngine 无法访问。

解决方案:将 debugpy 复制到 KBEngine 的模块搜索路径中:

# 1. 在系统 Python 上安装 debugpy
pip install debugpy

# 2. 找到 debugpy 位置
python -c "import debugpy; import os; print(os.path.dirname(debugpy.__file__))"

# 3. 复制到 KBEngine 路径(Windows 示例)
xcopy /E /I "%USERPROFILE%\AppData\Local\Programs\Python\Lib\site-packages\debugpy" "D:\workspaces\kbengine\kbe\res\scripts\common\debugpy"

# Linux 示例:
# cp -r /usr/local/lib/python3.x/site-packages/debugpy /path/to/kbe/res/scripts/common/

验证安装(在 Telnet Python 模式):

>>> import debugpy
>>> print(debugpy.__file__)

步骤:

  1. 安装 debugpy(确保与 KBEngine 使用的 Python 版本一致):
# KBEngine 通常使用 Python 3.x
python3 -m pip install debugpy

# 然后复制到 KBEngine 路径(见上方警告框)
  1. 在服务器代码中启动 debugpy:
# file: base/scripts/Avatar.py
import KBEngine
import sys
import os

# 添加 debugpy 路径(如果安装在非标准位置)
# sys.path.append('/path/to/debugpy')

class Avatar(KBEngine.Entity):
    def __init__(self):
        KBEngine.Entity.__init__(self)

        # 只在调试模式下启动 debugpy(避免生产环境启用)
        if KBEngine.getScript().config.get('debug', {}).get('enabled', False):
            import debugpy
            # 监听所有接口,端口 5678
            debugpy.listen(('0.0.0.0', 5678))
            KBEngine.INFO_MSG(f'debugpy is listening on port 5678, PID={os.getpid()}')

    def takeDamage(self, damage):
        if self.hp > damage:
            self.hp -= damage
        else:
            self.hp = 0
            self.onDeath()
  1. 配置 VSCode launch.json:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach (KBE)",
            "type": "debugpy",
            "request": "attach",
            "connect": {
                "host": "your-server-ip",
                "port": 5678
            },
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/kbe/res",
                    "remoteRoot": "/path/to/server/kbe/res"
                }
            ],
            "justMyCode": false
        }
    ]
}
  1. 设置端口转发(如果服务器在远程):
# 方法1: SSH 端口转发
ssh -L 5678:localhost:5678 user@server

# 方法2: 在 VSCode 中配置 Remote-SSH 扩展
  1. 在 VSCode 中设置断点并启动调试:

  2. 打开 Python 文件

  3. 在行号左侧点击设置断点(红点)

  4. 按 F5 或点击 "Run and Debug"

  5. 选择 "Python: Remote Attach (KBE)"

  6. 当代码执行到断点时会自动暂停

VSCode 调试界面:

┌─────────────────────────────────────────┐
│ variable          │ value              │
├─────────────────────────────────────────┤
│ ▼ self            │ Avatar(1001)        │
│   ▼ hp            │ 500                │
│   ▼ maxHp         │ 500                │
│   ▼ position      │ (100, 200, 300)    │
│ ▼ damage          │ 100                │
└─────────────────────────────────────────┘

    Avatar.py:24    ✗Paused ▶ Step↓
    ─────────────────────────────────────
    23  def takeDamage(self, damage):
    24  ->   if self.hp > damage:
    25          self.hp -= damage
    26      else:
  1. 配置开关控制(避免生产环境启用):

在 kbengine.xml 或专门的配置中添加调试开关:

# 检查环境变量或配置
import os
DEBUG_MODE = os.environ.get('KBE_DEBUG', '0') == '1'

if DEBUG_MODE:
    import debugpy
    debugpy.listen(('0.0.0.0', 5678))

启动服务器时设置环境变量:

# Linux
export KBE_DEBUG=1
./start.sh

# Windows
set KBE_DEBUG=1
start.bat
调试方法对比
方法优点缺点适用场景
Telnet Python无需修改代码,随时可用无断点功能,只能查状态快速查看运行时状态
pdb内置,无需额外安装需修改代码,界面简陋临时调试,无 GUI 环境
debugpy + VSCode完整 GUI,功能强大需安装配置,有性能开销复杂问题调试,开发阶段
调试技巧

技巧 1:条件断点

# 只在特定条件下进入调试
def takeDamage(self, damage):
    if damage > 1000:  # 只调试大伤害
        import pdb; pdb.set_trace()
    # ...

技巧 2:使用 ipdb(增强版 pdb)

pip install ipdb
import ipdb
ipdb.set_trace()  # 替代 pdb.set_trace()

技巧 3:日志 + Telnet 组合

# 打印关键变量
def takeDamage(self, damage):
    KBEngine.INFO_MSG(f'takeDamage: self={self.id}, hp={self.hp}, damage={damage}')
    # 然后在 Telnet 中查看日志和状态
注意事项

⚠️ 生产环境安全:

  • 调试端口不应暴露到公网
  • debugpy 允许执行任意代码,务必限制访问
  • 使用完后立即关闭调试模式

⚠️ 性能影响:

  • debugpy 会显著降低执行速度
  • pdb 断点会阻塞整个线程
  • 生产环境应避免使用这些调试工具

常见问题

Q: Telnet 中 import debugpy 报错 ModuleNotFoundError?

这是最常见的问题,原因:KBEngine 使用嵌入式 Python,模块搜索路径与系统 Python 隔离。

解决步骤:

# 1. 确认系统 Python 已安装 debugpy
python -c "import debugpy; print(debugpy.__file__)"

# 2. 找到 debugpy 安装位置(输出类似:C:\Python311\Lib\site-packages\debugpy)

# 3. 复制到 KBEngine 模块搜索路径
# Windows:
xcopy /E /I "C:\Python311\Lib\site-packages\debugpy" "D:\workspaces\kbengine\kbe\res\scripts\common\debugpy"

# Linux:
cp -r /usr/local/lib/python3.x/site-packages/debugpy /path/to/kbe/res/scripts/common/

验证(在 Telnet Python 模式):

>>> import sys
>>> print('\n'.join(sys.path))  # 查看 KBEngine 的模块搜索路径
>>> import debugpy
>>> print(debugpy.__file__)  # 应显示 kbe/res/... 路径

Q: 连接后立即断开?

  • 检查密码是否正确
  • 检查防火墙是否允许对应端口

Q: Python 模式无响应?

  • 可能是组件正在处理其他请求
  • 尝试重新连接

Q: 无法执行某些命令?

  • 某些命令只能在特定组件执行(如 writeToDB 只能在 BaseApp)
  • 检查当前连接的是哪个组件

Q: debugpy 连接成功但断点不触发?

  • 检查 pathMappings 配置是否正确
  • 确认代码文件路径与远程路径一致
  • 尝试设置 justMyCode: false

20.6 Message Logger:集中日志

20.6.1 KBEngine Logger

// kbe/src/server/tools/logger/logger.h(简化)

struct LOG_ITEM {
    int uid;
    int logtype;
    int componentType;
    int componentID;
    double t;
    KBEngine::KBETime kbetime;
    std::string logstream;
    bool persistent;           // 是否持久化
};

class Logger {
public:
    void writeLog(Network::Channel* pChannel, KBEngine::MemoryStream& s);
    void registerLogWatcher(Network::Channel* pChannel, KBEngine::MemoryStream& s);
    void sendInitLogs(LogWatcher& logWatcher);
    size_t bufferedLogsSize();

private:
    std::deque<LOG_ITEM*> buffered_logs_;  // 缓冲最近一批日志指针
};

所有组件的日志通过 LoggerInterface::writeLog 汇聚到 Logger 组件。writeLog() 会把日志格式化为统一字符串,先回调入口脚本的 onLogWrote(),再按需持久化,并广播给已注册的 LogWatcher。

20.6.2 BigWorld message_logger(更完整)

BigWorld 的 message_logger 是一个完整的日志聚合系统:

// BigWorld: server/tools/message_logger/logger.hpp

class Logger : public WatcherRequestHandler, public TimerHandler {
public:
    bool init(int argc, char* argv[]);
    bool handleNextMessage();
    bool shouldLogPriority(MessageLogger::NetworkMessagePriority priority);
protected:
    virtual void processExtensionMessage(int messageID,
            char* data, int dataLen, WatcherEndpoint& watcherEndpoint);
};

// 日志条目
struct LogEntry {
    LogEntry(const struct timeval& tv,
             MessageLogger::UserComponentID componentID, ...);
    const struct timeval& time() const;
    int categoryID() const;
    int messagePriority() const;
    int stringOffset() const;
};

双格式存储:binary_file_handler(二进制,高效写入)和 text_file_handler(文本,可读性好)。

按组件维度隔离:user_components.hpp 管理每个进程的注册信息(名称、PID、地址),日志按组件维度隔离存储。

Python 可查询:py_bwlog / py_query / py_query_result 提供 Python 接口,支持按组件、级别、时间、主机等维度查询历史日志。

结构化日志:categories.hpp / log_string_interpolator.hpp 提供日志分类和字符串插值,比 KBEngine 的纯文本日志更结构化。


20.7 ServerInfo:硬件与系统信息

// BigWorld: lib/server/server_info.hpp

class ServerInfo {
public:
    const BW::string& serverName() const { return serverName_; }
    const BW::string& cpuInfo() const { return cpuInfo_; }
    const CpuSpeeds& cpuSpeeds() const { return cpuSpeeds_; }
    const BW::string& memInfo() const { return memInfo_; }
    uint64 memTotal() const { return memTotal_; }
    uint64 memUsed() const { return memUsed_; }
    void updateMem();    // 运行时刷新内存使用

private:
    BW::string serverName_;
    BW::string cpuInfo_;
    BW::vector<float> cpuSpeeds_;
    BW::string memInfo_;
    uint64 memTotal_;
    uint64 memUsed_;
};

运维时需要知道"这个 CellApp 跑在什么机器上、CPU 什么型号、内存用了多少"。ServerInfo 提供了这些硬件层面的上下文信息,与 Watcher 中的逻辑指标互补。


20.8 分布式链路追踪:两套项目共同的缺失

20.8.1 问题

一次 EntityCall 的完整链路可能跨越多个进程:

BaseApp (脚本调用 EntityCall)
  → CellApp (处理 RPC)
    → 另一个 CellApp (ghost 转发)
      → BaseApp (回调脚本)

但整个链路没有 traceID。运维只能靠日志时间戳 + 经验手动拼链路。

20.8.2 OpenTelemetry Tracing 能做什么

  1. 给每次 EntityCall 分配 traceID / spanID
  2. 跨进程传递 trace context(通过 Bundle 头部注入 W3C TraceContext)
  3. 在 Jaeger / Zipkin 中可视化完整调用链

20.8.3 接入难点

  1. 高频调用:每秒百万级 EntityCall → sampling 策略必须精心设计(头部一致性采样 / 尾部自适应采样)
  2. 同步 tick 内的串行调用:span 嵌套关系比微服务更复杂——一个 tick 内可能有数百个 EntityCall
  3. 改动较深:需要在 MemoryStream / Bundle 层注入 trace context,侵入消息协议

20.9 OTel Metrics 接入的可能性

更准确地说,Watcher 已经提供了可拉取的指标树,但它不是 Prometheus / OTel 原生协议,所以通常需要一个自定义 exporter 或 collector 适配层去查询 watcher tree,再转换成 Metrics。关键指标清单:

指标来源告警阈值
实体数量(按类型/按组件)Watcher突增/突降
tick 耗时(P50/P99)ProfileVal> 100ms → tick 堆积
AOI 更新耗时ProfileVal持续增长
网络带宽(收/发 bytes/s)NetworkStats / SendingStats接近带宽上限
DB 写入延迟ProfileVal> 500ms → 数据库瓶颈
Channel 数量Watcher突降 → 大量断线
对象池使用率ObjectPool> 80% → 内存压力

20.10 常见调试场景

场景 1:定位属性同步延迟

Watcher → 查 tickProfile
  → Profile → 查 Witness::update 耗时
    → NetworkStats → 查带宽是否饱和

场景 2:定位 RPC 链路瓶颈

Profiler → 查 EntityCall 处理耗时
  → NetworkStats → 查消息队列深度
    → EntityTypeProfiler → 查哪个实体类型最耗时

场景 3:定位 AOI 性能问题

Profile → 查 RangeTrigger 耗时
  → Watcher → 查实体数量是否异常增长
    → EntityTypeProfiler → 查实体类型分布

场景 4:定位数据库写库延迟

Profile → 查 writeToDB 耗时
  → Watcher → 查 ThreadPool 队列长度
    → ServerInfo → 查内存是否不足

20.11 源码入口表

KBEngine

模块文件路径关键类/函数
Watcher 基类kbe/src/lib/helper/watcher.hWatcherObject, WatcherValue<T>, WatcherFunction<R>, WatcherMethod<R,OBJ>
Watcher 容器kbe/src/lib/helper/watcher.hWatchers, WatcherPaths
Profile 核心kbe/src/lib/helper/profile.hProfileVal, ProfileGroup, ScopedProfile
Profile 宏kbe/src/lib/helper/profile.hAUTO_SCOPED_PROFILE, START_PROFILE, STOP_PROFILE
网络统计kbe/src/lib/network/network_stats.hNetworkStats, Stats
Telnet 服务kbe/src/lib/server/telnet_server.hTelnetServer
Telnet 处理kbe/src/lib/server/telnet_handler.hTelnetHandler, TELNET_STATE
Loggerkbe/src/server/tools/logger/logger.hLogger, LOG_ITEM
对象池kbe/src/lib/common/objectpool.hObjectPool<T>

BigWorld

模块文件路径关键类/函数
Watcher 网络lib/network/watcher_nub.hppWatcherNub, WatcherMsg 枚举
分布式转发lib/server/watcher_forwarding.hppForwardingWatcher, ExposeHints
转发收集器lib/server/watcher_forwarding_collector.hppForwardingCollector
核心 Profilerlib/cstdmf/profiler.hppProfiler, ProfileMode, HitchDetector
实体 Profilerlib/server/entity_profiler.hppEntityProfiler, AutoScopedHelper
类型 Profilerlib/server/entity_type_profiler.hppEntityTypeProfiler
Cell Profilerserver/cellapp/cell_profiler.hppCellProfiler
CellApp Profileserver/cellapp/profile.hppCellProfileGroup, 各种 ProfileVal
网络统计lib/network/sending_stats.hppSendingStats
服务器信息lib/server/server_info.hppServerInfo
Message Loggerserver/tools/message_logger/logger.hppLogger, Component
日志条目server/tools/message_logger/log_entry.hppLogEntry
二进制处理server/tools/message_logger/binary_file_handler.hppBinaryFileHandler
文本处理server/tools/message_logger/text_file_handler.hppTextFileHandler
组件管理server/tools/message_logger/user_components.hppUserComponents
Python 查询server/tools/message_logger/py_bwlog.hppPyBWLog

20.12 源码漫游路径

路径 A:从 Watcher 注册到远程查询

1. kbe/src/lib/helper/watcher.h
   → WatcherValue<T> 构造,绑定变量引用

2. kbe/src/lib/helper/watcher.h
   → WatcherPaths::addWatcher() 注册到路径树

3. kbe/src/server/cellapp/cellapp.cpp
   → initializeWatcher() 中注册组件级 watcher

4. guiconsole / telnet
   → 发起查询 → addToStream() 序列化 → 网络 → 客户端显示

路径 B:从 Profiler 宏到负载均衡决策

1. 函数入口
   → AUTO_SCOPED_PROFILE("onTick") → ScopedProfile 构造 → ProfileVal::start()

2. 函数出口
   → ScopedProfile 析构 → ProfileVal::stop()

3. tick 结束
   → ProfileGroup::runningTime() 汇总

4. BigWorld: EntityProfiler::tick()
   → 计算平滑负载 → 上报 EntityTypeProfiler → CellProfiler → doBalance()

路径 C:从日志产生到集中存储

1. 组件中 DEBUG_MSG / INFO_MSG 宏
   → 格式化日志字符串

2. kbe/src/server/tools/logger/logger.h
   → writeLog() 接收 → buffered_logs_ 缓冲

3. BigWorld: message_logger/
   → Logger::handleNextMessage() → LogEntry 创建 → UserComponents 路由
   → binary_file_handler / text_file_handler 双写

20.13 小结

子系统KBEngineBigWorld业界对比
Watcher(指标)单机路径树,guiconsole/telnet 查询+ WatcherNub 网络层 + ForwardingWatcher 分布式转发Prometheus 有 TSDB + 告警 + PromQL
Profiler(剖析)ProfileVal 栈式嵌套,函数级+ EntityProfiler / EntityTypeProfiler / CellProfiler 三级体系pprof 提供系统级火焰图,互补
NetworkStats消息级收发统计+ bps/pps/mps + 重传 + piggybackPrometheus node_exporter 网络指标
Telnet4 状态机,Python 解释器类似kubectl exec 远程 shell
Logger集中收集 + LogWatcher 过滤+ 双格式存储 + 组件隔离 + Python 查询ELK 有全文索引 + 聚合
Tracing缺失缺失OpenTelemetry + Jaeger

核心差异:BigWorld 的 ForwardingWatcher 实现了从 Mgr 组件一站式查询所有下属组件的能力,这在 KBEngine 中没有对应实现。BigWorld 的三级 Profiler 体系(Entity → EntityType → Cell)也比 KBEngine 的单一 ProfileVal 层次更丰富。

共同缺失:两套项目都没有分布式链路追踪,无法自动关联一次 EntityCall 跨越多个进程的完整链路。这是接入现代可观测性栈时优先级最高的改进方向。

Next
Ch21 热更新、容错与运维工具