관리 메뉴

HAMA 블로그

예제로 보는 아카(akka) - 13. TCP 와 데이터의 끝? 본문

Akka

예제로 보는 아카(akka) - 13. TCP 와 데이터의 끝?

[하마] 이승현 (wowlsh93@gmail.com) 2016. 10. 19. 22:52


- Scala 2.11.8 기반 

- Akka 2.4.11 기반 

- Java 8  (akka 2.4 부터는 java 8 요구함. scala 2.11 은 java 7도 괜찮지만~) 



Akka TCP 와 데이터의 끝 

서론 

Akka TCP 서버를 만들때 데이터를 받는 부분의 대부분의 예는 그냥 Received(data) 이다. 즉 아래 형태.

def writing: Receive = {

case Received(data) =>
log.info(s"fromGW - '${data.utf8String}'")
ruleEngine ! fromTGH(data.utf8String)

data 는 ByteString 형 이기 때문에 Json문자열을 보낼 경우 utf8String 으로 변경해서 처리하는데 
여기서 문제가 하나 있다. 

다들 알다시피 TCP 는 특성이 순서는 보장하되 시작 과 끝을 보장하지 않는다는 점이다. 무슨 야그냐 하면 

"돈을 10,000원 보낸다"  라고 TCP 를 통해 메세지를 보내면 
"보낸다 10,000원 돈을"  이렇게 전달되진 않는다. 순서가 섞이지 않는다는 말

하지만 

"돈을 10" 이 먼저 도착 할 수 있다는 점이다. 그 후에 ",000원 보낸다" 이렇게 도착 할 수 있다는.. 
아니 이 녀석이 만원 빌려줬는데 10원을 갚아??  이렇게 오해하고 쌈이 날 수 있다는 야그..

"{ ..... "  이렇게만 도착해서 닫힘 괄호가 없어서 문제가 생길 수 도 있고..
 "돈을 10,000원 보낸다"  "어디 사세요?" 이렇게 두번 보냈는데 "돈을 10,000원 보낸다 어디" 이렇게 받을 수 도 있다.

그래서 대부분의 경우 이런 하나의 제대로된 데이터 뭉치를 구분하는 방법을 우리 스스로 추가해 줘야 하는데  
그 방법을 살펴보자

각종 데이터 뭉치 구별 방식 

1. 그냥 끝이란거 자체를 무시. 그냥 버퍼 1024byte 에 데이터를 받으면 그게 "돈을 10,000원 보낸다" 가 온다고 확신하고 처리하는 방법이다. 1024바이트 이상의 데이터는 보내고받지 않는다는 확신이 있어야 한다. 물론 이 방법은 무지편하긴 한데 가끔 문제가 생길 수 있다. 웬만한 환경에서는 문제가 잘 안생기긴 하더라..그래도 아주 중요한 데이터 통신이 라면 이렇게 하면 안된다.

2. /n 까지 하나의 데이터 뭉치라고 서로 약속하는것이다. 문자열이라고 서로 의견일치해야하고..즉 보내는 측에서 데이터끝에 /n 를 추가한다. writeLine 같은거 쓰면 자동으로 붙는다.받는 측에서 readLine으로 /n 까지 읽는다. 이렇게 되면 데이터 뭉치를 확신 할 수 있다. 만약 이미지나 바이너리 데이터는 base64 엔코딩등을 이용해서 문자열로 만든다. 요즘 Json 형식이 이기종 통신을 편하게 할 수 있게 해주기 때문에 이 방법이 가장 무난하지 않나 싶다. 

3. 오랫동안 내가 C++ IOCP 통신시 이용한 방식인데 (아마 다른 사람들도 이렇게 많이 했을 듯 하다) syn::컨텐츠길이::content::end 이런식으로 (비슷하게) 프로토콜을 만들어서 보내는 것이다.syn::5:abcde::end 요렇게 받는쪽에서 syn:: 를 읽고 4바이트를 추가로 읽어서 데이터 길이를 획득하고 데이터 길이 + 알파만큼 OS 에게 요청한다. OS 는 해당 버퍼를 가득 채웠다면 Completion Port 에서 알려 준다.그 후에 버퍼를 파싱해서 원하는 한뭉치의 데이터를 얻고 그 이후 데이터 뭉치와 구분한다.   (추가:여기서 버퍼파싱이란 버퍼에는  여러데이터 뭉치가 존재할 수 있기 때문에 end 로 파싱해서사용하고 남은 부분은 유지하며 뒤에 이어서오는 데이터를 붙인다는 뜻이다.)  proactor 패턴이다.

4. 이건 하둡(hadoop)코어에서 본것인데 하둡 RPC 내부에서는 이렇게 하더라. 일단 4바이트만큼 읽는다. 그 4바이트는 개별 데이터 뭉치의 길이다. 그래서 그 길이가 120이면 120만큼 buffer에 채우고 , 만약 한번 요청에 채우지 못했다면 채워질때까지 OS 에게 요청한다.   

5. 기타 등등 (뭐 다른 좋은 방식 있나요?) 

이런식으로 받은 개별 데이터 뭉치를 다시 데이터 타입 별로 쪼개고 지지고 볶고 해야 하기도 하지만 (효율,성능극대화)
그냥 Json 형식으로 보내고 받는게 성능이 굉장히 크리티컬한 경우가 아니라면 무난한듯 싶다. 성능문제도 사실 분산,병렬로 커버하는게 더 나은거 같기도하고.. (하지만 예를들어 원격데스크탑 같은 솔루션은 이미지를 얼마나 효과적으로 압축하는가, 그걸 얼마나 빠르게 보내고 받느냐가  중요하니 이렇게 할 리는 없을 거다)

그럼 Akka 에서 TCP 끝은 어떻게 되는가?  
(정확한 내부 동작을 이해하기엔 현실이 녹록치 않으니 실험을..)

실험1)

- 실험 : a 클라이언트에서 akka tcp 서버로 초당 50개의 30byte 메세지 전송 

- 결과: 정확한 메세지 뭉치 받음 

실험2)

- 실험 : a 와 b 클라이언트에서 akka tcp 서버로 초당 300개의 30byte 메세지 전송 

- 결과: 정확한 메세지 뭉치 받음 

??? 

내부에서 잘 처리하나? 

http://stackoverflow.com/questions/22914710/akka-io-tcp-with-json

Akka's TCP module is (and is designed to be) very "low level", so we're not providing any kind of frame delimiters. You should treat it more like an TCP level building block you then have to build your stuff on.

위의 링크를 읽어보면 그렇지 않을거 같은데.. =.=a 이 친구가 잘못 알고 있을 가능성도 크고..아카가 계속 변화중일 수도 있고.. (추가: http://stackoverflow.com/questions/30467411/akka-io-receive-line-by-line-in-2-3)

더 빡세게 실험해보자 

실험3)

- 실험 : 클라이언트 10개에서 akka tcp 서버로 초당 1000개의 30byte 메세지 전송 

- 결과: 정확한 메세지 뭉치 받음 

아마 내부에서 readLine 으로 동작하지 않나 싶은데, 이 정도면 내 환경에선 전혀 무리가 없다.  죄송한 말씀이지만 보다 더 정확한 결과를 알고 싶으면 내부 코드를 분석하던가 (아카는 이 시간에도 변화되고 있다)  자신의 환경에 맞는 벤치마크를 면밀히 해야 할 것이다. 나중에 더 자세한 정보를 정리해서  소개 드리고  싶다는 말을 전하며 여기까지..


추가 읽을 거리) 

Akka I/O layer desing 
Parsing lines from a stream of ByteStrings


Comments