NDC12_Lockless게임서버설계와구현

7,496 views

Published on

NDC2012 발표자료

Published in: Technology
0 Comments
22 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,496
On SlideShare
0
From Embeds
0
Number of Embeds
210
Actions
Shares
0
Downloads
132
Comments
0
Likes
22
Embeds 0
No embeds

No notes for slide

NDC12_Lockless게임서버설계와구현

  1. 1. Lockless 게임서버 설계와 구현잠금 없는김 성 익noerror@softnette.com소프트네트
  2. 2. 개요• 동기화(Synchronication)의 어려움을 극복하기 위해, 구조적으로 잠금(Lock)을 최소화하는 게임 서버 설계 제안
  3. 3. • 게임 서버 개발에서의 동기화• 직렬화를 통한 잠금 없는 서버 프레임워크• 잠금 없는 서버 구현 사례
  4. 4. 멀티쓰레드 게임서버 모델• IOCP를 사용하는 윈도우 기반의 서버모델 RecvAsync GQCS GQCS GQCS 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync GQCS : GetQueuedCompletionStatus
  5. 5. 코드#1void CSession::EnterRoom(int roomid){ m_Sector = m_Server->FindRoom(roomid); m_Sector->list.push_back(this);}void CSession::UpdatePosition(int roomid, const Vec3& pos){ m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); }}
  6. 6. 코드#1 Room1 Room1 Room2 Enter Attack Move Move Move Move Attack• 멀티쓰레드 모델 Enter Move Attack Move Move Move Attack
  7. 7. 코드#2void CSession::EnterRoom(int roomid){ m_Sector = m_Server->FindRoom(roomid); ENTER(m_Sector->cs); m_Sector->list.push_back(this); LEAVE(m_Sector->cs);}void CSession::UpdatePosition(int roomid, const Vec3& pos){ m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; ENTER(m_Sector->cs); for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); } LEAVE(m_Sector->cs);}
  8. 8. 동기화• 동기화(Synchronization) – 동시 접근 시 생기는 오류 막기 위해 크리티컬 섹 션(Critical section), 뮤텍스(Mutex), 이벤트(Event), 세마포어(Semaphore)등의 동기화 객체를 이용하 여 잠금(lock)처리 Barrier Synchronization • 범위가 클 수록 동시성(concurrency) 감소 Linear <-> Recursive
  9. 9. 동기화• 동기화를 올바르게 하지 않으면(#1) – (운이 좋다면) 즉시 다운 – 잘못된 메모리 참조 • 잘못된 값이 복사되서 전파 – 잘못된 결과값 • 트랜젝션(Transaction) 처리 – 전혀 엉뚱한 곳에서 예측이 어려운 형태의 오류로 나타남
  10. 10. 동기화• 동기화를 올바르게 하지 않으면(#2) – 잠금 상태에서 IO사용으로 인한 치명적인 성능 저 하 유발 – 데드락(deadlock) 발생
  11. 11. 동기화• 동기화 정책의 완전성 요구 – 안정성 측면 – 성능 측면• 지속적인 개발 스트레스 요소 새로운 콘텐츠 도입시 부담감 =>방어적 프로그래밍 =>개발 퍼포먼스 저하• 서버 개발자의 숙명, 실력
  12. 12. • 게임 서버 개발에서의 동기화• 직렬화를 통한 잠금 없는 서버 프레임워크• 잠금 없는 서버 구현 사례
  13. 13. 싱글 쓰레드 게임 서버 모델• 고정관념을 버려보면 “시스템을 최대한 활용하기 위해서 게임서버 는 멀티 쓰레드 모델이어야 한다”• 게임에 따라서는 꽤 괜찮은 모델
  14. 14. 싱글 쓰레드 게임 서버 모델• 단점 – 시스템 활용도 낮음 동시처리가 안되기 때문에 멀티쓰레드 서버 대비 처리량이 적기 때문 에 응답성이 다소 떨어질 수 있다 (latency↑) – 확장성(Scalibility)의 한계 CPU 코어 증가 등 하드웨어 개선을 통한 성능 향상은 미비 하지만 게임의 성격과 맞다면 여러 개의 서버 프로세스 실행은 가능 – 성능 때문에 DB등의 IO작업을 메인 쓰레드에서 직 접 처리할 수 없음 서버를 기능별로 분산(예 DB쿼리서버) 운영 / 비동기 (non-blocking)함 수 사용
  15. 15. 싱글 쓰레드 게임 서버 모델• 단점 – 서버를 기능별로 분산한다면 • 처리가 선형적이지 않아 코드가 비직관적 • 알고리즘이 여러 공간으로 분산 • 서버간 통신을 위한 기계적인 코드 작업 • 서버간의 시차로 인한 기계적인 예외 처리
  16. 16. 싱글 쓰레드 게임 서버 모델• 장점 – 잠금 처리가 불필요 • 원자성(atomicity), 격리성(isolation) 완벽 보장 – 안정적, 예측 가능 • 메모리가 침범되거나, 알고리즘이 교차 처리되어 꼬이 거나, 데드락 걸릴 확률이 없다
  17. 17. 기본 아이디어• 멀티 쓰레드 게임 서버 모델에 싱글 쓰레드 게 임 서버 모델의 장점을 가져온다• 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로 구동
  18. 18. 기본 아이디어• 멀티 쓰레드 게임 서버 모델에 싱글 쓰레드 게 임 서버 모델의 장점을 가져온다• 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로 구동 “직렬화” 선형화, 파이프라인, 시퀀스, …
  19. 19. 직렬화• 잠금이 필요한 단위로 실행하는 쓰레드를 지 정한다• 캐주얼 게임을 예로 들면 – 홀수 게임방 처리는 Thread#1 에서 – 짝수 게임방 처리는 Thread#2 에서 실행• 주) 여기서 사용하는 직렬화, 선형화는 일반적인 병렬프로그래밍에서 사용 되는 용어(직렬화-배리어, 선형화-원자화)와는 다른 의미입니다. 오해를 없 애기 위해 영문 표기 하지 않습니다.
  20. 20. 직렬화 Room1 Room1 Room2 Enter Attack Move Move Move Move Attack• 멀티쓰레드 모델 • 직렬화한 모델 Enter Move Attack Enter Move Move Move Attack Move Attack Move Move Move Attack
  21. 21. 코드#3void CSession::EnterRoom(int roomid){ m_Sector = m_Server->FindRoom(roomid); ENTER(m_Sector->cs); m_Sector->list.push_back(this); LEAVE(m_Sector->cs);}void CSession::UpdatePosition(int roomid, const Vec3& pos){ m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; ENTER(m_Sector->cs); for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); } LEAVE(m_Sector->cs);}
  22. 22. 코드#4void CSession::EnterRoom(int roomid){ _ASSERT(GetThreadID(roomid) == GetCurrentThreadID()); m_Sector = m_Server->FindRoom(roomid); m_Sector->list.push_back(this);}void CSession::UpdatePosition(int roomid, const Vec3& pos){ _ASSERT(GetThreadID(m_Room->id) == GetCurrentThreadID()); m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); }}
  23. 23. 장점• 잠금 처리 불필요 – 동시성(concurrency) 증가 – 동기화 처리의 스트레스 일부 해방 • 개발 퍼포먼스 증가 • 새로운 시스템 시도와 도입에 관대해짐 희망사항• 멀티 쓰레드 모델 사용 – 성능, 반응성 – 확장성
  24. 24. 구현• 쓰레드 별로 IOCP생성, 소켓은 필요한 쓰레드의 IOCP에 등록RecvAsync GQCS#1 GQCS#2 GQCS#3 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync 쓰레드#1 쓰레드#2 쓰레드#3
  25. 25. 구현• 치명적인 문제점 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸 들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하 다 – 연결 중에는 방 이동이 불가능 ???
  26. 26. 구현• 치명적인 문제점 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸 들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하 다 – 연결 중에 방 이동이 불가능 ???• 가장 간단한 해결 방법 – 힌트 PostQueuedCompletionStatus
  27. 27. PostQueuedCompletionStatus• IOCP핸들에 결과를 통보하는 함수 – 소켓 메시지 수신해서 GQCS에서 결과를 받는 것 과 동일한 결과 완료 큐에 결과를 쌓고 쓰레드가 대기 중이면 깨 우기
  28. 28. PostQueuedCompletionStatus• Demultiplexer 역할 while(m_bTerminate == false) { unsigned long dwReadbytes = -1; ULONG_PTR dwCompKey = 0; 이미지 출처 : 위키피디아 OVERLAPPED* s; if (::GetQueuedCompletionStatus(m_hIOCP, &dwReadbytes, &dwCompKey, &s, 1000)) { int idx = GetThreadIndex(s); ::PostQueuedCompletionStatus(m_hThreadIOCP[idx], dwReadbytes, dwCompKey, s); } }
  29. 29. RecvAsync GQCS GQCS#1 GQCS#2 GQCS#3 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync 쓰레드#1 쓰레드#2 쓰레드#3
  30. 30. • 게임 서버 개발에서의 동기화• 직렬화를 통한 잠금 없는 서버 프레임워크• 잠금 없는 서버 구현 사례
  31. 31. 개발 중인 서버 모델 소개• IOCP 멀티쓰레드 모델 기반• 직렬화를 통한 Lockless 서버 아키텍처• DB 동기(blocking)함수 사용• 게임 오브젝트 프레임 워크 – 클라이언트와 유사한 객체 관리 충돌처리, 아이템사용, ...• Http 서버스• UDP P2P 통신
  32. 32. IO 이슈• IO 처리 – 처리 방식 • 동기(blocking) 함수 • 비동기(non-blocking) 함수 – 종류 • DB쿼리, 로그 남기기, 파일 읽기 등등 – 병목!! • 느리다, 예측 불가능하다 • 잠금 상태에서 처리하면 불행한 사태 발생
  33. 33. IO 이슈• 동기함수 vs 비동기 함수 – 결과 • 바로 얻으냐 • 다른 위치에서 얻느냐 – 대기 • 완료 될 때까지 쓰레드가 대기 상태에서 기다리냐 • 완료와 상관없이 다음 처리로 넘어가느냐
  34. 34. IO 이슈• 일반적인 싱글 쓰레드 모델에서 – DB처리는 별도의 서버 혹은 쓰레드에서 – 아이템 리스트 얻기 절차 • 1. DB서버에 아이템 요청 – 보낸 패킷에 받을 세션 정보를 추가 • 2. DB서버에서 처리 • 3. 결과를 게임서버로 전달 • 4. 게임서버에서 DB세션 처리 부에서 전달 받음 • 5. 해당 세션을 찾은 후에 결과를 처리 – 해당 세션의 상태를 고려
  35. 35. IO 이슈• 일반적인 멀티 쓰레드 모델이라면 – 처리가 지연되더라도 다른 처리에 영향을 안 주기 때문에 IO 처리는 바로 처리해도 됨 – 바로 처리 <= 단순하고, 직관적인 처리 void CSession::EnumItem() { CDatabase::CRecordSet r; if (CGameDB::Query(&r, L"exec w_enum_item") == true) { for(int i=0; i<r.Count(); i++) { CPacket item(_SC_LOGIN_SUCCESS); SendPacket(this, item << r.GetString(i, 0) << r.GetInt(i, 1)); } } CPacket p(_SC_END_OF_ITEM); SendPacket(this, p); }
  36. 36. 정책• 명세 – IO를 동기(blocking) 방식으로 직접 처리한다• 하지만 싱글 쓰레드와 마찬가지로 직렬화한 상태에서 동기(blocking)함수를 사용하면 불행 한 일이 생김
  37. 37. 정책• 패킷을 구분 – 게임 관련된 패킷 (직렬화함) • 연관 쓰레드에서 실행 – 아닌 패킷 • 일반 멀티쓰레드 모델처럼 처리• 처리 중에 – 패킷 타입이 다르면 분기 – 쓰레드 할당이 바뀌면 분기
  38. 38. RecvAsync GQCS GQCS 패킷처리 패킷처리 RecvAsync RecvAsync
  39. 39. RecvAsync GQCS GQCS 패킷처리 패킷처리 게임패킷 RecvAsync RecvAsync GQCS#1 GQCS#2 패킷처리 패킷처리 RecvAsync RecvAsync 쓰레드#1 쓰레드#2
  40. 40. 구조• 쓰레드내에서 게임 업데이트 처리 – 완료 큐의 내용을 처리하면서 주기적으로 할당된 Room의 Update함수 호출 • GQCS 타임아웃 활용 – 클라이언트와 유사한 씬 처리 • 동기화 할 필요가 없으므로 클라이언트 만들듯 쉽게~ • 프레임 베이스로 서버 사이드 충돌, 액션, 물리 처리
  41. 41. RecvAsync GQCS GQCS 패킷처리 패킷처리 게임패킷 RecvAsync RecvAsync 오브젝트 GQCS#1 GQCS#2 업데이트 패킷처리 패킷처리 RecvAsync RecvAsync 쓰레드#1 쓰레드#2
  42. 42. 도전과제• Many Integrated Core• 로드 밸런싱 (Load Balancing)
  43. 43. 정리• 직렬화를 통해서 잠금 없는 서버 프레임워크 구성 가능 – 주) 직렬화로 동기화가 필요 없는 부분에 한정 그 외의 부분은 일반 멀티 쓰레드 모델처럼 동기 화 처리 필요 예) 캐주얼 게임의 경우 게임방안의 처리는 잠금 없이 처리 가능하지만 방의 생성과 삭제는 잠금 처리가 필요하다
  44. 44. 정리• 잠금 없는 서버 구조로 얻을 수 있는 것들 – 개발 퍼포먼스↑ – 안정성↑ – 동기화로 인한 복잡도↓ – 직관적인 코드 관리
  45. 45. • 감사합니다
  46. 46. tip• __declspec(thread) 키워드 – 전역처럼 사용하지만 쓰레드마다 할당됨 – 동기화할 필요가 없음 – 패킷 처리시 함수마다 session을 넘기고 있다면 드 라마틱하게 간결한 코드로 만들 수 있음
  47. 47. tip• PQCS 함수 활용 – 별도 메시지 큐와 동기화 처리 없는 비동기(non- blocking) 로그서비스 • GQCS로 받아서 저장하는 쓰레드 하나 운영 • PQCS로 로그 정보 전달 – 몰려도 시스템의 완료 큐에 안전하게 쌓여 있음
  48. 48. tip• PQCS 함수 활용 – 접속이나 종료 등 사후 처리도 쓰레드풀(Thread pool)에서 처리 • 완료키 값을 사전에 정의한 값으로 전달 – 세션정보를 완료키로 전달한다면 Bytes에 비상식적인 큰 값으 로, 혹은 OVERLAPPED 포인터를 임의의 전역변수 주소로 – #define _DESTROY -1234 //::PostQueuedCompletionStatus(handle, _DESTROY, key, NULL); //::PostQueuedCompletionStatus(handle, 0, _DESTROY, &overlapped); //static OVERLAPPED _destroy; //::PostQueuedCompletionStatus(handle, 0, key, &_destroy); • 서버 흐름 관리가 단순 명료해짐

×