内存泄漏排查
背景了解:告知 线上 room_work 运行一段时间内存就会慢慢往上涨,8G内存吃掉了4G。。。
思路
- 大概捋一下项目中有通过常驻内存操作实现业务逻辑的代码
1. room_work 这是个根据rid进行转发到不同node的,深度使用内存存储rid的各种业务数据
2. 道具方面,干冰跟骰子移入了room_work后,也是使用自身定义的内存对象承载的业务
......
cpu火焰图看看
直接本地环境全部启动之后,开始使用三个手机进入dj房间,进行所有功能疯狂乱点,生成cup火焰图,但讲真看不出来啥,才发现应该才内存才对
内存调用瞅瞅
上代码(go2cache)
//1. new一个go2cache的 server,这个对象负责将数据库的数据建立2层缓存(redis,memory),批量查内存优先,单个查忽略内存只取 redis
func NewServer(redis string, codec Codec) *Server {
memory := newMemoryCache(codec) //内存子对象
redisCacheServer := newRedisCache(redis, memory, codec)
db := newDbCache(redisCacheServer, codec)
return &Server{
redis: redisCacheServer,
db: db,
codec: codec,
}
}
//2. 着重看内存子对象
func newMemoryCache(codec Codec) *memoryCache {
config := bigcache.DefaultConfig(time.Second * 60)
config.Shards = 1024 //1024个分片,bigcache这个组件,没了节约内存,将我们的数据是按byte存入全局的[]byte里面去的
config.MaxEntrySize = 1024 * 16
config.HardMaxCacheSize = 500
cache, _ := bigcache.NewBigCache(config)
return &memoryCache{
cache: cache,
codec: codec,
}
}
func NewBigCache(config Config) (*BigCache, error) {
return newBigCache(config, &systemClock{})
}
func newBigCache(config Config, clock clock) (*BigCache, error) {
.......
for i := 0; i < config.Shards; i++ {
cache.shards[i] = initNewShard(config, onRemove, clock) //重点来了~
}
.......
}
func initNewShard(config Config, callback onRemoveCallback, clock clock) *cacheShard {
bytesQueueInitialCapacity := config.initialShardSize() * config.MaxEntrySize
maximumShardSizeInBytes := config.maximumShardSizeInBytes()
if maximumShardSizeInBytes > 0 && bytesQueueInitialCapacity > maximumShardSizeInBytes {
bytesQueueInitialCapacity = maximumShardSizeInBytes
}
return &cacheShard{
hashmap: make(map[uint64]uint32, config.initialShardSize()),
hashmapStats: make(map[uint64]uint32, config.initialShardSize()),
entries: *queue.NewBytesQueue(bytesQueueInitialCapacity, maximumShardSizeInBytes, config.Verbose), //重点
entryBuffer: make([]byte, config.MaxEntrySize+headersSizeInBytes),
onRemove: callback,
isVerbose: config.Verbose,
logger: newLogger(config.Logger),
clock: clock,
lifeWindow: uint64(config.LifeWindow.Seconds()),
statsEnabled: config.StatsEnabled,
}
}
// NewBytesQueue initialize new bytes queue.
// capacity is used in bytes array allocation
// When verbose flag is set then information about memory allocation are printed
func NewBytesQueue(capacity int, maxCapacity int, verbose bool) *BytesQueue {
return &BytesQueue{
array: make([]byte, capacity),
capacity: capacity,
maxCapacity: maxCapacity,
headerBuffer: make([]byte, binary.MaxVarintLen32),
tail: leftMarginIndex,
head: leftMarginIndex,
rightMargin: leftMarginIndex,
verbose: verbose,
}
}
最终内存好用结果打印
找我们都是怎么使用的
经过分析发现 /Flock-Server/rpc/server/internal/room/worker/base/base.go@Init 方法会在每次rid new一个work的时候被初始化一次, 了解下房间业务,发现房间是一个街区一个房间的,所以。。。
解决方式
方式一
//newMemoryCache 使用较小的内存做缓冲
func newMemoryCache(codec Codec) *memoryCache {
config := bigcache.DefaultConfig(time.Second * 60)
//config.Shards = 1024
config.Shards = 10// 改成系统默认的10个分片就行了,或者咱们的业务就别用内存了,直接对接redis, bigcache这个组件默认的1024估计是出于全局只要new一个考虑,而我们吧bigcache当子组件使用的时候,忘记这一茬了。。。
config.MaxEntrySize = 1024 * 16 //16KB
config.HardMaxCacheSize = 500
cache, _ := bigcache.NewBigCache(config)
return &memoryCache{
cache: cache,
codec: codec,
}
}
然后把
func (r *RoomWorkerBase) Init() {
r.Ctx = context.TODO()
r.ServerCache = go2cache.NewServer(consts.RedisRoom, cache.RoomCodec{}) //想办法把这个对象编程单例
r.I18n = i18n.NewI18n()
r.I18n.SetLanguage("en")
r.msgCh = make(chan interface{}, 500)
r.stopCh = make(chan interface{})
r.SyncHdl = make(map[reflect.Type]SyncHandler)
r.ASyncHdl = make(map[reflect.Type]AsyncHandler)
r.SyncPbHdl = make(map[protoreflect.Descriptor]SyncProtoHandler)
r.ASyncPbHdl = make(map[protoreflect.Descriptor]AsyncProtoHandler)
r.CmdHdl = make(map[string]CmdHandler)
r.timers = make(map[string]*time.Timer)
r.CommonCache = cache.NewRoomCommon()
r.RegisterHandlers()
}
方式二
直接把内存的二级缓存拿掉,让go2cache直接对接redis, 这种方式代码业务方无感知,只需要该go2cache内部
来个demo重现看看
func main() {
var m runtime.MemStats
asas := map[string]interface{}{}
for i := 0; i < 20; i++ {
asas[fmt.Sprintf("%d", i)] = go2cache.NewServer(consts.RedisRoom, RoomCodec{})
fmt.Println(fmt.Sprintf("第 %d: 次循环n", i))
runtime.ReadMemStats(&m)
fmt.Printf("%d Mn", m.Alloc/1024/1024)
time.Sleep(time.Second * 7)
}
}
对其他全局对象使用的一些思考
golang里面的map充当全局对象使用的时候, 要时刻提醒自己,这个map在被delete的时候,内存不会被gc的,只会被打tag,需要定时迁移新的map,
才能是老的map里面被tag的内存对象被回收。。。
总结
-
go tool pprof --alloc_space http://127.0.0.1:6064/debug/pprof/heap
对所有内存对象的监控打印,其中包括被GC的 -
go tool pprof http://127.0.0.1:6064/debug/pprof/heap
等价与go tool pprof --inuse_space http://127.0.0.1:6064/debug/pprof/heap
对活跃内存对象打印,不包活会被GC掉的对象 -
top -pid 1123
//对具体的pid进行top命令监控 -
ps -ef | grep worker
等价于ps -aux | grep nginx
根据进程名称查看进程的pid及启动信息 - 通过端口查出pid,进而查出进程的启动命令等信息
lsof -i :9527 //查看端口的pid
ps -ef | grep 12321 //根据pid 查出进程启动信息
阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/12491,转载请注明出处。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/12491,转载请注明出处。
评论0