2013-09-24 5 views
3

로그 트래픽을 udp에서 수신하고, 구문 분석을 시도한 다음 Redis에 삽입하는 프로그램이 있습니다. . 일정 수준의 트래픽에서, 메모리는골란 메모리 폭발 : newdefer

빠르게 ("폭발"기가 바이트에 수백 메가 바이트로 증가 할 것으로 보인다 나는 이런 일이 발생 직후 힙 프로파일을 잡고했고 다음과 같은 반환

(pprof) top100 -cum 
Total: 1731.3 MB 
    0.0 0.0% 0.0% 1731.3 100.0% gosched0 
    1162.5 67.1% 67.1% 1162.5 67.1% newdefer 
    0.0 0.0% 67.1% 1162.5 67.1% runtime.deferproc 
    0.0 0.0% 67.1% 1162.0 67.1% main.TryParse 
    0.0 0.0% 67.1% 438.0 25.3% runtime.main 
    301.5 17.4% 84.6% 437.5 25.3% main.main 
    136.0 7.9% 92.4% 136.0 7.9% runtime.malg 
    0.0 0.0% 92.4% 136.0 7.9% runtime.newproc 
    0.0 0.0% 92.4% 136.0 7.9% runtime.newproc1 
    1.5 0.1% 92.5% 131.3 7.6% main.RedisCuller 
    0.0 0.0% 92.5% 108.5 6.3% github.com/garyburd/redigo/redis.(*conn).Do 
    0.0 0.0% 92.5% 108.5 6.3% github.com/garyburd/redigo/redis.(*conn).readReply 
    0.0 0.0% 92.5% 108.5 6.3% github.com/garyburd/redigo/redis.(*pooledConnection).Do 
    95.8 5.5% 98.0%  95.8 5.5% cnew 
    0.0 0.0% 98.0%  95.8 5.5% runtime.cnewarray 
    34.0 2.0% 100.0%  34.0 2.0% runtime.convT2E 
    0.0 0.0% 100.0%  0.5 0.0% main.init 
    0.0 0.0% 100.0%  0.5 0.0% net/http/pprof.init 
    0.0 0.0% 100.0%  0.5 0.0% sync.(*Once).Do 
    0.0 0.0% 100.0%  0.5 0.0% syscall.Getenv 
    0.0 0.0% 100.0%  0.5 0.0% time.init 
프로그램이 경우

는 "건강한"프로필보다 다음과 같습니다

(pprof) top20 -cum 
Total: 186.7 MB 
    0.0 0.0% 0.0% 186.7 100.0% gosched0 
    0.5 0.3% 0.3% 122.7 65.7% main.RedisCuller 
    0.0 0.0% 0.3% 103.5 55.4% github.com/garyburd/redigo/redis.(*pooledConnection).Do 
    0.0 0.0% 0.3% 103.0 55.2% github.com/garyburd/redigo/redis.(*conn).Do 
    0.0 0.0% 0.3% 103.0 55.2% github.com/garyburd/redigo/redis.(*conn).readReply 
    88.2 47.2% 47.5%  88.2 47.2% cnew 
    0.0 0.0% 47.5%  88.2 47.2% runtime.cnewarray 
    0.0 0.0% 47.5%  57.0 30.5% main.TryParse 
    57.0 30.5% 78.0%  57.0 30.5% newdefer 
    0.0 0.0% 78.0%  57.0 30.5% runtime.deferproc 
    34.0 18.2% 96.3%  34.0 18.2% runtime.convT2E 
    1.5 0.8% 97.1%  6.5 3.5% main.main 
    0.0 0.0% 97.1%  6.5 3.5% runtime.main 
    5.0 2.7% 99.7%  5.0 2.7% runtime.malg 
    0.0 0.0% 99.7%  5.0 2.7% runtime.newproc 
    0.0 0.0% 99.7%  5.0 2.7% runtime.newproc1 
    0.0 0.0% 99.7%  0.5 0.3% bufio.NewWriter 
    0.0 0.0% 99.7%  0.5 0.3% bufio.NewWriterSize 
    0.0 0.0% 99.7%  0.5 0.3% github.com/garyburd/redigo/redis.(*Pool).get 
    0.0 0.0% 99.7%  0.5 0.3% github.com/garyburd/redigo/redis.(*pooledConnection).get 

나는 (이 자주 실패 할 수 있기 때문에) 파싱 기능을 둘러싼 내 코드에서 가진 유일한 연기 :

for { 
      rlen, _, err := sock.ReadFromUDP(buf[0:]) 
      checkError(err) 
      raw := logrow.RawRecord(string(buf[:rlen])) 
      go TryParse(raw, c) 
    } 

    ... 

    func TryParse(raw logrow.RawRecord, c chan logrow.Record) { 
     defer func() { 
       if r := recover(); r != nil { 
         //log.Printf("Failed Parse due to panic: %v", raw) 
         return 
       } 
     }() 
     rec, ok := logrow.ParseRawRecord(raw) 
     if !ok { 
       return 
       //log.Printf("Failed Parse: %v", raw) 
     } else { 
       c <- rec 
     } 
} 

사람이 갑자기 풍선을 일으킬 수있는 잘못된 일을하는 것은 명백합니까? 또는 그것을 잠그는 데 어떤 방향을 제시 할 수 있습니까?

편집합니다 (logrow.Record 채널의 주위에 더 많은 코드) : closured 지연 기능을 우리가 볼로 (이동 자체의 문제)의 누출로 밝혀졌다

c := make(chan logrow.Record) 
... 
go RedisInserter(c, bucket, retention, pool) 

func RedisInserter(c chan logrow.Record, bucket, retention int, p *redis.Pool) { 
     for rec := range c { 
       logrow.SendToRedis(rec, bucket, retention, p) 
     } 
} 
+0

당신은'c' ('chrow'의'logrow.Record')를 닫고 있습니까? 그렇다면 'c'의 종료를 연기 하시겠습니까? 더 많은 코드를 보지 않고 무엇을 제안해야할지 확신 할 수 없습니다. – Intermernet

+0

@Intermernet : 해당 채널 주위에 조금 더 많은 코드를 추가했습니다. 내가 "닫아야 할"것은 일어나지 않아야한다. 왜냐하면 채널은'go TryParse'에 의해 끊임없이로 그로우로 가득 채워져 있고'RedisInserter' 루프는 그 행들을 소비해야만하기 때문입니다. 지금 그것을 보면 logrow.SendToRedis가 차단되어 채널이 채워질 수 있지만 높은 newdefer 메모리 사용으로 이어질 수는 없는지 알 수 있습니다. –

+0

클로저 지연 기능에서 메모리 누수가 있습니다. 인쇄물의 주석을 제거한 경우 어떤 경우가 될 수 있습니다. : https://codereview.appspot.com/10784043/ 팁을 사용해 보셨습니까? – ClojureMostly

답변

1

에서 :

defer func() { 
      if r := recover(); r != nil { 
        //log.Printf("Failed Parse due to panic: %v", raw) 
        return 
      } 
    }() 

그래서 Go 최신 버전으로 다시 빌드하면 문제가 해결됩니다 (참조 : https://codereview.appspot.com/10784043/). 그러나, 현명한 일은 ParseRecordFunction을 디자인하는 것입니다. 그래서 경계를 벗어나서 공황 상태를 일으키지는 않습니다.