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/rs/zerolog"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"rua.plus/lolly/internal/config"
|
"rua.plus/lolly/internal/config"
|
||||||
|
"rua.plus/lolly/internal/netutil"
|
||||||
"rua.plus/lolly/internal/variable"
|
"rua.plus/lolly/internal/variable"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -142,7 +143,7 @@ func (l *Logger) LogAccess(ctx *fasthttp.RequestCtx, status int, size int64, dur
|
|||||||
// JSON 格式或空格式:输出结构化 JSON
|
// JSON 格式或空格式:输出结构化 JSON
|
||||||
if l.accessFormat == formatJSON || l.accessFormat == "" {
|
if l.accessFormat == formatJSON || l.accessFormat == "" {
|
||||||
l.accessLog.Info().
|
l.accessLog.Info().
|
||||||
Str("remote_addr", ctx.RemoteAddr().String()).
|
Str("remote_addr", netutil.FormatRemoteAddr(ctx)).
|
||||||
Bytes("request", append(append(ctx.Method(), ' '), ctx.Path()...)).
|
Bytes("request", append(append(ctx.Method(), ' '), ctx.Path()...)).
|
||||||
Int("status", status).
|
Int("status", status).
|
||||||
Int64("body_bytes_sent", size).
|
Int64("body_bytes_sent", size).
|
||||||
|
|||||||
@ -9,6 +9,7 @@ package netutil
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
@ -110,3 +111,87 @@ func GetRemoteAddrIP(ctx *fasthttp.RequestCtx) net.IP {
|
|||||||
}
|
}
|
||||||
return nil
|
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/google/uuid"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
"rua.plus/lolly/internal/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 内置变量常量
|
// 内置变量常量
|
||||||
@ -81,11 +82,7 @@ func init() {
|
|||||||
Name: VarRemoteAddr,
|
Name: VarRemoteAddr,
|
||||||
Description: "客户端 IP 地址",
|
Description: "客户端 IP 地址",
|
||||||
Getter: func(ctx *fasthttp.RequestCtx) string {
|
Getter: func(ctx *fasthttp.RequestCtx) string {
|
||||||
addr := ctx.RemoteAddr()
|
return netutil.FormatRemoteAddr(ctx)
|
||||||
if addr == nil {
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
return addr.String()
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -98,8 +95,8 @@ func init() {
|
|||||||
if addr == nil {
|
if addr == nil {
|
||||||
return "-"
|
return "-"
|
||||||
}
|
}
|
||||||
// 解析地址获取端口
|
// 解析地址获取端口,优先用 FormatRemoteAddr 缓存的结果
|
||||||
s := addr.String()
|
s := netutil.FormatRemoteAddr(ctx)
|
||||||
for i := len(s) - 1; i >= 0; i-- {
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
if s[i] == ':' {
|
if s[i] == ':' {
|
||||||
return s[i+1:]
|
return s[i+1:]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user