天天看點

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

mymon是Open-falcon的用來監控Mysql的元件,今天使用起來遇到了一個問題,資料庫明明正确配置,但是啟動的時候總是報“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”的錯誤

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

系統Centos6.10,Mysql5.1.73,中間都是我調試的過程記錄,如果想看解決方案直接去第5節

1 粗略分析

定位一下代碼,發現Mysql的連接配接是common/mysql.go這個檔案負責的,看一下代碼如下

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

我開始以為是conf.DataBase這幾個配置項出了問題,直接替換成了文本,仍然報錯

mymon調用的是一個叫mymysql的mysql驅動,無奈,隻好開始排查mymysql的問題

決定抓包分析之,看一下mymysql和Mysql之間的TCP連接配接

2 封包分析

用Linux自帶工具tcpdump,在mysql端抓取,-w表示儲存為檔案,網卡根據自己的情況選擇

tcpdump -w tcp.pcap -i [網卡] port 3306
           

登陸完mysql,把檔案弄回windows用wireshark打開

2.1 正常連接配接的TCP封包

登入Mysql,密碼正确登陸成功,mysql的機器ip是56.24,登陸的機子是56.21

sudo tcpdump -w correct.pcap -i eth1 port 3306 #在mysql的機器上
mysql -u root -p  #用另一台虛拟機,遠端登入mysql
           

wireshark打開

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

2.2 mymon的封包

sudo tcpdump -w mymon.pcap -i lo port 3306 #在mysql的機器上
./mymon etc/myMon.cfg  #啟動mymon
           

同樣wireshark打開

2.3 分析

兩個封包一對比很明顯了,上面是mymon下面是正常的,可以看到在資料庫發送了版本号等資訊後,mymon沒有給資料庫發送登陸請求的封包,然後等待了10秒之後資料庫斷開了連接配接

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

3 mymysql源碼分析

分析源代碼定位到native/init.go檔案夾下的auth()函數,這個函數是負責發送登陸使用者名和密碼的,代碼如下

func (my *Conn) auth() {
	if my.Debug {
		log.Printf("[%2d <-] Authentication packet", my.seq)
	}
	flags := uint32(
		_CLIENT_PROTOCOL_41 |
			_CLIENT_LONG_PASSWORD |
			_CLIENT_LONG_FLAG |
			_CLIENT_TRANSACTIONS |
			_CLIENT_SECURE_CONN |
			_CLIENT_LOCAL_FILES |
			_CLIENT_MULTI_STATEMENTS |
			_CLIENT_MULTI_RESULTS)
	// Reset flags not supported by server
	flags &= uint32(my.info.caps) | 0xffff0000
	if my.plugin != string(my.info.plugin) {
		my.plugin = string(my.info.plugin)
	}
	var scrPasswd []byte
	switch my.plugin {
	case "caching_sha2_password":
		flags |= _CLIENT_PLUGIN_AUTH
		scrPasswd = encryptedSHA256Passwd(my.passwd, my.info.scramble[:])
	case "mysql_old_password":
		my.oldPasswd()
		return
	default:
		// mysql_native_password by default
		scrPasswd = encryptedPasswd(my.passwd, my.info.scramble[:])
	}

	// encode length of the auth plugin data
	var authRespLEIBuf [9]byte
	authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(scrPasswd)))
	if len(authRespLEI) > 1 {
		// if the length can not be written in 1 byte, it must be written as a
		// length encoded integer
		flags |= _CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
	}

	pay_len := 4 + 4 + 1 + 23 + len(my.user) + 1 + len(authRespLEI) + len(scrPasswd) + 21 + 1

	if len(my.dbname) > 0 {
		pay_len += len(my.dbname) + 1
		flags |= _CLIENT_CONNECT_WITH_DB
	}
	pw := my.newPktWriter(pay_len)
	pw.writeU32(flags)
	pw.writeU32(uint32(my.max_pkt_size))
	pw.writeByte(my.info.lang)   // Charset number
	pw.writeZeros(23)            // Filler
	pw.writeNTB([]byte(my.user)) // Username
	pw.writeBin(scrPasswd)       // Encrypted password

	// write database name
	if len(my.dbname) > 0 {
		pw.writeNTB([]byte(my.dbname))
	}

	// write plugin name
	if my.plugin != "" {
		pw.writeNTB([]byte(my.plugin))
	}
	return
}
           

