perf: sync dispatch hot path optimizations

This commit is contained in:
A 2026-07-06 14:27:03 +08:00
parent 03b785ebf4
commit 7e64d9c30e
13 changed files with 824 additions and 215 deletions

View file

@ -2,6 +2,7 @@ package mtprotoedge
import (
"container/list"
"encoding/binary"
"sync"
"time"
)
@ -10,6 +11,10 @@ const (
rpcResultCacheTTL = 3 * time.Minute
rpcResultCacheMaxEntries = 4096
rpcResultCacheMaxBytes = 64 << 20
// rpcResultCacheShards 把缓存按 (auth_key_id, session_id) 分片:每条 RPC 都要
// Get(重复检测)+ Put(结果缓存),单把全局锁会让所有连接的 RPC 热路径在
// 一个 mutex 上汇聚(同 P0-5 的 SessionManager 教训)。分片数为 2 的幂。
rpcResultCacheShards = 16
)
type rpcResultCacheKey struct {
@ -25,7 +30,14 @@ type rpcResultCacheEntry struct {
expiresAt time.Time
}
// rpcResultCache 缓存已回发的 rpc_result(按 auth_key+session+req_msg_id),用于
// 跨连接重放重复请求。encodedOutboundMessage 构造后不可变(push fan-out 与 pending
// resend 均依赖该契约),因此 Get/Put 直接共享指针,不做防御性拷贝。
type rpcResultCache struct {
shards [rpcResultCacheShards]rpcResultCacheShard
}
type rpcResultCacheShard struct {
mu sync.Mutex
now func() time.Time
ttl time.Duration
@ -40,14 +52,24 @@ func newRPCResultCache(now func() time.Time) *rpcResultCache {
if now == nil {
now = time.Now
}
return &rpcResultCache{
now: now,
ttl: rpcResultCacheTTL,
maxEntries: rpcResultCacheMaxEntries,
maxBytes: rpcResultCacheMaxBytes,
order: list.New(),
byKey: make(map[rpcResultCacheKey]*list.Element),
c := &rpcResultCache{}
for i := range c.shards {
s := &c.shards[i]
s.now = now
s.ttl = rpcResultCacheTTL
s.maxEntries = rpcResultCacheMaxEntries / rpcResultCacheShards
s.maxBytes = rpcResultCacheMaxBytes / rpcResultCacheShards
s.order = list.New()
s.byKey = make(map[rpcResultCacheKey]*list.Element)
}
return c
}
func (c *rpcResultCache) shard(key rpcResultCacheKey) *rpcResultCacheShard {
// auth_key_id 与 session_id 都是均匀随机的 64-bit 值,异或折叠后取低位即可。
h := binary.LittleEndian.Uint64(key.authKeyID[:]) ^ uint64(key.sessionID)
h ^= h >> 32
return &c.shards[h&(rpcResultCacheShards-1)]
}
func (c *rpcResultCache) Get(authKeyID [8]byte, sessionID, reqMsgID int64) (*encodedOutboundMessage, bool) {
@ -55,102 +77,87 @@ func (c *rpcResultCache) Get(authKeyID [8]byte, sessionID, reqMsgID int64) (*enc
return nil, false
}
key := rpcResultCacheKey{authKeyID: authKeyID, sessionID: sessionID, reqMsgID: reqMsgID}
now := c.now()
s := c.shard(key)
now := s.now()
c.mu.Lock()
defer c.mu.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
elem, ok := c.byKey[key]
elem, ok := s.byKey[key]
if !ok {
return nil, false
}
entry := elem.Value.(*rpcResultCacheEntry)
if !entry.expiresAt.After(now) {
c.removeElement(elem)
s.removeElement(elem)
return nil, false
}
return cloneEncodedOutboundMessage(entry.encoded), true
return entry.encoded, true
}
func (c *rpcResultCache) Put(authKeyID [8]byte, sessionID, reqMsgID int64, encoded *encodedOutboundMessage) {
if c == nil || reqMsgID == 0 || encoded == nil {
return
}
copied := cloneEncodedOutboundMessage(encoded)
if copied == nil {
return
}
size := len(copied.body)
if c.maxBytes > 0 && size > c.maxBytes {
return
}
key := rpcResultCacheKey{authKeyID: authKeyID, sessionID: sessionID, reqMsgID: reqMsgID}
now := c.now()
s := c.shard(key)
size := len(encoded.body)
if s.maxBytes > 0 && size > s.maxBytes {
return
}
now := s.now()
c.mu.Lock()
defer c.mu.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
c.expireLocked(now)
if elem, ok := c.byKey[key]; ok {
c.removeElement(elem)
s.expireLocked(now)
if elem, ok := s.byKey[key]; ok {
s.removeElement(elem)
}
entry := &rpcResultCacheEntry{
key: key,
encoded: copied,
encoded: encoded,
size: size,
expiresAt: now.Add(c.ttl),
expiresAt: now.Add(s.ttl),
}
elem := c.order.PushBack(entry)
c.byKey[key] = elem
c.bytes += size
c.trimLocked()
elem := s.order.PushBack(entry)
s.byKey[key] = elem
s.bytes += size
s.trimLocked()
}
func (c *rpcResultCache) expireLocked(now time.Time) {
for elem := c.order.Front(); elem != nil; {
func (s *rpcResultCacheShard) expireLocked(now time.Time) {
for elem := s.order.Front(); elem != nil; {
next := elem.Next()
entry := elem.Value.(*rpcResultCacheEntry)
if entry.expiresAt.After(now) {
return
}
c.removeElement(elem)
s.removeElement(elem)
elem = next
}
}
func (c *rpcResultCache) trimLocked() {
for c.order.Len() > 0 {
tooManyEntries := c.maxEntries > 0 && c.order.Len() > c.maxEntries
tooManyBytes := c.maxBytes > 0 && c.bytes > c.maxBytes
func (s *rpcResultCacheShard) trimLocked() {
for s.order.Len() > 0 {
tooManyEntries := s.maxEntries > 0 && s.order.Len() > s.maxEntries
tooManyBytes := s.maxBytes > 0 && s.bytes > s.maxBytes
if !tooManyEntries && !tooManyBytes {
return
}
c.removeElement(c.order.Front())
s.removeElement(s.order.Front())
}
}
func (c *rpcResultCache) removeElement(elem *list.Element) {
func (s *rpcResultCacheShard) removeElement(elem *list.Element) {
if elem == nil {
return
}
entry := elem.Value.(*rpcResultCacheEntry)
delete(c.byKey, entry.key)
c.bytes -= entry.size
if c.bytes < 0 {
c.bytes = 0
}
c.order.Remove(elem)
}
func cloneEncodedOutboundMessage(src *encodedOutboundMessage) *encodedOutboundMessage {
if src == nil {
return nil
}
body := append([]byte(nil), src.body...)
return &encodedOutboundMessage{
body: body,
typeID: src.typeID,
reqMsgID: src.reqMsgID,
delete(s.byKey, entry.key)
s.bytes -= entry.size
if s.bytes < 0 {
s.bytes = 0
}
s.order.Remove(elem)
}