SONiC使用Redis提供的两种机制:
- publish/subscribe
- keyspace
封装出多种通信模型,建立以Redis数据库为中心的的消息传递机制。同时也通过调用Linux工具命令对系统进行配置,并以发送和监听netlink消息的方式与内核通信。SONiC使用了多个Redis数据库,用来存放不同的配置、状态和控制表项等信息。
数据库说明如下:
以数据库为中心的模型
模型1:SubscriberStateTable
使用keyspace机制,向订阅者传递key-value消息。该机制不需要执行PUBLISH通知,对于每个修改数据库的操作,都会产生keyspace的事件消息,订阅者通过监听指定KEY,来获取所有对该KEY修改事件的通知。在SONiC中,该通信模型常用于对CONFIG_DB和STATE_DB的监听处理。
对CONFIG_DB的修改一般用于对系统进行配置操作,如使用命令行来配置系统功能,SONiC在sonic-py-swsssdk组件中封装了对CONFIG_DB的操作,根据传递data是否为空执行hmset或delete操作。
这里以监听CONFIG_DB配置VLAN为例说明。
VlanMgr在初始化时监听CFG_VLAN_TABLE_NAME和CFG_VLAN_MEMBER_TABLE_NAME,当通过config命令添加vlan 100的操作,在CONFIG_DB中生成名为”VLAN|Vlan100″的KEY,此时会产生keyspace的事件消息,触发VlanMgr::doTask(Consumer &consumer)来处理。这里的Consumer即是通过orch类封装过的SubscriberStateTable。
模型2:NotificationProducer & NotificationConsumer
使用Redis的publish/subscribe模型直接封装实现,通过消息队列来传递信息,内容可以灵活定义。在SONiC中,该通信模型主要用于orchagent和SYNCD之间的事件通知,例如FDB事件、端口状态变化等等。
以FDB事件为例,SYNCD收到来自底层驱动的FDB事件,调用对数据库的hset或del操作更新ASIC_DB中的FDB表项,同时作为Notification生产者发送名为“fdb_event”的通知消息。
图1 以数据库为中心通信模型
Notification的消费者流程在fdborch中实现,通知消息触发:
FdbOrch::doTas (NotificationConsumer&consumer)
来进行后续处理,更新orchagent中的FDB信息。
模型3:ProducerStateTable &ConsumerStateTable
使用Redis的publish/subscribe模型封装,通过publish通知KEY修改事件,利用KeySet机制来传递信息。该通信模型通过集合(set)来传递key-value信息,不指定操作动作,当传递的value为空时,对应操作为del,当传递的value非空,对应操作为set。
在SONiC中,该通信模型用于围绕APPL_DB的消息传递,生产者一般为cfgmgr或应用组件的syncd进程,消费者则是orchagent。
这种通信模型,在publish通知KEY修改事件前允许对key-value进行多次操作,操作过程不保证执行顺序。这样做的好处是不必在每次设置key-value时都触发事件通知,可以提升处理效率,但对orchagent处理流程有一定要求。orchagent作为消费者,在Consumer::execute过程中取出所有待处理的key-value信息,加入待处理的m_toSync映射表,当m_toSync映射表还有未处理完的信息时,会将新任务与旧任务合并,然后交由每个orch实例处理。
Orchagent调度处理采用epoll事件通知模型,事件触发即会产生调度;在调度处理中,可能出现因资源依赖等因素导致任务处理无法完成,此时可以选择将任务保留在m_toSync中等待下一次调度处理。在大规模控制表项和较复杂逻辑关系的场景下,这种调度机制可能出现因资源限制、依赖条件不满足等因素导致的频繁或无效调度,Asterfusion通过优化处理顺序、改进批量操作以及在STATE_DB中设置状态标志等改进方式,提高了组件运行效率并增强了可靠性。
模型4:ProducerTable& ConsumerTable
使用Redis的publish/subscribe模型封装,通过publish通知KEY修改事件,利用KeyValueOpQueues机制来传递信息。该通信模型通过有序链表(list)来传递key-value-op三元消息,在SONiC中,该模型用于围绕ASIC_DB和FLEX_COUNTER_DB的消息传递。与模型3相比,该模型保证了操作的严格执行顺序,在syncd执行SAI API调用时保证了对底层ASIC的操作时序。
与内核的通信方式
SONiC中应用模块与内核通信主要用于处理网络接口事件和路由等,包括向内核发送消息和获取内核消息,一般通过netlink机制或调用系统工具来完成。SONiC中使用的teamd、FRR等开源组件与内核存在依赖关系,采用这种通信机制在减少模块耦合性的同时,可以尽量减少对原有开源组件的修改。
向内核发送消息一般有两种方式:一种是通过调用Linux工具命令,如调用ip命令配置网络接口IP地址和设置VRF,又如调用bridge命令配置VLAN;另一种方式是直接发送netlink消息,例如通过NETLINK_ROUTE生成内核路由表。调用工具命令方式比较简单,用封装的swss::exec方法最终通过popen执行拼装好的command指令。对netlink消息操作则是通过以libnl库为基础封装的NetLink类来完成,同时SWSS也定义了一套NetDispatcher机制来实现netlink消息监听和分发处理。
这里以neighsyncd为例进行说明。
neighsyncd通过NetDispatcher提供的方法注册RTM_NEWNEIGH和RTM_DELNEIGH两类netlink消息类型的回调函数,当触发netlink消息事件时,调用NetDispatcher的onNetlinkMessage方法来处理。这里通过netlink消息类型来查找之前注册的回调函数,并最终调用nl_msg_parse来执行回调函数。neighsyncd的回调函数根据netlink携带的信息组织生成邻居表项,并向APP_NEIGH_TABLE写入相应键值。在生成邻居表项过程中,使用了SWSS实现的一种名为linkcache的机制,该机制缓存曾经使用的NETLINK_ROUTE消息,通过网络接口索引来查询网络接口名称。
下面以teamd流程示例来说明聚合组配置和聚合过程的通信流程。
teamd聚合组配置
图2 聚合组配置流程
(1) 通过CLI创建名称为PortChannel0001的聚合组,并加入聚合组成员口Ethernet0和Ethernet4,在CONFIG_DB中生成配置表项。
(2) teamdmgrd进程监听相应键值变化,调用doLagTask和doLagMemberTask方法处理。
(3) doLagTask方法解析参数并生成所需的配置文件conf,通过调用teamd命令创建并配置聚合组,并调用ip命令设置聚合组接口MAC地址和管理状态;doLagMemberTask方法中先判断聚合组及待加入聚合组成员接口状态是否满足要求,如果满足则调用teamdctl和ip命令来配置聚合成员接口,这里会将聚合成员口设置为down,否则挂起当前任务后续再处理。
(4) teamdmgrd作为生产者将聚合组和成员的配置信息写入APPL_DB。
(5) portsorch作为消费者订阅APP_LAG_TABLEAPP_LAG_MEMBER_TABLE进行处理。
(6) portsorch调用sairedis的API,检查参数类型合法性并将LAG配置信息写入ASIC_DB。
(7) SYNCD订阅ASIC_DB中的LAG相关表项并处理。
(8) SYNCD调用ASIC SDK对SAI API的实现,并通过ASICdriver下发到底层芯片。
teamd聚合过程
(1) teamsyncd初始化阶段注册监听RTM_NEWLINK和RTM_DELLINK类型的netlink消息,同时也会注册teamd的操作handler,用于处理teamd聚合成员口状态变化以及teamd参数变化触发的事件。
图3 teamd聚合流程
(2) teamsyncd处理两类消息。一类是netlink消息,当触发NEWLINK或DELLINK时对应操作STATE_LAG_TABLE设置聚合组状态;另一类是teamd状态变化消息,当teamd通过LACP交互及内部状态机产生聚合成员口状态变化,调用TeamSync::TeamPortSync::onChange进行处理。
(3) teamd感知聚合成员口发生状态变化,teamsyncd从teamd获取当前聚合成员列表和状态,与变化前的聚合成员列表进行比较。如果聚合成员已存在且状态发生变化,则直接修改相应的APP_LAG_MEMBER_TABLE成员状态,如果成员列表发生变化,则向APP_LAG_MEMBER_TABLE添加新增的成员口并设置成员状态以及删除不存在的成员口。
(4) portsorch作为消费者订阅APP_LAG_MEMBER_TABLE进行处理,根据聚合成员口状态设置SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE和SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE,决定是否允许通过该成员口接收流量以及从该成员口发送流量。
(5) portsorch调用sairedis的API,并更新LAG Member配置表项及属性到ASIC_DB。
(6) SYNCD订阅ASIC_DB中的LAG Member表项并处理。
(7) 调用ASIC SDK对SAI API的实现,并通过ASIC driver下发到底层芯片。