Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use Go to build and develop high-load WebSocket server

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces "how to use Go to build and develop high-load WebSocket server". In daily operation, I believe many people have doubts about how to use Go to build and develop high-load WebSocket server. Xiaobian consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful to answer the doubts about "how to use Go to build and develop high-load WebSocket server". Next, please follow the editor to study!

Mode of realization

Let's take a look at how to use the Go function to implement parts of the server without any optimization.

In the process of net/http, let's talk about how we send and receive data. Data standing on top of WebSocket protocols (such as JSON objects) will be referred to as grouping below.

We begin to implement a Channel structure that includes sending and receiving these packets over a WebSocket connection.

Channel structure

/ / Packet represents application level data. Type Packet struct {...} / / Channel wraps user connection. Type Channel struct {conn net.Conn / / WebSocket connection. Send chan Packet / / Outgoing packets queue. } func NewChannel (conn net.Conn) * Channel {c: = & Channel {conn: conn, send: make (chan Packet, N),} go c.reader () go c.writer () return c}

Notice that there are reader and writer with a goroutines. Each goroutine requires its own memory stack, which may have an initial size of 2 to 8 KB, depending on the operating system and Go version.

With 3 million online connections, we will need 24 GB of memory (4 KB stack) to maintain all connections. This does not calculate the memory allocated for the Channel structure, outgoing packet ch.send, and other internal fields.

I/O goroutines

Let's look at the implementation of "reader":

Func (c * Channel) reader () {/ / We make a buffered read to reduce read syscalls. Buf: = bufio.NewReader (c.conn) for {pkt, _: = readPacket (buf) c.handle (pkt)}}

Here we use bufio.Reader to reduce the number of read () syscalls and read the same number as the buf buffer size. In the * cycle, we look forward to the arrival of new data. Remember: new data is expected to come. We'll be back later.

We will leave the parsing and processing of incoming packets because it is not important to the optimization we will discuss. However, buf now deserves our attention: by default, it is 4 KB, which means we need an additional 12 GB of memory. "writer" has a similar situation:

Func (c * Channel) writer () {/ / We make buffered write to reduce write syscalls. Buf: = bufio.NewWriter (c.conn) for pkt: = range c.send {_: = writePacket (buf, pkt) buf.Flush ()}}

We iterate through the c.send and write them to the buffer. As the careful reader has guessed, our 3 million connections will consume another 12 GB of memory.

HTTP

We already have a simple Channel implementation, and now we need a WebSocket connection to use it.

Note: if you don't know how WebSocket works. The client switches to the WebSocket protocol through a special HTTP mechanism called upgrade. After successfully processing the upgrade request, the server and client exchange binary WebSocket frames using TCP connections. This is a description of the frame structure in the connection.

Import ("net/http"some/websocket") http.HandleFunc ("/ v1/ws", func (w http.ResponseWriter, r * http.Request) {conn, _: = websocket.Upgrade (r, w) ch: = NewChannel (conn) / /.})

Note that http.ResponseWriter allocates memory for bufio.Reader and bufio.Writer (using a 4 KB buffer) for * http.Request initialization and further response writing.

No matter what WebSocket library is used, after successfully responding to the upgrade request, the server receives the IWebSocket O buffer along with the TCP connection after the responseWriter.Hijack () call.

Tip: in some cases, go:linkname can be used to return the buffer to sync.Pool within net/http by calling net/http.putBufio {Reader,Writer}.

Therefore, we need another 24 GB of memory to maintain 3 million links.

So, our program needs 72 gigabytes of memory even if it doesn't do anything.

Optimize

Let's review what we talked about in the introduction section and remember the behavior of the user connection. After switching to WebSocket, the client sends a packet containing related events, in other words, subscribing to events. Then (regardless of technical information such as ping/pong), the client may not send any other information throughout the connection life.

The life of the connection may be a few seconds to a few days.

So most of the time, our Channel.reader () and Channel.writer () are waiting to receive or send data for processing. Each has a 4 KB Istroke O buffer.

Now it's obvious that something can be done better, isn't it?

Netpoll

You remember that inside bufio.Reader.Read (), Channel.reader () implements that conn.read () is locked when there is no new data. If there is data in the connection, the Go runtime "wakes up" our goroutine and allows it to read the next packet. After that, goroutine locks up again, expecting new data. Let's see how the Go runtime understands that goroutine must be "woken up". If we look at the conn.Read () implementation, we will see the net.netFD.Read () call in it:

/ / net/fd_unix.go func (fd * netFD) Read (p [] byte) (n int, err error) {/ /. For {n, err = syscall.Read (fd.sysfd, p) if err! = nil {n = 0 if err = = syscall.EAGAIN {if err = fd.pd.waitRead (); err = = nil {continue} / /. Break} / /...}

Go uses sockets in non-blocking mode. EAGAIN says that there is no data in the socket and will not be locked when reading from an empty socket, and the operating system will return control to us.

We see a read () system call from the connection file descriptor. If the read returns an EAGAIN error, the runtime causes pollDesc.waitRead () to call:

/ / net/fd_poll_runtime.go func (pd * pollDesc) waitRead () error {return pd.wait ('r')} func (pd * pollDesc) wait (mode int) error {res: = runtime_pollWait (pd.runtimeCtx, mode) / /.}

If we dig deeper, we will see that netpoll is implemented using epoll in Linux and kqueue in BSD. Why not use the same method to connect? We can allocate a read buffer and use goroutine only when it is really necessary: when there is real readable data in the socket.

There is a problem exporting the netpoll function on github.com/golang/go.

Get rid of goroutines

Suppose we have a netpoll implementation of Go. Now we can avoid using the internal buffer to start Channel.reader () goroutine and subscribe to the event of readable data in the connection:

Ch: = NewChannel (conn) / / Make conn to be observed by netpoll instance. Poller.Start (conn, netpoll.EventRead, func () {/ / We spawn goroutine here to prevent poller wait loop / / to become locked during receiving packet from ch. Go Receive (ch)}) / / Receive reads a packet from conn and handles it somehow. Func (ch * Channel) Receive () {buf: = bufio.NewReader (ch.conn) pkt: = readPacket (buf) c.handle (pkt)}

It is easier to use Channel.writer () because only when we want to send packets can we run goroutine and allocate buffers:

Func (ch * Channel) Send (p Packet) {if c.noWriterYet () {go ch.writer ()} ch.send

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 253

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report