Golang GO와 마이크로서비스 ChatServer의 3 압력 측정 및 진단
8294 단어 golangpprof
인연
최근 읽기<>(유금량, 2021.1) 본 시리즈 노트는 golang으로 연습할 예정
케이스 요구사항(채팅 서버)
대상(Day 3)
프로세스
단원 테스트
package chat_server
import (
"fmt"
cs "learning/gooop/chat_server"
"math/rand"
"os"
"runtime"
"runtime/pprof"
"strings"
"sync"
"testing"
"time"
)
func Test_ChatServer(t *testing.T) {
fnAssertTrue := func(b bool, msg string) {
if !b {
t.Fatal(msg)
}
}
// create dump file
memProfile, err := os.Create("/home/ioly/chat_server_mem.profile")
if err != nil {
t.Fatal(err)
}
cpuProfile, err := os.Create("/home/ioly/chat_server_cpu.profile")
if err != nil {
t.Fatal(err)
}
rtnProfile, err := os.Create("/home/ioly/chat_server_routine.profile")
if err != nil {
t.Fatal(err)
}
err = pprof.StartCPUProfile(cpuProfile)
if err != nil {
t.Fatal(err)
}
defer pprof.StopCPUProfile()
port := 3333
server := cs.NewChatServer()
err = server.Open(port)
if err != nil {
t.Fatal(err)
}
clientCount := 500
wg := &sync.WaitGroup{}
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
address := fmt.Sprintf("localhost:%v", port)
for i := 0;i < clientCount;i++ {
err, client := cs.DialChatClient(address)
if err != nil {
t.Fatal(err)
}
id := fmt.Sprintf("c%02d", i)
//client.RecvHandler(func(client cs.IChatClient, msg cs.IMsg) {
// t.Logf("%v recv: %v
", id, msg)
//})
wg.Add(1)
go func() {
client.SetName(id)
client.Send(&cs.NameMsg{id })
n := 0
duration := rnd.Intn(3) + 1
for range time.Tick(time.Duration(duration) * time.Second) {
client.Send(&cs.ChatMsg{id, fmt.Sprintf("msg %02d from %v", n, id) })
n++
if n > 10 {
break
}
}
client.Close()
wg.Done()
}()
}
// wait and set status
done := []bool{false}
go func() {
wg.Wait()
done[0] = true
}()
// print passed seconds
passedSeconds := 0
for range time.Tick(time.Second) {
passedSeconds++
t.Logf("%v seconds passed", passedSeconds)
if done[0] {
break
}
}
time.Sleep(5*time.Second)
server.Close()
// dump heap file
runtime.GC()
err = pprof.Lookup("heap").WriteTo(memProfile, 0)
if err != nil {
t.Fatal(err)
}
err = pprof.Lookup("goroutine").WriteTo(rtnProfile, 0)
if err != nil {
t.Fatal(err)
}
// check server logs
logs := cs.Logging.AllLogs()
fnHasLog := func(log string) bool {
for _,it := range logs {
if strings.Contains(it, log) {
return true
}
}
return false
}
for i := 0;i < clientCount;i++ {
msg := fmt.Sprintf("tChatClient.postConnClosed, c%02d, serverFlag=false", i)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
msg = fmt.Sprintf("tChatServer.handleIncomingConn, clientCount=%v", i + 1)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
msg = fmt.Sprintf("tChatClient.postConnClosed, c%02d, serverFlag=true", i)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
msg = fmt.Sprintf("tChatServer.handleClientClosed, c%02d", i)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
}
}
테스트 출력
동시 500, 끝 부분만tChatClient.beginWrite, write error, c496, serverFlag=true
tChatClient.beginWrite,
루틴 유출 진단(복구 전)
누설 시 진단 로그 (병발 50)$ go tool pprof ~/chat_server_routine.profile
File: chat_server.test
Type: goroutine
Time: Mar 9, 2021 at 9:12am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 4, 100% of 4 total
Showing top 10 nodes out of 21
flat flat% sum% cum cum%
3 75.00% 75.00% 3 75.00% runtime.gopark
1 25.00% 100% 1 25.00% runtime/pprof.runtime_goroutineProfileWithLabels
0 0% 100% 1 25.00% command-line-arguments.Test_ChatServer
0 0% 100% 1 25.00% learning/gooop/chat_server.(*tChatClient).beginWrite
0 0% 100% 1 25.00% learning/gooop/chat_server.(*tChatClient).closeConn
0 0% 100% 1 25.00% main.main
0 0% 100% 1 25.00% runtime.chanrecv
0 0% 100% 1 25.00% runtime.chanrecv1
0 0% 100% 1 25.00% runtime.chansend
0 0% 100% 1 25.00% runtime.chansend1
(pprof)
closeConn 함수 막힘 보이기func (me *tChatClient) closeConn() {
if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
return
}
Logging.Logf("tChatClient.closeConn, %v, serverFlag=%v", me.name, me.serverFlag)
me.closeChan
코드로 볼 때closeConn 함수가 막히는 것은 단지 하나의 가능성일 뿐이다. 바로closeChan이 막히는 것이다.이 원인은 좀 이상하다. 왜냐하면 이미 atomic를 넣었기 때문이다.Compare AndSwap Int32 보호는 여전히 막혀 있습니다. 유일한 원인은 병발 환경에서closeChan이 아직 읽기 시작하지 않아서 쓸 수 없습니다.
루틴 유출 복구
쓰기 차단을 초래하는closeChan에 버퍼를 추가하는 것입니다. 크기는closeConn 함수와 같은 호출 가능한 입구 수량입니다.func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
it := &tChatClient{
conn: conn,
openFlag: 0,
closeFlag: 0,
serverFlag: serverFlag,
closeChan: make(chan bool, 3), // closeChan !!
sendChan: make(chan IMsg, gMaxPendingSend),
name: "anonymous",
sendLogs: []IMsg{},
dropLogs: []IMsg{},
recvLogs: []IMsg{},
}
it.open()
return it
}
루틴 유출 진단(복구 후)
복구 후 500까지 확대 및 발송하였으나 뚜렷한 루틴 누출이 발견되지 않았습니다.$ go tool pprof ~/chat_server_routine.profile
File: chat_server.test
Type: goroutine
Time: Mar 9, 2021 at 9:28am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 3, 100% of 3 total
Showing top 10 nodes out of 17
flat flat% sum% cum cum%
2 66.67% 66.67% 2 66.67% runtime.gopark
1 33.33% 100% 1 33.33% runtime/pprof.runtime_goroutineProfileWithLabels
0 0% 100% 1 33.33% command-line-arguments.Test_ChatServer
0 0% 100% 1 33.33% main.main
0 0% 100% 1 33.33% runtime.chanrecv
0 0% 100% 1 33.33% runtime.chanrecv1
0 0% 100% 1 33.33% runtime.main
0 0% 100% 1 33.33% runtime/pprof.(*Profile).WriteTo
0 0% 100% 1 33.33% runtime/pprof.profileWriter
0 0% 100% 1 33.33% runtime/pprof.writeGoroutine
(pprof)
(end)
tChatClient.beginWrite, write error, c496, serverFlag=true
tChatClient.beginWrite,
누설 시 진단 로그 (병발 50)
$ go tool pprof ~/chat_server_routine.profile
File: chat_server.test
Type: goroutine
Time: Mar 9, 2021 at 9:12am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 4, 100% of 4 total
Showing top 10 nodes out of 21
flat flat% sum% cum cum%
3 75.00% 75.00% 3 75.00% runtime.gopark
1 25.00% 100% 1 25.00% runtime/pprof.runtime_goroutineProfileWithLabels
0 0% 100% 1 25.00% command-line-arguments.Test_ChatServer
0 0% 100% 1 25.00% learning/gooop/chat_server.(*tChatClient).beginWrite
0 0% 100% 1 25.00% learning/gooop/chat_server.(*tChatClient).closeConn
0 0% 100% 1 25.00% main.main
0 0% 100% 1 25.00% runtime.chanrecv
0 0% 100% 1 25.00% runtime.chanrecv1
0 0% 100% 1 25.00% runtime.chansend
0 0% 100% 1 25.00% runtime.chansend1
(pprof)
closeConn 함수 막힘 보이기
func (me *tChatClient) closeConn() {
if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
return
}
Logging.Logf("tChatClient.closeConn, %v, serverFlag=%v", me.name, me.serverFlag)
me.closeChan
코드로 볼 때closeConn 함수가 막히는 것은 단지 하나의 가능성일 뿐이다. 바로closeChan이 막히는 것이다.이 원인은 좀 이상하다. 왜냐하면 이미 atomic를 넣었기 때문이다.Compare AndSwap Int32 보호는 여전히 막혀 있습니다. 유일한 원인은 병발 환경에서closeChan이 아직 읽기 시작하지 않아서 쓸 수 없습니다.
루틴 유출 복구
쓰기 차단을 초래하는closeChan에 버퍼를 추가하는 것입니다. 크기는closeConn 함수와 같은 호출 가능한 입구 수량입니다.func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
it := &tChatClient{
conn: conn,
openFlag: 0,
closeFlag: 0,
serverFlag: serverFlag,
closeChan: make(chan bool, 3), // closeChan !!
sendChan: make(chan IMsg, gMaxPendingSend),
name: "anonymous",
sendLogs: []IMsg{},
dropLogs: []IMsg{},
recvLogs: []IMsg{},
}
it.open()
return it
}
루틴 유출 진단(복구 후)
복구 후 500까지 확대 및 발송하였으나 뚜렷한 루틴 누출이 발견되지 않았습니다.$ go tool pprof ~/chat_server_routine.profile
File: chat_server.test
Type: goroutine
Time: Mar 9, 2021 at 9:28am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 3, 100% of 3 total
Showing top 10 nodes out of 17
flat flat% sum% cum cum%
2 66.67% 66.67% 2 66.67% runtime.gopark
1 33.33% 100% 1 33.33% runtime/pprof.runtime_goroutineProfileWithLabels
0 0% 100% 1 33.33% command-line-arguments.Test_ChatServer
0 0% 100% 1 33.33% main.main
0 0% 100% 1 33.33% runtime.chanrecv
0 0% 100% 1 33.33% runtime.chanrecv1
0 0% 100% 1 33.33% runtime.main
0 0% 100% 1 33.33% runtime/pprof.(*Profile).WriteTo
0 0% 100% 1 33.33% runtime/pprof.profileWriter
0 0% 100% 1 33.33% runtime/pprof.writeGoroutine
(pprof)
(end)
func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
it := &tChatClient{
conn: conn,
openFlag: 0,
closeFlag: 0,
serverFlag: serverFlag,
closeChan: make(chan bool, 3), // closeChan !!
sendChan: make(chan IMsg, gMaxPendingSend),
name: "anonymous",
sendLogs: []IMsg{},
dropLogs: []IMsg{},
recvLogs: []IMsg{},
}
it.open()
return it
}
복구 후 500까지 확대 및 발송하였으나 뚜렷한 루틴 누출이 발견되지 않았습니다.
$ go tool pprof ~/chat_server_routine.profile
File: chat_server.test
Type: goroutine
Time: Mar 9, 2021 at 9:28am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 3, 100% of 3 total
Showing top 10 nodes out of 17
flat flat% sum% cum cum%
2 66.67% 66.67% 2 66.67% runtime.gopark
1 33.33% 100% 1 33.33% runtime/pprof.runtime_goroutineProfileWithLabels
0 0% 100% 1 33.33% command-line-arguments.Test_ChatServer
0 0% 100% 1 33.33% main.main
0 0% 100% 1 33.33% runtime.chanrecv
0 0% 100% 1 33.33% runtime.chanrecv1
0 0% 100% 1 33.33% runtime.main
0 0% 100% 1 33.33% runtime/pprof.(*Profile).WriteTo
0 0% 100% 1 33.33% runtime/pprof.profileWriter
0 0% 100% 1 33.33% runtime/pprof.writeGoroutine
(pprof)
(end)