[譯]Go TCP Socket的實現

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

原文: TCP Socket Implementation On Golang by Gian Giovani.

譯者注: 作者並沒有從原始碼層級去分析Go socket的實現,而是利用strace工具來反推Go Socket的行為。這一方法可以擴充我們分析代碼的手段。
原始碼層級的分析可以看其實現: net poll,以及一些分析文章:The Go netpoller, The Go netpoller and timeout

Go語言是我寫web程式的首選, 它隱藏了很多細節,但仍然不失靈活性。最新我用strace工具分析了一下一個http程式,純屬手賤但還是發現了一些有趣的事情。

下面是strace的結果:

1234567891011121314151617181920
% time     seconds  usecs/call     calls    errors syscall------ ----------- ----------- --------- --------- ---------------- 91.24    0.397615         336      1185        29 futex  4.13    0.018009           3      7115           clock_gettime  2.92    0.012735          19       654           epoll_wait  1.31    0.005701           6       911           write  0.20    0.000878           3       335           epoll_ctl  0.12    0.000525           1       915       457 read  0.02    0.000106           2        59           select  0.01    0.000059           0       170           close  0.01    0.000053           0       791           setsockopt  0.01    0.000035           0       158           getpeername  0.01    0.000034           0       170           socket  0.01    0.000029           0       160           getsockname  0.01    0.000026           0       159           getsockopt  0.00    0.000000           0         7           sched_yield  0.00    0.000000           0       166       166 connect  0.00    0.000000           0         3         1 accept4------ ----------- ----------- --------- --------- ----------------100.00    0.435805                 12958       653 total

在這個剖析結果中有很多有趣的東東,但本文中要特別指出的是read的錯誤數和futex調用的錯誤數。

一開始我沒有深思futex的調用, 大部分情況它無非是一個喚醒調用(wake call)。既然這個程式會處理每秒幾百個請求,它應該包含很多go routine。另一方面,它使用了channel,這也會導致很多block情況,所以有很多futex調用也很正常。 不過後來我發現這個數也包含來自其它的邏輯,後面再表。

Why you no read

有誰喜歡錯誤(error)?短短一分鐘就有幾百次的錯誤,太糟糕了, 這是我看到這個剖析結果後最初的印象。那麼 read call又是什麼東東?

123
read(36, "GET /xxx/v3?q=xx%20ch&d"..., 4096) = 520...read(36, 0xc422aa4291, 1)               = -1 EAGAIN (Resource temporarily unavailable)

每次read調用同一個檔案描述符,總是(可能)伴隨著一個 EAGAIN error。我記得這個錯誤,當檔案描述符還沒有準備(ready)某個操作的時候就會返回這個錯,上面的例子中操作是read。問題是為什麼Go會這樣做呢?

我猜想這可能是epoll_wait的一個bug, 它為每一個檔案描述符提供了錯誤的ready事件?每一個檔案描述符? 看起來read事件是錯誤事件的兩倍,為什麼是兩倍?

老實說,我的epoll知識很了了,程式只是一個簡單的處理事件的socket handler(類似)。沒有多線程,沒有同步,非常簡單。

通過Google我找到了一篇極棒的文章分析評論epoll,由Marek所寫,。

這篇文章重要的摘要就是:在多線程中使用epoll, 不必要的喚醒(wake up)通常是不可避免的,因為我們想通知每個等待事件的worker。

這也正好解釋了我們的futex 喚醒數。還是讓我們看一個簡化版本來好好理解怎麼在基於事件的socket處理常式中使用epoll吧:

  1. Bind socket listenerfile descriptor, 我們稱之為 s_fd
  2. 使用epoll_create建立 epoll file descriptor , 我們稱之為 e_fd
  3. 通過epol_ctl bind s_fde_fd, 處理特殊的事件(通常EPOLLIN|EPOLLOUT)
  4. 建立一個無限迴圈 (event loop), 它會在每次迴圈中調用epoll_wait得到已經ready串連
  5. 處理ready的串連, 在多worker實現中會通知每一個worker

Using strace I found that golang using edge triggered epoll
使用strace我發現 golang使用 edge triggered epoll:

1
epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2490298448, u64=140490870550608}}) = 0

這意味著下面的過程應該是go socket的實現:

1、Kernel: 收到一個新串連.
2、Kernel: 通知等待的線程 threads A 和 B. 由於level-triggered 通知的"驚群"(“thundering herd”)行為,kernel必須喚醒這兩個線程.
3、Thread A: 完成 epoll_wait().
4、Thread B: 完成 epoll_wait().
5、Thread A: 執行 accept(), 成功.
6、Thread B: 執行 accept(), 失敗, EAGAIN錯誤.

現在我有八成把握就是這個case,不過還是讓我們用一個簡單的程式來分析。

12345678910111213
package mainimport "net/http"func main() {http.HandleFunc("/", handler)http.HandleFunc("/test", handler)http.ListenAndServe(":8080", nil)}func handler(w http.ResponseWriter, r *http.Request) {}

一個簡單的請求後的strace結果:

12345678
epoll_wait(4, [{EPOLLIN|EPOLLOUT, {u32=2186919600, u64=140542106779312}}], 128, -1) = 1futex(0x7c1bd8, FUTEX_WAKE, 1)          = 1futex(0x7c1b10, FUTEX_WAKE, 1)          = 1read(5, "GET / HTTP/1.1\r\nHost: localhost:"..., 4096) = 348futex(0xc420060110, FUTEX_WAKE, 1)      = 1write(5, "HTTP/1.1 200 OK\r\nDate: Sat, 03 J"..., 116) = 116futex(0xc420060110, FUTEX_WAKE, 1)      = 1read(5, 0xc4200f6000, 4096)             = -1 EAGAIN (Resource temporarily unavailable)

看到epoll_wait有兩個futex調用,我認為是worker執行以及一次 error read。

如果GOMAXPROCS設定為1,在單worker情況下:

12345678910111213
epoll_wait(4,[{EPOLLIN, {u32=1969377136, u64=140245536493424}}], 128, -1) = 1futex(0x7c1bd8, FUTEX_WAKE, 1)          = 1accept4(3, {sa_family=AF_INET6, sin6_port=htons(54400), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28], SOCK_CLOEXEC|SOCK_NONBLOCK) = 6epoll_ctl(4, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=1969376752, u64=140245536493040}}) = 0getsockname(6, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0setsockopt(6, SOL_TCP, TCP_NODELAY, [1], 4) = 0setsockopt(6, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0setsockopt(6, SOL_TCP, TCP_KEEPINTVL, [180], 4) = 0setsockopt(6, SOL_TCP, TCP_KEEPIDLE, [180], 4) = 0accept4(3, 0xc42004db78, 0xc42004db6c, SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)read(6, "GET /test?kjhkjhkjh HTTP/1.1\r\nHo"..., 4096) = 92write(6, "HTTP/1.1 200 OK\r\nDate: Sat, 03 J"..., 139) = 139read(6, "", 4096)

當使用1個worker,epoll_wait之後只有一次futex喚醒,並沒有error read。然而我發現並不總是這樣, 有時候我依然可以得到read error和兩次futex 喚醒。

And then what to do?

在Marek的文章中他談到Linux 4.5之後可以使用EPOLLEXCLUSIVE。我的Linux版本是4.8,為什麼問題還是出現?或許Go並沒有使用這個標誌,我希望將來的版本可以使用這個標誌。

從中我學到了很多知識,希望你也是。

[0] https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
[1] https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/
[2] https://gist.github.com/wejick/2cef1f8799361318a62a59f6801eade8

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.