一、简介
本周公司接了一个小项目,是给北京国舜科技股份有限公司做一个 HTTP 相关的小功能产品。大概实现功能是将交换机的源数据通过解析,分析出 HTTP 包配对的 request 和 response 头,并把每对的 request 和 response 头相关字段内容,通过TCP方式发送给对方的指定的服务器。
二、要求
需要一台流量设备来抓取分析交换机中的数据,流量设备通过推送的方式将解析后的数据发送到我们平台,平台基于数据进行分析,关于流量设备有下边几个需求:
1、流量设备
a.需要同时支持双向(流出,流入)流量分析 b.需能支持千兆网峰值流量
2、双方对接方案
采用 TCP 长连接对接,也就是通过 socket 以 TCP 长连接方式向对方服务器发送指定格式的数据内容,对方以相应的格式进行解析即可。
3、发送数据包格式
1: typedefstruct{
2: unsignedintmagic;// =0x4458434A,即“DXCJ”,用于同步时重新找到一个消息起始点
3: unsignedintlength;// 负载数据的长度
4: char*data;// 实际数据内容
5: }
6: // 即之前定义的按照需求文档中2张表的顺序\t分隔的请求+响应数据,长度为length,之后即下一条消息的magic字段
注意:对于magic和length字段需要考虑网络字节序问题,我们设备都是小端字节序,对方要求是大端字节序,网络字节序也是大端字节序,最后转换为网络字节序传输即可。我们这边是用C语言发送数据,对方接收端程序是java语言写的,不过这都无所谓了,底层协议都是标准的,可以相互通信。
4、负载均衡
对方接收数据端会有多个主机进行数据的接收,因此存在多主机负载均衡的问题。可以通过设置配置文件,内容大概以这样的形式: host:port,多个主机就存在多个列表。这个配置文件由双方来协商,可以由对方来配置,也存在对方后续会不定时的添加主机或修改主机的情况。在发送数据时,使用轮询调度(Round-Robin Scheduling)将流量分发到各个节点上。例如,若有 3 个节点,发送 4 个消息,按如下顺序: 给节点1发一条消息,再给节点 2 发一条消息,再给节点 3 发一条消息,再从节点1开始继续循环,实现简单的负载均衡。
同时需要支持失败重试,比如 3 个节点有一个节点宕了,那么我这边会尽快启动,启动之前流量由另两个节点处理。待失败节点启动后,再次 3 台机器同时工作;通过长连接端口来判定节点失败。
5、数据格式
需求的字段有很多,有一部分字段需要额外重新解析,解析完毕各个字段之后需要以指定的格式进行发送;一个请求各字段间依照上述2个表序号顺序使用\t拼接为一整行作为一条消息发送。如果某个字段为空,不做忽略。例如,假如3个字段:Name\tAge\tAddress 如果Age为空,分隔符保留。则输出:Name\t\tAddress即可,如:liming\t\tchangpingqu。
三、实现方式
本功能实际上并不复杂,完全可以在 ineedle 系统基础之上做一些简单修改,形成数据消息,然后发送给对方即可。关于 ineedle 后续的额外功能可以摘除,以提高整个 ineedle 系统的效率。
四、方案设计
总共有4部分需要修改或添加
1、HTTP的请求头和相应头字段
因为之前ineedle解析的相关字段是根据后续分析需求而定的,比较少,并不全面;而这次对方需求中的字段有一大部分在ineedle中是没有的,需要再额外再次进行解析。需要在ineedle系统中的相应解码代码中作出相应的解码工作,解码方式还按照原来的实现方式,只是添加些而已,然后挂到相应mbuf上,供后续sess使用。当然首要工作还是要对这58个字段(后续又增加7个字段)进行查找和分析,找出这些字段的经典取值,以及相关作用,用于后续代码中buf长度的判断,最终暂时定为(1024*8字节)大小。
2、定时刷新主机列表
这一部分可以在ineedle系统中原来flt_main进程中的某个定时线程中做,初次可以设置为10秒钟定时周期。主要定时刷新主机:端口列表是否有修改,或者判断是否有宕机设备重新启动。在刷新主机列表之前要判断一下配置文件host_list的时间戳是否变化,如果没有变化说明这段时间内用户没有更新主机列表,我们在程序中就不会再更新主机列表。还有一点就是用来重新复活之前宕机的设备,原理是这样的,如果某个设备宕机,就加入黑名单列表,就是在激活主机列表中将该主机alive标志位置1,表示宕机,后续不再往该设备上发送消息,直到用户重启该设备并定时器刷新复活。因此如果存在一个或多个设备宕机之后,用户将设备启动之后,需要做一个操作就是修改host_list文件的时间戳,简单touch一下就行,如果不touch的话,再没有修改host_list文件内容情况下,我们程序就不会重新刷新主机列表,也就不会再复活宕机而又重启的设备,这个条件到时候要提醒对方一定要做这个操作,否则单单重启宕机设备可能是无效的。这个地方原来是这样设计的,但是对方设备不仅可能会存在宕机情况,比如也可能是程序挂掉等,也是不能通信的,这种情况发生的几率还是比较大的,不能使用上述方式来搞,比较麻烦,每次都需要用户来ssh登陆并操作,这样可能本身就是一个缺陷,现在决定暂时放弃这个做法,也就是不去判断配置文件的时间戳,每次直接去读取配置主机列表,这样应该不会有太多性能消耗,不会有太大影响。
3、HTTP报文的request和reponse配置向队列写消息
这部分不用修改太多东西,主要是将分析到的request头和response头的相关字段,组合成指定格式的消息并发送给我们的缓存队列里边。存在一点问题,就是原来的ineedle每次解析完request和response头相关信息后都是挂在mbuf结构体上,比如上次解析的request头信息挂载mbuf上,下次到response包解析时,会把上次的mbuf清空,来填写这次response报文解析的数据,因此这时得不到完整的req和res的组合信息。因此需要将req和res信息挂到sess上,因为req和res在共同的sess上,因此可以读到上次的信息,并在第二次读到正确的res包时,进行req和res数据的组合,组合完毕将数据拷贝到缓冲数组。需要注意的是一个sess中可能有多个req和res,注意后边的req和res要把前边的数据覆盖掉,保证数据清零,不要让上次数据影响到这次的数据。这里实现的一下细节,需要在mbuf中添加一些需要额外解析的字段,而且在sess表结构中添加http_req_res_buf来存储一对请求响应的数据内容。组合完毕,在发送到数据队列中。
4、启动单独线程发送消息
另外单独启动一个线程来读取缓冲队列的消息,并按照主机列表的顺序通过socket TCP方式依次发送到相应主机上,如果遇到主机宕机情况,将其加入黑名单,并后续不发送数据给它。理论上是这样,要考虑接收主机的负载均衡。通过定时不断刷新,如果之前宕机程序又重新启动,需要重新尝试连接该主机并连接tcp并继续向该主机发送消息。从缓存队列读取消息并发送消息过程,完全类似一个生产者与消费者的模型,考虑到数据读写的互斥操作,读写线程之间要用线程的条件变量和线程锁来实现互斥的功能。
五、详细设计
这里就详细介绍一下,这几部分的详细设计实现细节。
1、解码http请求和响应字段数据
这一部分没有什么特别需要注意的地方,严格参考之前的解码方式,对比原来方案,实现代码编写,然后将生成解码的值挂到mbuf结构体上。这时就需要在mbuf结构体上添加需要的字段,待后续sess汇总时要用到,其实这时候tcp连接已经完全建立,sess表已经存在,完全可以将解析的字段值直接挂到sess表上,但是这样做有点混乱,没有严格按照之前的代码方案,所以最好的方式还是按照前边的方式比较好。
2、定时刷新主机列表
定时10秒间隔不停刷新主机列表。
3、汇总req和res并向缓冲队列写消息
4、发送线程不断发送消息
六、编码工作
。。。。。。暂时可以参考代码
七、编码中遇到的问题
01、问题1 ---- MSG_NOSIGNAL
在tcp编程过程中,测试代码时,发现如果服务器端被关闭,则client端系统会自动向内核发送一个信号,直接把client程序给杀死。这是linux默认的行为,这个地方我们不需要这样,需要在send函数后边标志位设置一下MSG_NOSIGNAL标记。这样设置完毕就可以了。
八、测试中遇到的问题
01、问题1
当时设置的如果接收方宕机重启后需要手动修改时间戳,这个问题需要修复一下。这个地方只要在刷新线程中将判断时间戳函数地方注释掉即可。
02、问题2
socket状态一直关闭不正常状态,出现FIN_WAIT1?????比较多,不知道是什么原因,后来也没有发现过。后续有时间查找具体原因。
03、问题3
双方接收数据对接不成功,对方接收消息时,经常出现错误,比如接收magic和length都会有错误,当时也不确定是什么问题,或者说是双方谁的程序有问题。后来又经过后续的连续测试,双方检查程序,结果也都没有确定哪方程序有问题?最后在C程序端发送socket上设置TCP_NODELAY标记后,对方接收数据暂时正确和正常。但这样做只能告诉内核快速发送,不要有延时,这样做的话,对jar端产生的影响就是接收缓存中的msg比较简单,从而减小出现错误的可能性,现在这只是推断,还不能确定具体原因是什么?
04、问题4
ineedle程序在对方接收端全部挂掉的时候会陷入睡眠,导致看门狗没有喂狗,致使ineedle重启。这种情况是很不友好的,需要除去这种情况,就在这个地方设置了一个下标志位,每次判断一下,如果是全部宕机的情况,这时就不要像往常那样进入睡眠,直接跳转到下条数据处理流程代码即可,直接丢弃数据,这种情况是不可避免的。
九、总结
最后在国舜做测试,做 ineedle 开机启动,遇到了一些问题,将启动程序命令写入到/etc/init.d/rc.local文件中,单单写入 /var/dz_resource/ineedle/release/ineedle 这样是运行不了,需要在这个命令之前 source /etc/profile 文件,可能是有什么环境变量需要设置,暂时没有找到具体什么原因,后续再研究。
十、遗留问题
ineedle设备HA特性尚不支持。后续待开发。
十一、测试数据
小设备发包极限:50M/S
银灰色小设备测试ineedle性能:
最大速率达到50M/S的速率时开始丢包,算是极限速率吧。
发包数据与生成消息比例:
50M/S ----> 1.6M/S
20M/S ----> 0.65M/S
十二、发包设备(交换机)
之前是用一个设备上边跑linux_pcap程序来给待测设备进行发包,这样对于低配置的发包设备来说,发包速率很低,最高才50M/s左右;对于较高配置(如DELL R430)速率也才到90M/s,这样达不到测试程序速率的效果。为此买了一台千兆交换机,准备用三台设备同时往交换机的三个网口来打流量,这样交换机镜像口就能汇总三个设备流量到一个口上,再通过一根网线将此网口数据打到待测试设备网口上即可。三台设备每台50M/S,三台总共150M/s,此时交换机镜像口接收数据速率有且120M/S,这样的速率基本上达到千兆网速,算是打到了千兆网卡的极限了。此工具以后可以测试后续性能比较好的设备了。但是发包工具当时做了简单的修改,把修改ip、cookie、等信息给去掉了,pcap拿到数据包之后直接从端口发送出去,这样效率应该会高一些,后续有时间可以继续优化一下发包工具。
其中拿到新交换机之后,看说明书进行简单配置,其实说明书并未有卵用,简单的不能再简单,就说明了哪些端口的指示灯表示什么,什么颜色代表网口的工作状态。然后通过交换机的web界面进行了简单配置,设置了几个端口为镜像口,和监测口。在设备与交换机相连的过程中,出现了协商速率不一致问题,明明都设置的是千兆全双工,设备显示的协商速率确实百兆的,后来仔细琢磨和尝试,换了灰色的网线之后,很轻易的自动识别并协商为1000M的网速,估计是和网线有关,估计那蓝色和红色的网线不支持千兆速率的吧。
十三、设备安装遇到的问题
从国舜拿到的设备回来之后开始安装操作系统,连上显示器,死活都不会显示,而且不会出现bios界面,刚在gs还是好好的,妹的,把内存条插拔了几次,重新安装上之后,不一会就听到了滴滴2声,显示器唰唰的打印出了bios的启动信息,原来是路上设备颠簸,内存松动,这种情况不止一次出现,之前就出现过一次。这种bios都不显示信息的,一定不是系统问题,肯定是基本设备的硬件没有检测到,或者是检测不到位,这样不满足系统启动的条件,就启动不了,而且没有打印信息,连bios都启动不了,太过分了,至少也要嘀嘀嘀报警一阵子啊,变态,这也算是知识的积累,总结经验。