높은 동시 서버 설계 - 연결 풀 설계

고병렬 서버는 메모리 탱크, 연결 탱크, 데이터베이스 연결 탱크 등 일부 탱크의 설계를 필요로 한다.
풀(pool)의 디자인은 주로 일부 자원의 빈번한 신청과 방출을 고려한다. 특히 높은 병발 서버에서 수만, 심지어 수십만 개의 병발 초마다 디자이너들은 이런 것들을 고려해야 한다.
예를 들어 데이터베이스 연결 탱크(sqlpool)는 TCP를 통해 통신하는 것으로 IO류에 속하며 일정한 지연이 있어 높은 병발 시스템에서 빈번한 창설은 시스템 성능에 심각한 영향을 줄 수 있다.
메모리(mem)의 분배는 자물쇠(mutex)와 관련된 것이기 때문에 자물쇠가 있으면 시간이 지연되기 때문에 큰 메모리를 신청하고 그 다음에 분배와 방출을 해서 자물쇠 비용을 절약할 수 있다.
서버의 연결 처리는 메모리뿐만 아니라 일부 속성의 값도 관련된다. 이것은 CPU 시간을 차지하는 것이다. 만약에 처음부터 대량의 연결을 만들면 나중에 다시 사용하기 편리하다.
다음은 데이터베이스 연결 풀을 예로 들어 연결의 구조를 정의합니다.
typedef struct tst_sql_s tst_sql_t;
struct tst_sql_s{
	MYSQL     *sql;
	tst_sql_t   *next;
	tst_sql_t   *prev;
};

현실 개발에서 나는 (free-busi) 모델로 연못을 설계하는 것을 좋아한다는 것을 발견했다.
struct  tst_sql_pool_s
{
	tst_sql_t *free_sql;
	tst_sql_t *busi_sql;
	…
};

풀의 연결을 두 부분으로 나누면 일부는 빈 (free), 일부는 사용 중인 (busi), 함수:
tst_sql_t* tst_sql_pool_get( tst_sql_pool_t* pool )
{
	tst_sql_t *sql;
	if( !pool ){
		return 0;
	}
	
	sql = pool->free_sql;
	
	if( !sql ){
		return 0;
	}

	pool->free_sql = sql->next;
	sql->next = pool->busi_sql;
	sql->prev = 0;
	if( pool->busi_sql ){
		pool->busi_sql->prev = sql;
	}
	pool->busi_sql = sql;
	
	return sql;
}

int tst_sql_pool_put( tst_sql_pool_t* pool, tst_sql_t* sql )
{
	if( !pool || !sql ){
		return 0;
	}

	if( sql->prev ){
		sql->prev->next = sql->next;
	}
	else{
		pool->busi_sql = sql->next;
	}

	if( sql->next ){
		sql->next->prev = sql->prev;
	}

	sql->next = pool->free_sql;
	pool->free_sql = sql;
	
	return 0;
}

기본적으로 연못의 관리를 마쳤지만, 우리도 이 판단이 사실 매우 번거롭다는 것을 알 수 있다. 이렇게 번거롭게 하지 않아도 되는지 모르겠다.
위의 함수에서도 알 수 있듯이 번거로움은 주로 busi못에 있다. free못의 처리는 사실 매우 간단하기 때문에 다음과 같은 디자인이 있다.
연결 탱크는 빈 연결만 저장하고 연결 상태를 저장하지 않으며 상태의 구분을 관리 함수에 맡겨야 한다.
다음은 연결 탱크로 예를 들겠습니다.
나는 연결 탱크의 구조를 다시 설계했다.
typedef struct tst_conn_s tst_conn_t;
typedef struct tst_conn_pool_s tst_conn_pool_t;
struct tst_conn_s
{
	int  fd;
	……..
	……..
	tst_conn_t* next;
};

struct tst_conn_pool_s
{
	………
	……….
	tst_conn_t*  conns;
};

풀 관리 함수:
tst_conn_t* tst_conn_pool_get( tst_conn_pool_t* pool )
{
	tst_conn_t* conn;

	if( !pool ){
		return 0;
	}

	conn = pool->conns;
	if( !conn ){
		return 0;
	}

	pool->conns = conn->next;
	
	return conn;

}
#define TST_CONN_POOL_ERROR -1
#define TST_CONN_POOL_OK 0

int tst_conn_pool_put( tst_conn_pool_t* pool, tst_conn_t* conn )
{
	if( !pool || !conn ){
		return TST_CONN_POOL_ERROR;
	}

	conn->next = pool->conns;
	pool->conns = conn;
	
	return TST_CONN_POOL_OK;
}	

이렇게 하면 연결 탱크의 분배와 회수 기능을 한다.
일반적으로 디자인에 있어서 모듈의 투명성을 높이고 결합을 낮춘다. 나는 탱크의 관리를 모듈 내부에 놓고 대외적으로 일치성 인터페이스만 제공한다.
#define TST_CONN_POOL_ERROR -1
#define TST_CONN_POOL_OK 0
tst_conn_t* tst_conn_get();
int tst_conn_free( tst_conn_t* conn );

모듈 내부는 하나의 전역적인 탱크로 모듈 내에서 통일된 관리를 한다.

좋은 웹페이지 즐겨찾기