侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P3-03 · SONiC 控制面链路:APPL_DB → ASIC_DB → syncd

PacketForge
2026-08-18 / 0 评论 / 1 阅读 /
对应路线图:Phase 3 (Day 92-119) | DPDK 24.11.6 LTS | 内核对照 v6.6
上一篇:P3-02 OVS-DPDK 调优 | 下一篇:P3-04 SAI 头文件全景

一句话定位

SONiC 把"交换机配置"拆成一条Redis 接力链:业务进程写 APPL_DB → orchagent 消费、翻译成 SAI 对象写 ASIC_DB → syncd 消费、调厂商 SDK 驱动 ASIC。控制面与数据面之间唯一的耦合点是 Redis 的 key-value 协议——谁写谁读、写成什么格式,全部由这套协议定义。

全景:Redis 六库与三进程

SONiC 控制面三进程与 Redis 六库

数据库名是 sairedis 的公开常量(lib/sairediscommon.h:104-106):ASIC_DB/STATE_DB/COUNTERS_DB/FLEX_COUNTER_DB,加上 CONFIG_DB、APPL_DB 就是六库。职责一句话版:CONFIG_DB 存静态配置;APPL_DB 是业务进程(BGP/LLDP/Port)的目标意图;ASIC_DB 是翻译后的 SAI 对象;STATE_DB 是 ASIC 实际状态;COUNTERS_DB/FLEX_COUNTER_DB 是统计。本篇只跟 APPL_DB → ASIC_DB 这条主线。

第一棒:orchagent 从 APPL_DB 消费任务

orchagent 是 swss 的核心,每个业务一个 Orch 子类(PortOrch、RouteOrch……),任务处理入口 Orch::doTask(orchagent/orch.cpp:919-955,节选):

void Orch::doTask()
{
    // limit the number of tasks moved from RetryMap to SyncMap in one iteration 
    // to avoid starvation of new tasks in SyncMap
    auto threshold = gBatchSize == 0 ? 30000 : gBatchSize;
    size_t count = 0;
    for (auto &it : m_consumerMap)
    {
        try
        {
            count += retryToSync(it.first, threshold - count);
            it.second->drain();
        }
        catch (const std::invalid_argument& e)
        {
            SWSS_LOG_ERROR("Exception caught: type=invalid_argument, table=%s, orch=%s, error=%s",
                           it.first.c_str(), typeid(*this).name(), e.what());
        }
        // ...(其余 catch 分支省略)
    }
}

m_consumerMap 把表名映射到 Consumer,每个 Orch 通过它声明"我订阅 APPL_DB 的哪些表"。消费动作(orch.cpp:455-456):subTable->pops(entries) → addToSync(entries)。Redis 表被当成队列:pops 取出 KeyOpFieldsValuesTuple(key + SET/DEL + field:value 列表),addToSync 交给子类 doTask 处理。RouteOrch 收到一条 ROUTE_TABLE:192.168.1.0/24 的 SET,就调 SAI 接口创建路由对象。

第二棒:ClientSai 把 SAI 调用序列化成 ASIC_DB 写操作

swss 链接 sairedis 的客户端库——把标准 SAI C API 变成 Redis 写。ClientSai::create(lib/ClientSai.cpp:112-209,节选):

sai_status_t ClientSai::create(
        _In_ sai_object_type_t objectType,
        _Out_ sai_object_id_t* objectId,
        _In_ sai_object_id_t switchId,
        _In_ uint32_t attr_count,
        _In_ const sai_attribute_t *attr_list)
{
    MUTEX();
    // NOTE: in client mode, each create oid must be retrieved from server since
    // server is actually creating new oid, and it's value must be transferred
    // over communication channel
    *objectId = SAI_NULL_OBJECT_ID;
    // ...(INIT_SWITCH=false 的握手分支,省略)...
    auto status = create(
            objectType,
            sai_serialize_object_id(switchId),
            attr_count,
            attr_list);
    if (status == SAI_STATUS_SUCCESS)
    {
        // since user requested create, OID value was created remotely and it
        // was returned in m_lastCreateOids
        *objectId = m_lastCreateOids.at(0);
    }
    // ...
}

三个关键点:① OID 由对端分配——orchagent 传 SAI_NULL_OBJECT_ID,真正的 OID 是 syncd 回传的(m_lastCreateOids);② switch ID 也要序列化成字符串做 key;③ 全程互斥(MUTEX())——Redis 通信通道是复用的。写出的 key 形如 ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{...},value 是 SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID:oid:0x... 这类 field:value——这套文本协议同时是 ASIC_DB 的 schema。

