
为什么选择 Redis 当通信总线传统网络设备的内部模块通信方式五花八门——共享内存、IPC 管道、私有消息队列。每种都能用但都有局限性语言绑定、调试困难、扩展性差。SONiC 选了一条不太常规的路把 Redis 数据库当作所有模块之间的通信总线。这个选择初看奇怪细想很聪明语言无关。SONiC 的代码库里有 Corchagent、syncd、PythonCLI、SNMP 代理、Gotelemetry。如果用共享内存或 C IPCPython 组件就很难参与。Redis 提供了各语言都有客户端库的通用协议。可观察性极强。调试时你可以直接用redis-cli查看系统里所有状态——路由表、端口配置、ASIC 编程指令一目了然。对比传统设备里模块间通信全是二进制 IPC 消息Debug 时只能抓包猜含义。天然支持发布/订阅。模块 A 写入一条数据模块 B、C、D 都能收到通知。写入者完全不需要知道有谁在消费——完美的松耦合。高性能且足够。Redis 是纯内存数据库单实例每秒可处理百万级操作。而交换机控制面的操作频率通常只有每秒几十到几百次——余量巨大。访问方式所有容器通过 UNIX domain socket 连接 Redis——不走网络栈延迟在微秒级。五大数据库全景SONiC 的 Redis 实例里维护了五个逻辑数据库每个有自己的编号和明确职责。你可以把它们想象成五本不同用途的账本CONFIG_DB用户的意志CONFIG_DB 是你跟 SONiC 交互的起点。你在 CLI 里敲的每一条config命令、你在 config_db.json 里写的每一行配置最终都存在这里。谁写入它你通过 CLIconfig interface speed Ethernet0 100000自动化工具通过 config_db.json reload管理接口gNMI、RESTCONFZero Touch Provisioning设备首次上线时的自动配置谁读取它各容器中的 *mgrd 进程intfmgrd、vlanmgrd负责读取配置并在 Linux 内核层面执行数据长什么样CONFIG_DB 使用TABLE|KEY的命名格式值是 hash 类型的 field-value 对PORT|Ethernet0 admin_status: up speed: 100000 mtu: 9100 lanes: 65,66,67,68 VLAN|Vlan100 vlanid: 100 VLAN_MEMBER|Vlan100|Ethernet4 tagging_mode: untagged ACL_RULE|DATAACL|RULE_1 SRC_IP: 10.0.0.0/8 PACKET_ACTION: DROP特殊之处CONFIG_DB 是唯一会被持久化到磁盘的数据库。设备重启后SONiC 从/etc/sonic/config_db.json恢复它。其他数据库的内容在运行时动态生成不需要持久化。APPL_DB应用层的需求清单APPL_DB 是所有应用向 SONiC 核心系统提交需求的入口。BGP 学到一条路由写到这里。ARP 解析了一个邻居也写到这里。谁写入它进程写入什么fpmsyncdBGP 容器路由条目ROUTE_TABLEportsyncdSwss 容器端口属性——speed、lanes、mtuPORT_TABLEintfsyncdSwss 容器接口 IP 地址变更neighsyncdSwss 容器ARP/NDP 发现的邻居 MAC 和 IPNEIGH_TABLEteamsyncdTeamd 容器LAG 端口成员状态lldp_syncdLLDP 容器邻居设备信息LLDP_ENTRY_TABLE谁消费它orchagent— 它是 APPL_DB 的核心消费者读取所有上述状态进行编排数据长什么样ROUTE_TABLE:10.0.0.0/24 nexthop: 192.168.1.1 ifname: Ethernet0 NEIGH_TABLE:Ethernet0:192.168.1.1 neigh: 00:11:22:33:44:55 family: IPv4 PORT_TABLE:Ethernet0 speed: 100000 mtu: 9100 lanes: 65,66,67,68 oper_status: up理解关键APPL_DB 说的是我想让系统做什么而不是硬件目前是什么状态。orchagent 收到这些需求后会负责把它们变成现实。STATE_DB解决谁先谁后的问题在复杂系统里模块之间经常有依赖关系。举个例子你配置了一个 VLAN指定 Ethernet4 为成员端口。但问题是——Ethernet4 此刻可能还没完成初始化。如果 vlanmgrd 不等 Ethernet4 就直接配置 VLAN结果就是失败。STATE_DB 就是为了解决这种先有鸡还是先有蛋的问题。它追踪系统中每个实体的实际就绪状态让依赖方知道什么时候可以安全地执行下一步。一个典型的工作流程时刻 T1: 用户配置 VLAN100成员 Ethernet4 → CONFIG_DB 写入 VLAN_MEMBER|Vlan100|Ethernet4 → vlanmgrd 收到通知检查 STATE_DB 时刻 T2: vlanmgrd 查看 STATE_DB发现 Ethernet4 还没 ready → 先不动等着 时刻 T3: portsyncd 完成 Ethernet4 初始化 → 写入 STATE_DB: PORT_TABLE|Ethernet4 → {state: ok} 时刻 T4: vlanmgrd 收到 STATE_DB 变更通知 → 确认 Ethernet4 ready → 执行 VLAN 配置 → 成功这种模式让 SONiC 不需要关心模块启动顺序——只要最终状态一致结果就是正确的。ASIC_DB给芯片的施工图纸ASIC_DB 是软件世界和硬件世界的分界线。写入这里的数据已经不是人类友好的格式了——它是以 SAI 对象的方式组织的方便 syncd 直接翻译成 SAI API 调用。谁写入orchagent经过编排处理后的最终产出谁消费syncd读取后调用 SAI API 编程硬件数据长什么样ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{ dest:10.0.0.0/24, switch_id:oid:0x21000000000000, vr:oid:0x300000000003c } SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID: oid:0x4000000000123 ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP:oid:0x4000000000123 SAI_NEXT_HOP_ATTR_IP: 192.168.1.1 SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID: oid:0x6000000000089 SAI_NEXT_HOP_ATTR_TYPE: SAI_NEXT_HOP_TYPE_IP看到那些oid:0x...了吗那些是 SAI 对象 ID。orchagent 的核心工作之一就是把 APPL_DB 里人类可读的数据IP 地址、接口名转换成这种 SAI 对象格式的数据。类比如果 APPL_DB 是我想从北京到上海那 ASIC_DB 就是右转进入京沪高速入口匝道行驶 1200 公里在沪闵路出口下匝道——是具体的、可执行的施工指令。COUNTERS_DB实时仪表盘COUNTERS_DB 存储所有端口的统计信息——收了多少包、发了多少包、有多少错误、丢了多少。谁写入syncd 进程周期性从 ASIC 硬件读取计数器谁消费CLIshow interfaces counters、SNMP、gNMI Telemetry这个数据库不需要持久化——统计计数器是实时的重启后从零开始。完整数据流一条路由的 12 步旅程现在把所有数据库串起来看一条路由从 BGP 学习到硬件转发的完整路径详细步骤步骤发生了什么在哪里①BGP peer 发来 UPDATE 消息bgpd (TCP:179)②bgpd 经过路由策略过滤后传给 zebraBGP 容器内③zebra 选择最优路径计算出 FIBBGP 容器内④zebra 通过 netlink 更新 Linux 内核路由表Linux 内核⑤zebra 通过 FPM 接口把路由推给 fpmsyncdBGP 容器内⑥fpmsyncd 写入 APPL_DB 的 ROUTE_TABLERedis⑦orchagent 收到 pub/sub 通知读取新路由Swss 容器⑧orchagent 查找 nexthop 对应的邻居 MAC从 NEIGH_TABLESwss 容器⑨orchagent 构造 SAI 路由对象 nexthop 对象Swss 容器⑩orchagent 写入 ASIC_DBRedis⑪syncd 收到通知调用 SAI API如sai_route_api-create_route_entry()Syncd 容器⑫SAI 实现调用厂商 SDK → 硬件 ASIC 转发表更新数据面生效硬件整个路径BGP容器 → Redis(APPL_DB) → Swss容器 → Redis(ASIC_DB) → Syncd容器 → SAI → ASIC从外部路由器发来一条路由到它在硬件上生效这整个过程通常在几百毫秒到几秒内完成。Pub/Sub 通知机制事件驱动的秘密你可能注意到了步骤 ⑦ 和 ⑪ 里orchagent 和 syncd 都是收到通知后才行动的——不是在循环里不停轮询。这就是 Redis pub/sub 的威力。工作原理是这样的生产者往 Redis Hash 里写入一条数据同时往一个 notification channel 发布通知嘿某个表有新数据了消费者通过select()多路复用监听多个 channel收到通知后从 Hash 里读取最新数据执行处理在代码层面SONiC 封装了一套 C 类来简化这个过程角色类名做什么生产者ProducerStateTable写数据到 Redis Hash 发送通知消费者ConsumerStateTable订阅通知 从 Hash 读取数据多路复用Select同时监听多个 table 的变更这种设计比纯 pub/sub 消息可靠得多——就算消费者暂时离线数据还在 Hash 里上线后可以补读。不怕丢消息。动手验证如果你有 SONiC 设备或虚拟机可以直接看到这些数据库的内容# 查看 APPL_DB 中的路由 sonic-db-cli APPL_DB keys ROUTE_TABLE* sonic-db-cli APPL_DB hgetall ROUTE_TABLE:10.0.0.0/24 # 查看 CONFIG_DB 中的端口配置 sonic-db-cli CONFIG_DB hgetall PORT|Ethernet0 # 查看 ASIC_DB 中的路由对象 sonic-db-cli ASIC_DB keys *ROUTE* # 查看 COUNTERS_DB 中的端口统计 sonic-db-cli COUNTERS_DB hgetall COUNTERS:oid:0x1000000000001也可以进入 database 容器直接用 redis-clidocker exec -it database redis-cli -n 0 # APPL_DB docker exec -it database redis-cli -n 4 # CONFIG_DB docker exec -it database redis-cli -n 1 # ASIC_DB为什么分成五个数据库一句话回答关注点分离 生命周期不同。数据库本质持久化重启后怎么办CONFIG_DB用户意图✅ 保存到磁盘从 config_db.json 恢复APPL_DB应用产生的状态❌各 *syncd 进程重新收集STATE_DB模块间依赖状态❌各模块重新上报ASIC_DB硬件编程指令❌orchagent 重新编排COUNTERS_DB统计信息❌从零计数另一个好处消费者只需要盯自己关心的数据库。orchagent 只订阅 APPL_DB 的变更syncd 只订阅 ASIC_DB 的变更各管各的互不干扰。本篇小结要点内容为什么用 Redis语言无关、可观察、pub/sub、高性能数据库数量5 个各有明确分工用户入口CONFIG_DB — 你的配置存这里应用入口APPL_DB — 所有 *syncd 进程写入硬件出口ASIC_DB — orchagent 产出syncd 执行依赖解决STATE_DB — 谁先 ready的追踪器事件驱动ProducerStateTable / ConsumerStateTable下一篇预告第5篇Swss 与 orchagent——SONiC 的大脑— orchagent 内部的 Orch 类怎么分工RouteOrch 和 NeighOrch 如何协作以及当依赖未满足时 orchagent 怎么处理。参考资料SONiC Architecture Wiki: Architecture · sonic-net/SONiC Wiki · GitHub