• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
NDC12_Lockless게임서버설계와구현
 

NDC12_Lockless게임서버설계와구현

on

  • 4,778 views

NDC2012 발표자료

NDC2012 발표자료

Statistics

Views

Total Views
4,778
Views on SlideShare
4,611
Embed Views
167

Actions

Likes
5
Downloads
85
Comments
0

4 Embeds 167

http://devilchen.tistory.com 136
http://cafe.naver.com 25
http://blog.naver.com 4
https://si0.twimg.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    NDC12_Lockless게임서버설계와구현 NDC12_Lockless게임서버설계와구현 Presentation Transcript

    • Lockless 게임서버 설계와 구현잠금 없는김 성 익noerror@softnette.com소프트네트
    • 개요• 동기화(Synchronication)의 어려움을 극복하기 위해, 구조적으로 잠금(Lock)을 최소화하는 게임 서버 설계 제안
    • • 게임 서버 개발에서의 동기화• 직렬화를 통한 잠금 없는 서버 프레임워크• 잠금 없는 서버 구현 사례
    • 멀티쓰레드 게임서버 모델• IOCP를 사용하는 윈도우 기반의 서버모델 RecvAsync GQCS GQCS GQCS 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync GQCS : GetQueuedCompletionStatus
    • 코드#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); }}
    • 코드#1 Room1 Room1 Room2 Enter Attack Move Move Move Move Attack• 멀티쓰레드 모델 Enter Move Attack Move Move Move Attack
    • 코드#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);}
    • 동기화• 동기화(Synchronization) – 동시 접근 시 생기는 오류 막기 위해 크리티컬 섹 션(Critical section), 뮤텍스(Mutex), 이벤트(Event), 세마포어(Semaphore)등의 동기화 객체를 이용하 여 잠금(lock)처리 Barrier Synchronization • 범위가 클 수록 동시성(concurrency) 감소 Linear <-> Recursive
    • 동기화• 동기화를 올바르게 하지 않으면(#1) – (운이 좋다면) 즉시 다운 – 잘못된 메모리 참조 • 잘못된 값이 복사되서 전파 – 잘못된 결과값 • 트랜젝션(Transaction) 처리 – 전혀 엉뚱한 곳에서 예측이 어려운 형태의 오류로 나타남
    • 동기화• 동기화를 올바르게 하지 않으면(#2) – 잠금 상태에서 IO사용으로 인한 치명적인 성능 저 하 유발 – 데드락(deadlock) 발생
    • 동기화• 동기화 정책의 완전성 요구 – 안정성 측면 – 성능 측면• 지속적인 개발 스트레스 요소 새로운 콘텐츠 도입시 부담감 =>방어적 프로그래밍 =>개발 퍼포먼스 저하• 서버 개발자의 숙명, 실력
    • • 게임 서버 개발에서의 동기화• 직렬화를 통한 잠금 없는 서버 프레임워크• 잠금 없는 서버 구현 사례
    • 싱글 쓰레드 게임 서버 모델• 고정관념을 버려보면 “시스템을 최대한 활용하기 위해서 게임서버 는 멀티 쓰레드 모델이어야 한다”• 게임에 따라서는 꽤 괜찮은 모델
    • 싱글 쓰레드 게임 서버 모델• 단점 – 시스템 활용도 낮음 동시처리가 안되기 때문에 멀티쓰레드 서버 대비 처리량이 적기 때문 에 응답성이 다소 떨어질 수 있다 (latency↑) – 확장성(Scalibility)의 한계 CPU 코어 증가 등 하드웨어 개선을 통한 성능 향상은 미비 하지만 게임의 성격과 맞다면 여러 개의 서버 프로세스 실행은 가능 – 성능 때문에 DB등의 IO작업을 메인 쓰레드에서 직 접 처리할 수 없음 서버를 기능별로 분산(예 DB쿼리서버) 운영 / 비동기 (non-blocking)함 수 사용
    • 싱글 쓰레드 게임 서버 모델• 단점 – 서버를 기능별로 분산한다면 • 처리가 선형적이지 않아 코드가 비직관적 • 알고리즘이 여러 공간으로 분산 • 서버간 통신을 위한 기계적인 코드 작업 • 서버간의 시차로 인한 기계적인 예외 처리
    • 싱글 쓰레드 게임 서버 모델• 장점 – 잠금 처리가 불필요 • 원자성(atomicity), 격리성(isolation) 완벽 보장 – 안정적, 예측 가능 • 메모리가 침범되거나, 알고리즘이 교차 처리되어 꼬이 거나, 데드락 걸릴 확률이 없다
    • 기본 아이디어• 멀티 쓰레드 게임 서버 모델에 싱글 쓰레드 게 임 서버 모델의 장점을 가져온다• 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로 구동
    • 기본 아이디어• 멀티 쓰레드 게임 서버 모델에 싱글 쓰레드 게 임 서버 모델의 장점을 가져온다• 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로 구동 “직렬화” 선형화, 파이프라인, 시퀀스, …
    • 직렬화• 잠금이 필요한 단위로 실행하는 쓰레드를 지 정한다• 캐주얼 게임을 예로 들면 – 홀수 게임방 처리는 Thread#1 에서 – 짝수 게임방 처리는 Thread#2 에서 실행• 주) 여기서 사용하는 직렬화, 선형화는 일반적인 병렬프로그래밍에서 사용 되는 용어(직렬화-배리어, 선형화-원자화)와는 다른 의미입니다. 오해를 없 애기 위해 영문 표기 하지 않습니다.
    • 직렬화 Room1 Room1 Room2 Enter Attack Move Move Move Move Attack• 멀티쓰레드 모델 • 직렬화한 모델 Enter Move Attack Enter Move Move Move Attack Move Attack Move Move Move Attack
    • 코드#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);}
    • 코드#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); }}
    • 장점• 잠금 처리 불필요 – 동시성(concurrency) 증가 – 동기화 처리의 스트레스 일부 해방 • 개발 퍼포먼스 증가 • 새로운 시스템 시도와 도입에 관대해짐 희망사항• 멀티 쓰레드 모델 사용 – 성능, 반응성 – 확장성
    • 구현• 쓰레드 별로 IOCP생성, 소켓은 필요한 쓰레드의 IOCP에 등록RecvAsync GQCS#1 GQCS#2 GQCS#3 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync 쓰레드#1 쓰레드#2 쓰레드#3
    • 구현• 치명적인 문제점 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸 들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하 다 – 연결 중에는 방 이동이 불가능 ???
    • 구현• 치명적인 문제점 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸 들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하 다 – 연결 중에 방 이동이 불가능 ???• 가장 간단한 해결 방법 – 힌트 PostQueuedCompletionStatus
    • PostQueuedCompletionStatus• IOCP핸들에 결과를 통보하는 함수 – 소켓 메시지 수신해서 GQCS에서 결과를 받는 것 과 동일한 결과 완료 큐에 결과를 쌓고 쓰레드가 대기 중이면 깨 우기
    • 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); } }
    • RecvAsync GQCS GQCS#1 GQCS#2 GQCS#3 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync 쓰레드#1 쓰레드#2 쓰레드#3
    • • 게임 서버 개발에서의 동기화• 직렬화를 통한 잠금 없는 서버 프레임워크• 잠금 없는 서버 구현 사례
    • 개발 중인 서버 모델 소개• IOCP 멀티쓰레드 모델 기반• 직렬화를 통한 Lockless 서버 아키텍처• DB 동기(blocking)함수 사용• 게임 오브젝트 프레임 워크 – 클라이언트와 유사한 객체 관리 충돌처리, 아이템사용, ...• Http 서버스• UDP P2P 통신
    • IO 이슈• IO 처리 – 처리 방식 • 동기(blocking) 함수 • 비동기(non-blocking) 함수 – 종류 • DB쿼리, 로그 남기기, 파일 읽기 등등 – 병목!! • 느리다, 예측 불가능하다 • 잠금 상태에서 처리하면 불행한 사태 발생
    • IO 이슈• 동기함수 vs 비동기 함수 – 결과 • 바로 얻으냐 • 다른 위치에서 얻느냐 – 대기 • 완료 될 때까지 쓰레드가 대기 상태에서 기다리냐 • 완료와 상관없이 다음 처리로 넘어가느냐
    • IO 이슈• 일반적인 싱글 쓰레드 모델에서 – DB처리는 별도의 서버 혹은 쓰레드에서 – 아이템 리스트 얻기 절차 • 1. DB서버에 아이템 요청 – 보낸 패킷에 받을 세션 정보를 추가 • 2. DB서버에서 처리 • 3. 결과를 게임서버로 전달 • 4. 게임서버에서 DB세션 처리 부에서 전달 받음 • 5. 해당 세션을 찾은 후에 결과를 처리 – 해당 세션의 상태를 고려
    • 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); }
    • 정책• 명세 – IO를 동기(blocking) 방식으로 직접 처리한다• 하지만 싱글 쓰레드와 마찬가지로 직렬화한 상태에서 동기(blocking)함수를 사용하면 불행 한 일이 생김
    • 정책• 패킷을 구분 – 게임 관련된 패킷 (직렬화함) • 연관 쓰레드에서 실행 – 아닌 패킷 • 일반 멀티쓰레드 모델처럼 처리• 처리 중에 – 패킷 타입이 다르면 분기 – 쓰레드 할당이 바뀌면 분기
    • RecvAsync GQCS GQCS 패킷처리 패킷처리 RecvAsync RecvAsync
    • RecvAsync GQCS GQCS 패킷처리 패킷처리 게임패킷 RecvAsync RecvAsync GQCS#1 GQCS#2 패킷처리 패킷처리 RecvAsync RecvAsync 쓰레드#1 쓰레드#2
    • 구조• 쓰레드내에서 게임 업데이트 처리 – 완료 큐의 내용을 처리하면서 주기적으로 할당된 Room의 Update함수 호출 • GQCS 타임아웃 활용 – 클라이언트와 유사한 씬 처리 • 동기화 할 필요가 없으므로 클라이언트 만들듯 쉽게~ • 프레임 베이스로 서버 사이드 충돌, 액션, 물리 처리
    • RecvAsync GQCS GQCS 패킷처리 패킷처리 게임패킷 RecvAsync RecvAsync 오브젝트 GQCS#1 GQCS#2 업데이트 패킷처리 패킷처리 RecvAsync RecvAsync 쓰레드#1 쓰레드#2
    • 도전과제• Many Integrated Core• 로드 밸런싱 (Load Balancing)
    • 정리• 직렬화를 통해서 잠금 없는 서버 프레임워크 구성 가능 – 주) 직렬화로 동기화가 필요 없는 부분에 한정 그 외의 부분은 일반 멀티 쓰레드 모델처럼 동기 화 처리 필요 예) 캐주얼 게임의 경우 게임방안의 처리는 잠금 없이 처리 가능하지만 방의 생성과 삭제는 잠금 처리가 필요하다
    • 정리• 잠금 없는 서버 구조로 얻을 수 있는 것들 – 개발 퍼포먼스↑ – 안정성↑ – 동기화로 인한 복잡도↓ – 직관적인 코드 관리
    • • 감사합니다
    • tip• __declspec(thread) 키워드 – 전역처럼 사용하지만 쓰레드마다 할당됨 – 동기화할 필요가 없음 – 패킷 처리시 함수마다 session을 넘기고 있다면 드 라마틱하게 간결한 코드로 만들 수 있음
    • tip• PQCS 함수 활용 – 별도 메시지 큐와 동기화 처리 없는 비동기(non- blocking) 로그서비스 • GQCS로 받아서 저장하는 쓰레드 하나 운영 • PQCS로 로그 정보 전달 – 몰려도 시스템의 완료 큐에 안전하게 쌓여 있음
    • 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); • 서버 흐름 관리가 단순 명료해짐