关于BFD(双向转发检测)开发的总结

       前一阵子因为业务需要,需要设备提供高可靠性(HA),      高可靠性指的是设备出故障的时候能够不影响业务的运行,我们部门产品的高可靠性的实现方案是双机备份,就是一台机器出故障的时候另一台设备能够接替出故障的设备继续运行,双机之间是主备模式。正常情况下,主设备上线运行,备设备监测主设备状态,一旦检测到主设备出故障,就立即切换状态。这里就用到了BFD。

         BFD(Bidirectional Forwarding Detection,[RFC5880])全称双向转发检测协议。提供了一种链路之间联通性检测的方式,优势是检测速度快、另一点就是因为是标准协议,各家的方案对接起来不会太难。如果不用BFD的话,设备之间检测时间级别是秒级,也就是主机器出故障到备机上线运行,中间有几秒时间业务是中断的,举个栗子,当你和女友通电话的时候, 天空正在下雨,一个雷击,正好击中了附近的电信基站,这时信号中断,等到你重新接通电话的时候,等待的时间就是切换时间。对于某些情况,损失几秒不是太大的问题,最多就是再来一次。但是对于一些重要行业的关键应用,业务停摆带来的损失巨大,比如说金融业,证券系统。BFD的好处就是它检测的时间精度很高,一般来说都支持毫秒级,虽然协议的标准是微妙,但是对于常见的嵌入式设备,微秒的精度会给系统cpu带来很大的压力,所以毫秒更常见一些。

       BFD的协议虽然很简单,但是对于没有网络协议栈实现经验的人来说,还是有点困难的。比如我这种..

首先就是去看协议,标准的协议,反复的看。看过多遍之后就去阅读已有的实现方案,可以在github、SourceForge 等网站找到一些。借鉴现有的实现来实现自己的BFD。最初看到一个实现是kbfd,它是在内核态实现的,并且内核版本有点老,2.6.x。我们的系统版本基于openwrt,内核3.4.39。所以就决定移植出来,中间涉及到的知识包括内核态套接字编程、内核高精度定时器hrtimer的使用、工作队列以及延迟工作队列等。移植不是重点,重点在于对协议栈、状态机的实现。虽然BFD的状态机简单,只有三个状态Down, Init, Up。但是状态机的变迁管理还是很痛疼的。

        但是,这样开发出来的功能有风险。不能使用。风险?在哪。

       相较于用户层层面开发,内核态的一个好处就是效率高,不用频繁在内核态和用户态之间通信。但是风险也大,稍微一个kernel panic,整个系统就挂了,所以决定将整个BFD在用户层实现。这中间涉及到了Linux用户态编程的相关知识,包括多线程、消息队列、网络套接字和异步通信等。这部分代码是c完成的。产品的业务层代码是用go写的,因为要和其它模块交互通信,所以学习了Go语言(顺便夸一下Go语言,并发编程真的好棒!)。最终的实现就是BFD的核心功能以c库的形式提供,Go实现封装调用。

        BFD核心功能的实现框架大概是这样一个样子,每个会话由一个独立线程控制,另外有一个独立线程进行消息分发 。总体架构大概是如下这幅图:

监听线程的实现采用的是epoll监听,包括定时器超时事件、套接字收发等。有消息的话就发送到对应的会话线程里的消息队列中。而BFD会话线程的任务就是监听自身的消息队列,循环处理。没有消息的话就等待。消息队列的实现有两种,一种是linux提供的Posix消息队列,另一种是自定义的消息队列,系统提供的消息队列限制太多,例如一个进程创建的消息队列个数有限,其次就是消息队列的长度。所以这里采用了自定义的消息队列,利用条件变量与互斥锁来实现。

         关于BFD协议的开发基本上就是这个样子,期间经历了三次大的更改。一次是内核态实现、一次是用户态实现最后就是CGo的方式实现。各项功能运行良好。还有就是并没有完全实现协议里面定义的各项功能,例如加密、查询模式、ECHO模式等。这一部分在下个版本更新中再考虑。

       通信协议的实现很重要的一点就是读协议,掌握了协议内容才能够开发。最好读原版,大部分通信的协议RFC都是英文版。

       またね!