一个监控项目有个需求, 会对一批域名全国的边缘节点进行探测, 这里包括, 丢包率, http 响应时间, 探测频率大概时间是 2min 一个周期. 这里的域名大概有几百个甚至上千. 由于是 golang 写的调度和 agent, 所以, 这里探测丢包率是一个有意思的问题. 由于目前 Git 上没有一个好用的支持 multi-ping 的库包, 或者多 ping 有 bug, 我自己实现了一个.
Git 地址: https://github.com/caucy/batch_ping
1,icmp 协议介绍
icmp 的报文头部一共是 2+2+4+4+4 个字节.
- type ICMP struct {
- Type uint8
- Code uint8
- CheckSum uint16
- Identifier uint16
- SequenceNum uint16
- }
这里 type 是 icmp 类型, 常见有发送报文头 Echo, 回收报文头 Echo Reply 等, 更多类型 见 https://tools.ietf.org/html/rfc792. Code 进一步划分 ICMP 的类型, 该字段用来查找产生错误的原因; CheckSum 校验码部分, 这个字段包含有从 ICMP 报头和数据部分计算得来的, 用于检查错误的数据; 而 Identifier 通常为进程 id, 标识具体是哪个进程发送的 icmp 包; SequenceNum 标识发送包的顺序 id.
icmp 有个特点, listen 能收到其他进程 ping 的结果, 看下面例子:
- package main
- import (
- "log"
- "github.com/caucy/batch_ping"
- )
- func main() {
- ipSlice := []string{}
- // ip list should not more than 65535
- ipSlice = append(ipSlice, "3g.qq.com")
- bp, err := ping.NewBatchPinger(ipSlice, false) // true will need to be root
- if err != nil {
- log.Fatalf("new batch ping err %v", err)
- }
- bp.SetDebug(true) // debug == true will fmt debug log
- bp.SetCount(100)
- bp.Run()
- }
启动上面的进程, 会连续 ping 3g.qq.com, 同时, 再启动一个进程 ping www.baidu.com , 日志会显示, 收到了 220.181.38.150 的 icmp 包.
image
2, 如何支持同时支持 ping 多个 addr
第一种是最简单的, 也是大多数探针采用的方式: subprocess . 这个方式有个缺点, 就是每个任务会 fork 一个进程, 非常耗费耗费资源.
第二种方式, 我是这样想的, golang 有 icmp 包, 能够支持 send and recive, 我直接起协程 去 收发, 每个协程和 subprocess 一样, 先发后等, 这样不就行了? 然后起一组协程池, 这样并发也能控制. 然而, 上面例子已经提到了, listen 后的 conn 能收到其他进程 ping 的结果, 这样实现挺麻烦.
第三种方式, 一个协程收, 一个协程发. 最后选择的是这种方式.
一个协程收, 一个协程发, 有什么比较麻烦地方? 因为 icmp 层只能标识 seq, 所以会出现 icmp 包头相同的情况, 同时, 批量收发, 非常容易出现丢包的情况.
3,batch-ping 特性
支持原地址控制
支持 ipv6 (操作系统本身支持 "ip6:ipv6-icmp","udp6" dial )
支持时间间隔控制
支持发送方式控制
支持多 addr 控制
支持 Mac, Linux
使用示例:
- package main
- import (
- "log"
- "github.com/caucy/batch_ping"
- )
- func main() {
- ipSlice := []string{}
- // ip list should not more than 65535
- ipSlice = append(ipSlice, "2400:da00:2::29") //support ipv6
- ipSlice = append(ipSlice, "baidu.com")
- bp, err := ping.NewBatchPinger(ipSlice, false) // true will need to be root
- if err != nil {
- log.Fatalf("new batch ping err %v", err)
- }
- bp.SetDebug(true) // debug == true will fmt debug log
- bp.SetSource("") // if hava multi source ip, can use one isp
- bp.OnFinish = func(stMap map[string]*ping.Statistics) {
- for ip, st := range stMap {
- log.Printf("\n--- %s ping statistics ---\n", st.Addr)
- log.Printf("ip %s, %d packets transmitted, %d packets received, %v%% packet loss\n", ip,
- st.PacketsSent, st.PacketsRecv, st.PacketLoss)
- log.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
- st.MinRtt, st.AvgRtt, st.MaxRtt, st.StdDevRtt)
- log.Printf("rtts is %v \n", st.Rtts)
- }
- }
- err = bp.Run()
- if err != nil {
- log.Printf("run err %v \n", err)
- }
- bp.OnFinish(bp.Statistics())
- }
4, 可能问题
因为 icmp 基于 udp, 时间间隔非常小, 发送机器非常多的时候, 会出现非常严重丢包, 内核参数需要优化.
来源: http://www.jianshu.com/p/89ded7066ac4