自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go Udp 的高性能優(yōu)化

開發(fā) 后端
我們知道應(yīng)用程序之間的網(wǎng)絡(luò)傳輸會(huì)存在粘包半包的問題。該問題的由來我這里就不描述了,大家去搜吧。使用 tcp 會(huì)存在該問題,而 udp 是不存在該問題的。

[[417346]]

本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)桃花源」,作者峰云就她了。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)桃花源公眾號(hào)。

前段時(shí)間(已經(jīng)是 2 年前了??)優(yōu)化了 golang udp client 和 server 的性能問題,我在這里簡單描述下 udp 服務(wù)的優(yōu)化過程。

當(dāng)然,udp 性能本就很高,就算不優(yōu)化,也輕易可以到幾十萬的 qps,但我們想更好的優(yōu)化 go udp server 和 client。

UDP 存在粘包半包問題?

我們知道應(yīng)用程序之間的網(wǎng)絡(luò)傳輸會(huì)存在粘包半包的問題。該問題的由來我這里就不描述了,大家去搜吧。使用 tcp 會(huì)存在該問題,而 udp 是不存在該問題的。

為啥? tcp 是無邊界的,tcp 是基于流傳輸?shù)模瑃cp 報(bào)頭沒有長度這個(gè)變量,而 udp 是有邊界的,基于消息的,是可以解決粘包問題的。udp 協(xié)議里有 16 位來描述包的大小,16 位決定他的數(shù)字最大數(shù)字是 65536,除去 udp 頭和 ip 頭的大小,最大的包差不多是 65507 byte。

