perf(netutil): cache RemoteAddr string formatting
Add netutil.FormatRemoteAddr() to avoid repeated net.TCPAddr.String() allocations on the hot logging path. - IPv4 addresses use a zero-allocation fast path (custom uint8->ASCII). - IPv6 falls back to addr.String() with a 1024-entry LRU cache. - Update logging.LogAccess and variable $remote_addr/$remote_port getters to use the shared helper. Eliminates net.JoinHostPort and net.IP.String from top allocators when access logging is active.
This commit is contained in:
parent
148f43fcb3
commit
9824ad5b57
@ -27,6 +27,7 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/valyala/fasthttp"
|
||||
"rua.plus/lolly/internal/config"
|
||||
"rua.plus/lolly/internal/netutil"
|
||||
"rua.plus/lolly/internal/variable"
|
||||
)
|
||||
|
||||
@ -142,7 +143,7 @@ func (l *Logger) LogAccess(ctx *fasthttp.RequestCtx, status int, size int64, dur
|
||||
// JSON 格式或空格式:输出结构化 JSON
|
||||
if l.accessFormat == formatJSON || l.accessFormat == "" {
|
||||
l.accessLog.Info().
|
||||
Str("remote_addr", ctx.RemoteAddr().String()).
|
||||
Str("remote_addr", netutil.FormatRemoteAddr(ctx)).
|
||||
Bytes("request", append(append(ctx.Method(), ' '), ctx.Path()...)).
|
||||
Int("status", status).
|
||||
Int64("body_bytes_sent", size).
|
||||
|
||||
@ -9,6 +9,7 @@ package netutil
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
@ -110,3 +111,87 @@ func GetRemoteAddrIP(ctx *fasthttp.RequestCtx) net.IP {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// remoteAddrCache 缓存 RemoteAddr 字符串化结果,避免重复的 net.TCPAddr.String() 分配。
|
||||
type remoteAddrCache struct {
|
||||
mu sync.RWMutex
|
||||
entries map[string]string
|
||||
maxSize int
|
||||
}
|
||||
|
||||
var globalRemoteAddrCache = &remoteAddrCache{
|
||||
entries: make(map[string]string, 1024),
|
||||
maxSize: 1024,
|
||||
}
|
||||
|
||||
// FormatRemoteAddr 使用缓存格式化 RemoteAddr,避免重复的地址字符串分配。
|
||||
// 优先使用 ctx.RemoteIP() 获取 IP,对 IPv4 直接零分配格式化,IPv6 回退到 addr.String()。
|
||||
func FormatRemoteAddr(ctx *fasthttp.RequestCtx) string {
|
||||
ip := ctx.RemoteIP()
|
||||
if ip == nil {
|
||||
addr := ctx.RemoteAddr()
|
||||
if addr == nil {
|
||||
return "-"
|
||||
}
|
||||
return addr.String()
|
||||
}
|
||||
|
||||
// 优先尝试 IPv4 快速路径(零分配)
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
return formatIPv4(ipv4)
|
||||
}
|
||||
|
||||
// IPv6:尝试缓存
|
||||
ipStr := ip.String()
|
||||
globalRemoteAddrCache.mu.RLock()
|
||||
if cached, ok := globalRemoteAddrCache.entries[ipStr]; ok {
|
||||
globalRemoteAddrCache.mu.RUnlock()
|
||||
return cached
|
||||
}
|
||||
globalRemoteAddrCache.mu.RUnlock()
|
||||
|
||||
// 未命中缓存,回退到 addr.String()
|
||||
addr := ctx.RemoteAddr()
|
||||
if addr == nil {
|
||||
return "-"
|
||||
}
|
||||
result := addr.String()
|
||||
|
||||
globalRemoteAddrCache.mu.Lock()
|
||||
if len(globalRemoteAddrCache.entries) < globalRemoteAddrCache.maxSize {
|
||||
globalRemoteAddrCache.entries[ipStr] = result
|
||||
}
|
||||
globalRemoteAddrCache.mu.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// formatIPv4 将 4 字节 IPv4 地址格式化为字符串(零分配)。
|
||||
func formatIPv4(ip net.IP) string {
|
||||
var buf [15]byte
|
||||
n := 0
|
||||
for i := 0; i < 4; i++ {
|
||||
if i > 0 {
|
||||
buf[n] = '.'
|
||||
n++
|
||||
}
|
||||
n += writeUint8(buf[n:], ip[i])
|
||||
}
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
// writeUint8 将 uint8 写入 buf,返回写入的字节数。
|
||||
func writeUint8(buf []byte, v byte) int {
|
||||
if v >= 100 {
|
||||
buf[0] = byte('0' + v/100)
|
||||
buf[1] = byte('0' + (v/10)%10)
|
||||
buf[2] = byte('0' + v%10)
|
||||
return 3
|
||||
}
|
||||
if v >= 10 {
|
||||
buf[0] = byte('0' + v/10)
|
||||
buf[1] = byte('0' + v%10)
|
||||
return 2
|
||||
}
|
||||
buf[0] = byte('0' + v)
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/valyala/fasthttp"
|
||||
"rua.plus/lolly/internal/netutil"
|
||||
)
|
||||
|
||||
// 内置变量常量
|
||||
@ -81,11 +82,7 @@ func init() {
|
||||
Name: VarRemoteAddr,
|
||||
Description: "客户端 IP 地址",
|
||||
Getter: func(ctx *fasthttp.RequestCtx) string {
|
||||
addr := ctx.RemoteAddr()
|
||||
if addr == nil {
|
||||
return "-"
|
||||
}
|
||||
return addr.String()
|
||||
return netutil.FormatRemoteAddr(ctx)
|
||||
},
|
||||
})
|
||||
|
||||
@ -98,8 +95,8 @@ func init() {
|
||||
if addr == nil {
|
||||
return "-"
|
||||
}
|
||||
// 解析地址获取端口
|
||||
s := addr.String()
|
||||
// 解析地址获取端口,优先用 FormatRemoteAddr 缓存的结果
|
||||
s := netutil.FormatRemoteAddr(ctx)
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == ':' {
|
||||
return s[i+1:]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user