第三棒:syncd 消费 ASIC_DB,调厂商 SDK

syncd 侧走 RedisRemoteSaiInterface(服务端模式)。请求-响应类消息走独立频道,notifySyncd(lib/RedisRemoteSaiInterface.cpp:2236-2266)是典型样例:

    auto key = sai_serialize(redisNotifySyncd);
    // we need to use "GET" channel to be sure that
    // all previous operations were applied, ...
    m_communicationChannel->set(key, entry, REDIS_ASIC_STATE_COMMAND_NOTIFY);
    auto status = waitForNotifySyncdResponse();

普通 SAI 调用走 ASIC_DB 的 key-value 写,notify/GET 请求走 REDIS_ASIC_STATE_COMMAND_NOTIFY 频道,响应回来在 REDIS_TABLE_GETRESPONSE 表(sairediscommon.h:100)。syncd 处理完 create 把 OID 写回响应表,orchagent 的 m_lastCreateOids 才有值——闭环。最终 syncd 持有一张真实的 sai_switch_api_t 表(references/sai-headers/inc/saiswitch.h:4261-4279),一路调进厂商 SDK(BCM/MLNX 或 vslib)。

🐧 内核对照:rtnetlink 是内核的"ASIC_DB"

ip route add 最终进 rtnl_newlink(net/core/rtnetlink.c:3744-3757):

static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
            struct netlink_ext_ack *extack)
{
    struct rtnl_newlink_tbs *tbs;
    int ret;

    tbs = kmalloc(sizeof(*tbs), GFP_KERNEL);
    if (!tbs)
        return -ENOMEM;

    ret = __rtnl_newlink(skb, nlh, tbs, extack);
    kfree(tbs);
    return ret;
}
环节内核SONiC
用户接口netlink socket(RTM_NEWLINK)Redis 写(APPL_DB)
消息格式nlmsghdr + nlattr 二进制 TLV文本 key + field:value
翻译层rtnetlink 直接改 net_deviceorchagent 翻译成 SAI 对象
执行层内核驱动(ndo_*)syncd → 厂商 SAI SDK

同一张架构图,两种实现:用户态请求 → 统一入口 → 翻译 → 驱动执行。SONiC 用 Redis 因为控制面要多进程、可热重启、可容器化——Redis 天然是进程间的共享内存 + 队列;内核用 netlink 因为要服务全系统且严格校验(extack 报错路径)。

面试 4 问

  1. 为什么 create 的 OID 不由 orchagent 分配? → 服务端才真正创建对象,OID 必须从 syncd 经通信通道回传(ClientSai.cpp:123-125 注释、190 行 m_lastCreateOids)。
  2. APPL_DB 和 ASIC_DB 的区别? → APPL_DB 是意图("我想要一条路由"),ASIC_DB 是翻译后的 SAI 对象("SAI_ROUTE_ENTRY + NEXT_HOP_ID"),中间隔着 orchagent 的翻译。
  3. syncd 和 orchagent 之间除了 key-value 还有什么通道? → 请求-响应类走独立频道,如 REDIS_ASIC_STATE_COMMAND_NOTIFY(RedisRemoteSaiInterface.cpp:2260),避免与 op 队列竞争(2248-2255 注释)。
  4. doTask 的 threshold 为什么存在? → 限制单轮从 RetryMap 搬进 SyncMap 的任务数(默认 30000),防止重试任务饿死新任务(orch.cpp:921-923)。

🔧 动手实验

# 观察一条路由的完整旅程
redis-cli -n 0 HSET "ROUTE_TABLE:10.0.0.0/24" "nexthop" "10.0.0.1"   # APPL_DB
redis-cli -n 1 KEYS "ASIC_STATE*"          # ASIC_DB 出现序列化后的 SAI 路由
redis-cli -n 6 KEYS "*"                    # STATE_DB 出现 ASIC 实际状态
docker exec -it syncd redis-cli -n 1 MONITOR   # 看谁在消费

产出 perf-data/P3/03-sonic-path.md:APPL_DB 写入 → ASIC_DB 出现 → STATE_DB 落盘的耗时三段计时表。


下一篇:P3-04 · SAI 头文件全景与 15 个 MVP API

0

评论 (0)

取消