高性能異步批量ping的golang實現

一個監控項目有個需求,會對一批域名全國的邊緣節點進行探測,這裏包括,丟包率,http 響應時間,探測頻率大概時間是2min 一個週期。這裏的域名大概有幾百個甚至上千。因爲是golang 寫的調度和agent, 因此,這裏探測丟包率是一個有意思的問題。因爲目前git 上沒有一個好用的支持multi-ping 的庫包,或者多ping 有bug,我本身實現了一個。html

git 地址:https://github.com/caucy/batch_pinglinux

1,icmp 協議介紹

icmp 的報文頭部一共是2+2+4+4+4 個字節。git

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。github

icmp 有個特色,listen 能收到其餘進程ping 的結果,看下面例子:golang

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 包。併發

2, 如何支持同時支持ping 多個addr

第一種是最簡單的,也是大多數探針採用的方式:subprocess 。這個方式有個缺點,就是每一個任務會fork 一個進程,很是耗費耗費資源。app

第二種方式,我是這樣想的,golang 有icmp 包,可以支持send and recive, 我直接起協程 去 收發,每一個協程和subprocess 同樣,先發後等,這樣不就好了?而後起一組協程池,這樣併發也能控制。然而,上面例子已經提到了,listen 後的conn 能收到其餘進程 ping 的結果,這樣實現挺麻煩。優化

第三種方式,一個協程收,一個協程發。最後選擇的是這種方式。ui

一個協程收,一個協程發,有什麼比較麻煩地方?由於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,時間間隔很是小,發送機器很是多的時候,會出現很是嚴重丟包,內核參數須要優化。

最後,文章不易,但願你們點個star,試用。 https://github.com/caucy/batch_ping

相關文章
相關標籤/搜索