高并发场景下使用json.Decoder读取请求体后如何实现请求体重复读取?
Hey there! Let's work through this problem for your high-throughput app handling 4-5 million requests per second. First, let's clear up why your initial encoder approach didn't work, then dive into efficient solutions that play nice with both high QPS and long requests.
Why your encoder idea failed
The line c.Request.Body = json.NewEncoder(b).Encode(obj) doesn't work because Encode(obj) returns an error, not an io.ReadCloser (which is what Request.Body expects). Plus, re-encoding the decoded obj is a bad idea for two big reasons:
- The re-encoded JSON might not match the original request body (think field order, whitespace, escape differences)
- You could lose data if
objhas unexported fields or custom JSON handling that doesn't round-trip perfectly
The right approach: Capture the original body while decoding
Instead of re-encoding, we need to save the original request body bytes as we read them for decoding. The io.TeeReader is perfect for this—it reads from the original reader and writes every byte to a writer at the same time, so we don't have to read the body twice.
Basic implementation
import ( "bytes" "io" "encoding/json" ) // Inside your request handler bodyBuf := bytes.NewBuffer(nil) // Use TeeReader to copy bytes to bodyBuf as we decode teeReader := io.TeeReader(c.Request.Body, bodyBuf) // Decode from the teeReader instead of the original body err := json.NewDecoder(teeReader).Decode(&obj) if err != nil { return err } // Reset the buffer's read position to the start if _, err := bodyBuf.Seek(0, io.SeekStart); err != nil { return err } // Replace the request body with a re-readable version c.Request.Body = io.NopCloser(bodyBuf)
Optimize for high QPS and long requests
For your 4-5M RPS workload, creating a new bytes.Buffer per request can add unnecessary GC pressure. Use a sync.Pool to reuse buffers and reduce memory allocations:
import ( "bytes" "io" "encoding/json" "sync" ) // Create a pool of buffers (tune the initial capacity to match your typical request size) var bodyBufPool = sync.Pool{ New: func() interface{} { return bytes.NewBuffer(make([]byte, 0, 4096)) // 4KB starting capacity }, } // Inside your request handler bodyBuf := bodyBufPool.Get().(*bytes.Buffer) bodyBuf.Reset() // Clear any leftover data from previous use defer bodyBufPool.Put(bodyBuf) // Return the buffer to the pool when done teeReader := io.TeeReader(c.Request.Body, bodyBuf) err := json.NewDecoder(teeReader).Decode(&obj) if err != nil { return err } if _, err := bodyBuf.Seek(0, io.SeekStart); err != nil { return err } c.Request.Body = io.NopCloser(bodyBuf)
This way, we reuse buffers instead of allocating new ones for every request, which keeps your app efficient even under extreme load. For extra-long requests, you can adjust the initial buffer capacity to match your average request size—this reduces the number of buffer resizes needed.
Why not use ioutil (or io.ReadAll)?
You mentioned avoiding ioutil (now deprecated in favor of io.ReadAll) because of long request management. That's smart—reading the entire body into memory upfront can lead to high memory usage if you have large requests. The TeeReader approach reads the body incrementally as part of the decoding process, so it doesn't hold the entire body in memory any longer than necessary.
内容的提问来源于stack exchange,提问作者mythic

