우선, 대답은 '예'입니다. OOM을 일으킬 가능성이 매우 높으며 이에 대한 논의가 있습니다: github.com/golang/go/issues/20135. 일반적인 의미는 대규모 맵
에서 삭제
작업이 실제로 메모리를 해제하지 않고 메모리 OOM을 유발할 수 있다는 것입니다. 일반적인 접근 방식은 지도를 다시 작성하는 것입니다. safemap
의 컨테이너 구성요소는 go-zero
에 내장되어 있습니다. safemap
은 이를 어느 정도 방지할 수 있습니다.
그럼 먼저 go
에서 기본으로 제공하는 지도
를 삭제하는 방법을 살펴볼까요? map
中,delete
操作没有真正释放内存而可能导致内存 OOM。
所以一般的做法:就是 重建map。而 go-zero
中内置了 safemap
的容器组件。safemap
在一定程度上可以避免这种情况发生。
那首先我们看看 go
原生提供的 map
是怎么删除的?
1 package main 2 3 func main() { 4 m := make(map[int]string, 9) 5 m[1] = "hello" 6 m[2] = "world" 7 m[3] = "go" 8 9 v, ok := m[1] 10 _, _ = fn(v, ok) 11 12 delete(m, 1) 13 } 14 15 func fn(v string, ok bool) (string, bool) { 16 return v, ok 17 }
测试代码如上,我们可以通过 go tool compile -S -N -l testmap.go | grep "CALL"
:
0x0071 00113 (test/testmap.go:4) CALL runtime.makemap(SB) 0x0099 00153 (test/testmap.go:5) CALL runtime.mapassign_fast64(SB) 0x00ea 00234 (test/testmap.go:6) CALL runtime.mapassign_fast64(SB) 0x013b 00315 (test/testmap.go:7) CALL runtime.mapassign_fast64(SB) 0x0194 00404 (test/testmap.go:9) CALL runtime.mapaccess2_fast64(SB) 0x01f1 00497 (test/testmap.go:10) CALL "".fn(SB) 0x0214 00532 (test/testmap.go:12) CALL runtime.mapdelete_fast64(SB) 0x0230 00560 (test/testmap.go:7) CALL runtime.gcWriteBarrier(SB) 0x0241 00577 (test/testmap.go:6) CALL runtime.gcWriteBarrier(SB) 0x0252 00594 (test/testmap.go:5) CALL runtime.gcWriteBarrier(SB) 0x025c 00604 (test/testmap.go:3) CALL runtime.morestack_noctxt(SB)
执行第12行的 delete
,实际执行的是 runtime.mapdelete_fast64
。
这些函数的参数类型是具体的 int64
,mapdelete_fast64
跟原始的 delete
操作一样的,所以我们来看看 mapdelete
。
长图预警!!!
大致代码分析如上,具体代码就留给大家去阅读了。其实大致过程:
key
是否存在count--
所以你在大面积删除 key
,实际 map
存储的 key
是不会删除的,只是标记当前的key状态为 empty
。
其实出发点,和 mysql
的标记删除类似,防止后续会有相同的 key
插入,省去了扩缩容的操作。
但是这个对有些场景是不妥的,如果开发者在未来时间内都不会再插入相同的 key
,很可能会导致 OOM
。
所以针对以上情况,go-zero
开发了 safemap
。下面我们看看 safemap
是如何避免这个问题的?
直接从操作 safemap
中分析为什么要这么设计:
newmap
中map
是一个整体,所以 key
只能留一份所以为什么要设置两个 map
就很清楚了:
dirtyOld
作为存储主体,如果 delete
操作达到阈值,则会触发迁移。dirtyNew
作为暂存体,会在到达阈值时,存放部分 key/value
所以在迁移操作时,我们需要做的就是:将原先的 dirtyOld
清空,存储的 key/value 通过 for-range 重新存储到 dirtyNew
,然后将 dirtyNew
指向 dirtyOld
。
可能会有疑问:不是说
key/value
没有删除吗,只是标记了tophash=empty
其实在
for-range
过程中,会过滤掉tophash 的 key
这样就实现了不需要的 key 不会被加入到 dirtyNew
,进而不会影响 dirtyOld
네이티브 맵 삭제
rrreee테스트 코드는 위와 같으며 go tool compile -S -N -l testmap.go | grep "CALL"
을 전달할 수 있습니다.
행 실행 12 delete
, 실제 실행은 runtime.mapdelete_fast64
입니다.
int64
이며, mapdelete_fast64
는 원래 delete
작업과 동일하므로 를 살펴보겠습니다. > 지도삭제
.
긴 사진 경고! ! !
키
가 존재하는지 쿼리count--
키
를 삭제합니다. , 실제 맵
저장된 키
는 삭제되지 않지만, 현재 키 상태는 비어있음
으로 표시됩니다. 🎜🎜실제로 시작점은 mysql
의 태그 삭제와 비슷합니다. 이는 나중에 동일한 키
가 삽입되는 것을 방지하여 확장 및 축소 작업이 필요하지 않게 해줍니다. 🎜🎜그러나 이는 일부 시나리오에서는 부적절합니다. 개발자가 나중에 동일한 키
를 다시 삽입하지 않으면 OOM
이 발생할 가능성이 높습니다. 🎜🎜그래서 위 상황에 대응하여 go-zero
에서는 safemap
을 개발했습니다. safemap
이 이 문제를 어떻게 방지하는지 살펴보겠습니다. 🎜🎜🎜🎜safemap🎜🎜 safemap
작업에서 직접 왜 이렇게 설계되었는지 분석해 보세요. 🎜🎜🎜map
은 전체이므로 key
의 사본 하나만 남을 수 있습니다.map
이 설정된 이유는 분명합니다: 🎜dirtyOld
가 저장 주제로, 삭제
가 작업이 임계값에 도달하면 마이그레이션이 트리거됩니다. dirtyNew
임시 저장소로 임계값에 도달하면 키/값
의 일부가 저장됩니다.dirtyOld
를 지우고 for-range를 통해 저장된 키/값을 다시 dirtyNew
에 저장한 다음입니다. dirtyNew
지점을 dirtyOld
🎜로 변경하세요. 🎜🎜질문이 있을 수 있습니다.🎜이렇게 하면 불필요한 키가 필터링되지 않습니다.키/값
이 삭제되지 않았다는 의미 아닌가요? 방금tophash=empty
로 표시되었습니다🎜🎜 🎜실제로에서 for-range
프로세스 중에tophash 의 키가 필터링됩니다.🎜
dirtyNew
에 추가되었으며 dirtyOld
에는 영향을 미치지 않습니다. 🎜🎜🎜🎜🎜이것은 사실 구세대와 신세대의 가비지 수집 개념입니다. 🎜🎜자세한 구현 내용은 소스코드를 확인해주세요! 🎜🎜🎜🎜프로젝트 주소🎜🎜github.com/tal-tech/go-zero🎜🎜go-zero 사용을 환영하고 🎜star🎜 지원해 주세요! 🎜위 내용은 Golang 맵에서 GC를 수행하는 것에 대해 들어보셨나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!