You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高并发场景下使用json.Decoder读取请求体后如何实现请求体重复读取?

解决高QPS应用中请求体回写的问题

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 obj has 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 04:27:30