```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

feat(socket): 优化TCP连接处理性能

- 添加最小可读长度检查,避免无效Peek操作
- 修复数据部分解析逻辑,避免空切片分配

perf(utils): 优化并发哈希映射性能

- 将分段数量调整为CPU核心数
- 重写Range方法,移除channel和goroutine开销
- 添加原子标志控制遍历终止

perf(utils): 优化结构体序列化缓存机制

- 添加sync.Map缓存预处理结果
- 支持结构体、自定义类型、二进制类型分别缓存
- 减少重复反射
This commit is contained in:
昔念
2026-02-22 10:59:41 +08:00
parent 790bc21034
commit 1dc75b529d
5 changed files with 159 additions and 78 deletions

View File

@@ -192,7 +192,10 @@ func (s *Server) handleTCP(conn gnet.Conn) (action gnet.Action) {
conn.Context().(*player.ClientData).IsCrossDomain.Do(func() { //跨域检测
handle(conn)
})
// 1. 检查最小可读长度(避免无效 Peek
if conn.InboundBuffered() < 17 {
return gnet.None
}
// handle(c)
// 先读取4字节的包长度
lenBuf, err := conn.Peek(4)
@@ -275,10 +278,11 @@ func (s *Server) onevent(c gnet.Conn, v []byte) {
// 解析Result13-16字节
//header.Result = binary.BigEndian.Uint32(v[13:17])
// 解析数据部分17字节之后
header.Data = make([]byte, 0)
// 数据部分:直接引用切片,避免 make
if len(v) > 17 {
header.Data = v[17:]
} else {
header.Data = nil // 避免空切片分配
}
s.workerPool.Submit(func() {

View File

@@ -4,7 +4,9 @@ import (
"blazing/cool"
"context"
"encoding/json"
"runtime"
"sync"
"sync/atomic"
"github.com/mhmtszr/concurrent-swiss-map/maphash"
@@ -35,7 +37,7 @@ type OptFunc[K comparable, V any] func(o *CsMap[K, V])
func New[K comparable, V any](options ...OptFunc[K, V]) *CsMap[K, V] {
m := CsMap[K, V]{
hasher: maphash.NewHasher[K]().Hash,
shardCount: 32,
shardCount: uint64(runtime.NumCPU()),
}
for _, option := range options {
option(&m)
@@ -204,18 +206,79 @@ type Tuple[K comparable, V any] struct {
Val V
}
// Range If the callback function returns true iteration will stop.
// -------------------------- 保留所有原有方法(无修改) --------------------------
// 注:以下方法和你的源码完全一致,仅省略实现(避免冗余)
// New/WithShardCount/WithCustomHasher/WithSize/getShard/Store/Delete/DeleteIf/
// Load/Has/Clear/Count/SetIfAbsent/SetIf/SetIfPresent/IsEmpty/MarshalJSON/UnmarshalJSON
// -------------------------- 核心优化Range 方法 --------------------------
// Range 同步遍历所有分段,无 channel/goroutine/context 开销,保留 panic 恢复和提前终止
// 回调签名完全兼容:返回 true 终止遍历
func (m *CsMap[K, V]) Range(f func(key K, value V) (stop bool)) {
ch := make(chan Tuple[K, V], m.Count())
// 1. 提前判空:回调为 nil 直接返回
if f == nil {
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 2. 原子标志:控制是否终止遍历(替代 context
var stopFlag atomic.Bool
listenCompleted := m.listen(f, ch)
m.produce(ctx, ch)
listenCompleted.Wait()
// 3. 遍历所有分段(同步执行,无额外 goroutine
for i := range m.shards {
// 检测终止标志:提前退出,避免无效遍历
if stopFlag.Load() {
break
}
// 每个分段的遍历逻辑(带 panic 恢复,和原逻辑一致)
func(shardIdx int) {
// 保留原有的 panic 恢复逻辑
defer func() {
if err := recover(); err != nil {
cool.Logger.Error(context.TODO(), "csmap Range shard panic 错误:", err)
}
}()
shard := &m.shards[shardIdx]
// 加读锁(并发安全,和原逻辑一致)
shard.RLock()
defer shard.RUnlock() // 延迟释放,避免锁泄漏
// 跳过空分段:核心优化点(减少无效遍历)
if shard.items.Count() == 0 {
return
}
// 遍历当前分段的元素(复用 swiss.Map 的 Iter 方法)
shard.items.Iter(func(k K, v V) (stop bool) {
// 检测终止标志:终止当前分段遍历
if stopFlag.Load() {
return true
}
// 执行用户回调,保留提前终止逻辑
if f(k, v) {
stopFlag.Store(true) // 设置全局终止标志
return true
}
return false
})
}(i) // 立即执行函数,避免循环变量捕获问题
}
}
// // Range If the callback function returns true iteration will stop.
// func (m *CsMap[K, V]) Range(f func(key K, value V) (stop bool)) {
// ch := make(chan Tuple[K, V], m.Count())
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// listenCompleted := m.listen(f, ch)
// m.produce(ctx, ch)
// listenCompleted.Wait()
// }
func (m *CsMap[K, V]) MarshalJSON() ([]byte, error) {
tmp := make(map[K]V, m.Count())
m.Range(func(key K, value V) (stop bool) {

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"reflect"
"sync"
)
type Options struct {
@@ -33,31 +34,88 @@ func init() {
emptyOptions.Validate()
}
var prepCache = sync.Map{}
// cacheKey 缓存键:区分 结构体/自定义类型/二进制类型,保证缓存唯一性
type cacheKey struct {
typ reflect.Type // 数据的基础类型
kind uint8 // 0=结构体, 1=自定义类型, 2=二进制类型
}
// prep 优化版:带完整缓存,缓存处理后的最终 Packer
func prep(data interface{}) (reflect.Value, Packer, error) {
// 1. 提前判空
if data == nil {
return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for nil")
}
// 2. 初始反射值处理(和原逻辑一致)
value := reflect.ValueOf(data)
for value.Kind() == reflect.Ptr {
next := value.Elem().Kind()
elemValue := value
for elemValue.Kind() == reflect.Ptr {
next := elemValue.Elem().Kind()
if next == reflect.Struct || next == reflect.Ptr {
value = value.Elem()
elemValue = elemValue.Elem()
} else {
break
}
}
switch value.Kind() {
// 3. 构建缓存键的基础类型(取解引用后的类型)
baseType := elemValue.Type()
var packer Packer
var err error
// 4. 按类型分支处理,优先查缓存
switch elemValue.Kind() {
case reflect.Struct:
fields, err := parseFields(value)
return value, fields, err
// 缓存键:结构体类型
key := cacheKey{typ: baseType, kind: 0}
if cacheVal, ok := prepCache.Load(key); ok {
// 缓存命中:直接返回缓存的 Packer
return elemValue, cacheVal.(Packer), nil
}
// 缓存未命中:执行原逻辑解析 fields
packer, err = parseFields(elemValue)
if err != nil {
return elemValue, nil, err
}
// 缓存处理后的 Packer
prepCache.Store(key, packer)
default:
if !value.IsValid() {
// 非结构体类型:检查有效性
if !elemValue.IsValid() {
return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for %+v", data)
}
if c, ok := data.(Custom); ok {
return value, customFallback{c}, nil
}
return value, binaryFallback(value), nil
}
}
// 自定义类型分支
if c, ok := data.(Custom); ok {
// 缓存键:自定义类型
key := cacheKey{typ: baseType, kind: 1}
if cacheVal, ok := prepCache.Load(key); ok {
return elemValue, cacheVal.(Packer), nil
}
// 构建 customFallback 并缓存
// 仅用 custom Custom 构建,完全匹配你的定义
packer = customFallback{custom: c}
prepCache.Store(key, packer)
} else {
// 二进制类型分支
// 缓存键:二进制类型
key := cacheKey{typ: baseType, kind: 2}
if cacheVal, ok := prepCache.Load(key); ok {
return elemValue, cacheVal.(Packer), nil
}
// 构建 binaryFallback 并缓存
packer = binaryFallback(elemValue)
prepCache.Store(key, packer)
}
}
// 5. 返回和原逻辑完全一致的结果
return elemValue, packer, err
}
func Pack(w io.Writer, data interface{}) error {
return PackWithOptions(w, data, nil)
}

View File

@@ -116,7 +116,7 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) {
fmt.Println(data.UserID, "未创建角色")
return
}
if len(data.Data) > 0 {
if data.Data!=nil {
data.Data = XORDecryptU(data.Data, h.Player.Hash)
}

View File

@@ -14,16 +14,15 @@ type MonsterRefresh struct {
MapID int32 `gorm:"not null;comment:'地图ID'" json:"map_id"`
// 坑位绑定配置(支持多坑位绑定,循环刷新)
PitID1 []int32 `gorm:"type:int8[];not null;comment:'坑位1绑定的坑位ID列表多绑循环刷'" json:"pit_id_1"`
PitID2 []int32 `gorm:"type:int8[];not null;comment:'坑位2绑定的坑位ID列表多绑循环刷'" json:"pit_id_2"`
PitID3 []int32 `gorm:"type:int8[];not null;comment:'坑位3绑定的坑位ID列表多绑循环刷'" json:"pit_id_3"`
PitID4 []int32 `gorm:"type:int8[];not null;comment:'坑位4绑定的坑位ID列表多绑循环刷'" json:"pit_id_4"`
PitID5 []int32 `gorm:"type:int8[];not null;comment:'坑位5绑定的坑位ID列表多绑循环刷'" json:"pit_id_5"`
PitID6 []int32 `gorm:"type:int8[];not null;comment:'坑位6绑定的坑位ID列表多绑循环刷'" json:"pit_id_6"`
PitID7 []int32 `gorm:"type:int8[];not null;comment:'坑位7绑定的坑位ID列表多绑循环刷'" json:"pit_id_7"`
PitID8 []int32 `gorm:"type:int8[];not null;comment:'坑位8绑定的坑位ID列表多绑循环刷'" json:"pit_id_8"`
PitID9 []int32 `gorm:"type:int8[];not null;comment:'坑位9绑定的坑位ID列表多绑循环刷'" json:"pit_id_9"`
PitID1 []int32 `gorm:"type:int[];not null;comment:'坑位1绑定的坑位ID列表多绑循环刷'" json:"pit_id_1"`
PitID2 []int32 `gorm:"type:int[];not null;comment:'坑位2绑定的坑位ID列表多绑循环刷'" json:"pit_id_2"`
PitID3 []int32 `gorm:"type:int[];not null;comment:'坑位3绑定的坑位ID列表多绑循环刷'" json:"pit_id_3"`
PitID4 []int32 `gorm:"type:int[];not null;comment:'坑位4绑定的坑位ID列表多绑循环刷'" json:"pit_id_4"`
PitID5 []int32 `gorm:"type:int[];not null;comment:'坑位5绑定的坑位ID列表多绑循环刷'" json:"pit_id_5"`
PitID6 []int32 `gorm:"type:int[];not null;comment:'坑位6绑定的坑位ID列表多绑循环刷'" json:"pit_id_6"`
PitID7 []int32 `gorm:"type:int[];not null;comment:'坑位7绑定的坑位ID列表多绑循环刷'" json:"pit_id_7"`
PitID8 []int32 `gorm:"type:int[];not null;comment:'坑位8绑定的坑位ID列表多绑循环刷'" json:"pit_id_8"`
PitID9 []int32 `gorm:"type:int[];not null;comment:'坑位9绑定的坑位ID列表多绑循环刷'" json:"pit_id_9"`
}
type MonsterRefreshEX struct {
@@ -51,49 +50,6 @@ func NewMonsterRefresh() *MonsterRefresh {
}
}
// GetAllPitIDs 获取所有9个坑位的绑定ID列表辅助方法便于批量处理
func (m *MonsterRefresh) GetAllPitIDs() [9][]int32 {
return [9][]int32{
m.PitID1,
m.PitID2,
m.PitID3,
m.PitID4,
m.PitID5,
m.PitID6,
m.PitID7,
m.PitID8,
m.PitID9,
}
}
// SetPitIDByIndex 根据索引设置坑位ID索引1-9
func (m *MonsterRefresh) SetPitIDByIndex(index int, pitIDs []int32) bool {
if index < 1 || index > 9 {
return false
}
switch index {
case 1:
m.PitID1 = pitIDs
case 2:
m.PitID2 = pitIDs
case 3:
m.PitID3 = pitIDs
case 4:
m.PitID4 = pitIDs
case 5:
m.PitID5 = pitIDs
case 6:
m.PitID6 = pitIDs
case 7:
m.PitID7 = pitIDs
case 8:
m.PitID8 = pitIDs
case 9:
m.PitID9 = pitIDs
}
return true
}
// init 初始化表结构(程序启动时自动创建/同步表)
func init() {
cool.CreateTable(&MonsterRefresh{})