也就是說經過重重包裝,pw就是往TCP連接配接的緩沖區寫資料的一個對象,但是資料為什麼沒有發送出去呢

我修改了代碼,在auth()函數return前強行調用pw.wr.Flush()重新整理緩沖區,抓包發現,一個長度128的包發出來了,但是并沒有被識别為一個Mysql連接配接

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

把這個包的頭部和正确的包對比

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

查閱資料,得知Protocols in frame封裝于實體層

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

又查了一下,說這個Protocols in frame是wireshark自己識别出來的一個東西,不是資料包裡面帶的,嗯

繼續詳細對比兩個資料包

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

紅框框出來的這一位,可以看到是表示資料的長度的,右邊正确資料包的資料長度為3a(十六進制,就是58),左邊的長度不對,是80,是以這就是資料包沒有發送的原因麼,長度設定得過大,資料沒有填滿是以一直沒有發送

看一下這段代碼,pay_len經過很長的算式計算出來,在我的賬号密碼情況下這個pay_len是80

而仔細看正确的資料包可以發現,資料包最後一個部分就是加密後的密碼,那麼後面的21+1是個啥呢,看代碼,我沒有填dbname和my.plugin,那麼在密碼之後寫的資料應該就是"mysql_native_password"了吧,

pay_len := 4 + 4 + 1 + 23 + len(my.user) + 1 + len(authRespLEI) + len(scrPasswd) + 21 + 1

if len(my.dbname) > 0 {
	pay_len += len(my.dbname) + 1
	flags |= _CLIENT_CONNECT_WITH_DB
}
pw := my.newPktWriter(pay_len)
pw.writeU32(flags)
pw.writeU32(uint32(my.max_pkt_size))
pw.writeByte(my.info.lang)   // Charset number
pw.writeZeros(23)            // Filler
pw.writeNTB([]byte(my.user)) // Username
pw.writeBin(scrPasswd)       // Encrypted password


// write database name
if len(my.dbname) > 0 {
	pw.writeNTB([]byte(my.dbname))
}

// write plugin name
if my.plugin != "" {
	pw.writeNTB([]byte(my.plugin))
}
           

把my.plugin這個變量相關的代碼抽出來,發現了問題,my.info這個變量儲存的是握手之後Mysql傳回的資料,在我的電腦上,Mysql并沒有傳回plugin相關的資訊,導緻my.plugin被抹成了空字元串

if my.plugin != string(my.info.plugin) {
    my.plugin = string(my.info.plugin)
}
           

這時候我發現了一個問題,我一直看的是mymon/vendor檔案夾底下的mymysql代碼,mymon本身使用的也是這個代碼,但是我從github上下載下傳的mymysql代碼有所不同,沒有使用my.info.plugin,發送plugin的代碼改成了如下的一部分

https://github.com/ziutek/mymysql

if my.plugin != "" {
	pw.writeNTB([]byte(my.plugin))
} else {
	pw.writeNTB([]byte("mysql_native_password"))
}
           

我把vendor底下的代碼改了一下,重新運作,已經可以連接配接了,但是報了其他的錯,也就是說這是mymon/vendor檔案夾下的mymysql版本太低,和我的Mysql不适配造成的

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

 我決定用github上的mymysql替換一下,

cd ~
sudo rm -r $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/native $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/mysql
git clone git clone https://github.com/ziutek/mymysql.git
cp -r mymysql/native $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/native
cp -r mymysql/mysql $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/mysql
cd $GOPATH/src/github.com/open-falcon/mymon
make && ./mymon etc/myMon.cfg  #編譯運作
           