但根據(jù)我們的測(cè)試,udp 并沒有完美的解決應(yīng)用層粘包半包的問題。如果你的 go udp server 的讀緩沖是 1024,那么 client 發(fā)送的數(shù)據(jù)不能超過 server read buf 定義的 1024 byte,不然還是要處理半包了。如果發(fā)送的數(shù)據(jù)小于 1024 byte,倒是不會(huì)出現(xiàn)粘包的問題。

  1. // xiaorui.cc 
  2. buf := make([]byte, 1024) 
  3. for { 
  4.     n, _ := ServerConn.Read(buf[0:]) 
  5.     if string(buf[0:n]) != s { 
  6.         panic(...) 
  7. ... 

在 Linux下 借助 strace 發(fā)現(xiàn) syscall read fd 的時(shí)候,最大只獲取 1024 個(gè)字節(jié)。這個(gè) 1024 就是上面配置的讀緩沖大小。

  1. // xiaorui.cc 
  2.  
  3. [pid 25939] futex(0x56db90, FUTEX_WAKE, 1) = 1 
  4. [pid 25939] read(3, "Introduction... 隱藏... overview of IPython'", 1024) = 1024 
  5. [pid 25939] epoll_ctl(4, EPOLL_CTL_DEL, 3, {0, {u32=0, u64=0}}) = 0 
  6. [pid 25939] close(3  
  7. [pid 25940] <... restart_syscall resumed> ) = 0 
  8. [pid 25939] <... close resumed> )       = 0 
  9. [pid 25940] clock_gettime(CLOCK_MONOTONIC, {19280781, 509925143}) = 0 
  10. [pid 25939] pselect6(0, NULLNULLNULL, {0, 1000000}, 0  
  11. [pid 25940] pselect6(0, NULLNULLNULL, {0, 20000}, 0) = 0 (Timeout) 
  12. [pid 25940] clock_gettime(CLOCK_MONOTONIC, {19280781, 510266460}) = 0 
  13. [pid 25940] futex(0x56db90, FUTEX_WAIT, 0, {60, 0}  

下面是 golang 里 socket fd read 的源碼,可以看到你傳入多大的 byte 數(shù)組,他就 syscall read 多大的數(shù)據(jù)。

  1. // xiaorui.cc 
  2. func read(fd int, p []byte) (n int, err error) { 
  3.     var _p0 unsafe.Pointer 
  4.     if len(p) > 0 { 
  5.         _p0 = unsafe.Pointer(&p[0]) 
  6.     } else { 
  7.         _p0 = unsafe.Pointer(&_zero) 
  8.     } 
  9.     r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(_p0), uintptr(len(p))) 
  10.     n = int(r0) 
  11.     if e1 != 0 { 
  12.         err = errnoErr(e1) 
  13.     } 
  14.     return 

http2 為毛比 http1 的協(xié)議解析更快,是因?yàn)?http2 實(shí)現(xiàn)了 header 的 hpack 編碼協(xié)議。thrift 為啥比 grpc 快?單單對(duì)比協(xié)議結(jié)構(gòu)體來說,thrift 和 protobuf 的性能半斤八兩,但對(duì)比網(wǎng)絡(luò)應(yīng)用層協(xié)議來說,thrift 要更快。因?yàn)間rpc是在 http2 上跑的,grpc server 不僅要解析 http2 header,還要解析 http2 body,這個(gè) body 就是 protobuf 數(shù)據(jù)。

所以說,高效的應(yīng)用層協(xié)議也是高性能服務(wù)的重要的一個(gè)標(biāo)準(zhǔn)。我們先前使用的是自定義的 TLV 編碼,t 是類型,l 是 length,v 是數(shù)據(jù)。一般解決網(wǎng)絡(luò)協(xié)議上的數(shù)據(jù)完整性差不多是這個(gè)思路。當(dāng)然,我也是這么搞得。

如何優(yōu)化 udp 應(yīng)用協(xié)議上的開銷?

上面已經(jīng)說了,udp 在合理的 size 情況下是不需要依賴應(yīng)用層協(xié)議解析包問題。那么我們只需要在 client 端控制 send 包的大小,server 端控制接收大小,就可以節(jié)省應(yīng)用層協(xié)議帶來的性能高效。??別小看應(yīng)用層協(xié)議的 cpu 消耗!

解決 golang udp 的鎖競爭問題

在 udp 壓力測(cè)試的時(shí)候,發(fā)現(xiàn) client 和 server 都跑不滿 cpu 的情況。開始以為是 golang udp server 的問題,去掉所有相關(guān)的業(yè)務(wù)邏輯,只是單純的做 atomic 計(jì)數(shù),還是跑不滿 cpu。通過 go tool pprof 的函數(shù)調(diào)用圖以及火焰圖,看不出問題所在。嘗試使用 iperf 進(jìn)行 udp 壓測(cè),golang udp server 的壓力直接干到了滿負(fù)載。可以說是壓力源不足。

那么 udp 性能上不去的問題看似明顯了,應(yīng)該是 golang udp client 的問題了。我嘗試在 go udp client 里增加了多協(xié)程寫入,10 個(gè) goroutine,100 個(gè) goroutine,500 個(gè) goroutine,都沒有好的明顯的提升效果,而且性能抖動(dòng)很明顯。??

進(jìn)一步排查問題,通過 lsof 分析 client 進(jìn)程的描述符列表,client 連接 udp server 只有一個(gè)連接。也就是說,500 個(gè)協(xié)程共用一個(gè)連接。接著使用 strace 做 syscall 系統(tǒng)調(diào)用統(tǒng)計(jì),發(fā)現(xiàn) futex 和 pselect6 系統(tǒng)調(diào)用特別多,這一看就是存在過大的鎖競爭。翻看 golang net 源代碼,果然發(fā)現(xiàn) golang 在往 socket fd 寫入時(shí),存在寫鎖競爭。

TODO 圖片

  1. // xiaorui.cc 
  2.  
  3. // Write implements io.Writer. 
  4. func (fd *FD) Write(p []byte) (int, error) { 
  5.     if err := fd.writeLock(); err != nil { 
  6.         return 0, err 
  7.     } 
  8.     defer fd.writeUnlock() 
  9.     if err := fd.pd.prepareWrite(fd.isFile); err != nil { 
  10.         return 0, err 
  11.     } 

怎么優(yōu)化鎖競爭?

實(shí)例化多個(gè) udp 連接到一個(gè)數(shù)組池子里,在客戶端代碼里隨機(jī)使用 udp 連接。這樣就能減少鎖的競爭了。

總結(jié)

udp 性能調(diào)優(yōu)的過程就是這樣子了。簡單說就兩個(gè)點(diǎn):一個(gè)是消除應(yīng)用層協(xié)議帶來的性能消耗,再一個(gè)是 golang socket 寫鎖帶來的競爭。

 

當(dāng)我們一些性能問題時(shí),多使用 perf、strace 功能,再配合 golang pprof 分析火焰圖來分析問題。實(shí)在不行,直接干 golang 源碼。

 

責(zé)任編輯:武曉燕 來源: 碼農(nóng)桃花源
相關(guān)推薦

2024-12-25 14:03:03

2022-03-21 14:13:22

Go語言編程

2023-03-10 09:11:52

高性能Go堆棧

2019-03-01 11:03:22

Lustre高性能計(jì)算

2021-05-27 10:02:57

Go緩存數(shù)據(jù)

2019-05-21 09:40:47

Elasticsear高性能 API

2024-04-28 10:17:30

gnetGo語言

2009-01-05 10:00:11

JSP優(yōu)化Servlet性能優(yōu)化

2023-08-29 15:10:04

持續(xù)性能優(yōu)化開發(fā)

2014-03-19 14:34:06

JQuery高性能

2018-03-30 18:17:10

MySQLLinux

2018-09-18 17:20:14

MySQL優(yōu)化數(shù)據(jù)庫

2023-12-01 07:06:14

Go命令行性能

2023-12-14 08:01:08

事件管理器Go

2021-08-04 09:33:22

Go 性能優(yōu)化

2023-12-30 18:35:37

Go識(shí)別應(yīng)用程序

2019-03-14 15:38:19

ReactJavascript前端

2023-11-01 11:59:13

2025-01-13 13:00:00

Go網(wǎng)絡(luò)框架nbio

2025-02-05 12:09:12

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)