錯誤依舊,好吧,起碼資料庫是連上了

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

4 吾生有崖而bug無崖

這個錯誤發生在mymon/show.go的182行,newMetaData.SetValue(row.Int(0))這個調用上,ziutek報錯,相關代碼如下,根據報錯,主要是Row.Int(0)這個出了問題,ziutek表示Row這個對象儲存Mysql傳回的結果,估計是結果傳回出錯

var row mysql.Row
newMetaData := NewMetric(conf, metric)
row, _, err = db.QueryFirst("SELECT /*!50504 @@GLOBAL.innodb_stats_on_metadata */;")
newMetaData.SetValue(row.Int(0))
           

這個select語句肥腸奇怪,我從來沒見過,進入Mysql執行一下這個select,Mysql直接給我報了個文法錯誤

我仔細看了看,越看越覺得像一對注釋,又看了看其他語句,原來如此啊

給他改掉

var row mysql.Row
newMetaData := NewMetric(conf, metric)
row, _, err = db.QueryFirst("SELECT /*!50504 */@@GLOBAL.innodb_stats_on_metadata;")
newMetaData.SetValue(row.Int(0))
           

運作,成功了,沒有報錯

了嗎?

看一眼myMon.log

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

????

這個錯誤是在show.go的ShowBinaryLogs(()函數執行中産生的,我的Mysql沒有啟用binlog,是以傳回了,這裡把直接傳回改成一個Warning

binaryLogStatus, err := ShowBinaryLogs(conf, db)
if err != nil {
    Log.Warning("Binary Log is not used")
} else {
    data = append(data, binaryLogStatus...)
}
           

好了,去用戶端看一眼myMon回報的資料,mysql_alive_local/isSlave=0,port=3306,readOnly=0,type=mysql有了一個資料1,湊合湊合就這樣了

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

5 解決方案總結

5.1 unexpected EOF

碰到了這種錯誤“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

這個錯誤我将近研究了一周的時間,終于摸索出了一些,主要是mymon這個版本使用的Mysql驅動和我的Mysql不比對,替換為最新版本的mymysql可以運作

cd ~
sudo rm -r $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/native $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/mysql
git clone git clone https://github.com/ziutek/mymysql.git
cp -r mymysql/native $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/native
cp -r mymysql/mysql $GOPATH/src/github.com/open-falcon/mymon/vendor/github.com/ziutek/mymysql/mysqlcd $GOPATH/src/github.com/open-falcon/mymon

           

5.2 index out of range

碰到這個“panic: runtime error: index out of range”

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

把show.go的181行修改一下

#修改前
row, _, err = db.QueryFirst("SELECT /*!50504 @@GLOBAL.innodb_stats_on_metadata */;")
#修改後
row, _, err = db.QueryFirst("SELECT /*!50504 */ @@GLOBAL.innodb_stats_on_metadata;")
           

編譯運作

make && ./mymon etc/myMon.cfg  #編譯運作
           

5.3 You are not using binary logging

看一眼myMon.log,如果出現“You are not using binary logging”,說明你的Mysql沒有開binlog

Mysql監控元件mymon報錯“NewMySQLConnection Error: Building mysql connection failed!: unexpected EOF”問題1 粗略分析2 封包分析3 mymysql源碼分析4 吾生有崖而bug無崖5 解決方案總結5.1 unexpected EOF

修改一下show.go的82-84行,這裡我在日志裡打了一個Warning輸出,你把整個binlog的代碼注釋掉也可以

#修改前
if err != nil {
    return []*MetaData{binlogFileCounts, binlogFileSize}, err
}
#修改後
if err != nil {
    Log.Warning("Binary Log is not used")
} else {
    data = append(data, binaryLogStatus...)
}
           

編譯運作

make && ./mymon etc/myMon.cfg  #編譯運作
           

繼續閱讀