본문 바로가기

Java/Vert.x

버텍스 코어: TCP 서버 동시성 확장을 위한 배포 방법

TCP 서버 중복 배포

TCP 서버의 핸들러는 항상 동일한 이벤트 루프 스레드(Event loop thread)에서의 실행을 보장합니다.

코어가 아무리 많은 서버일지라도 버티클 인스턴스가 하나만 배포된 TCP 서버의 경우 코어 하나만 사용하는 문제가 있습니다.

이 문제는 단순하지만 TCP 서버를 더 많이 실행하는 것으로 해결 할 수 있습니다.

for (int i = 0; i < 10; i++) {
	NetServer server = vertx.createNetServer();
	server.connectHandler(socket -> {
		socket.handler(buffer -> {
			// Just echo back the data
			socket.write(buffer);
		});
	});

	server.listen(100200, "localhost");
}

다른 방법으로는 TCP 서버를 실행하는 버티클 인스턴스 자체를 여러 개 배포하는 것입니다.

DeploymentOptions options = new DeploymentOptions().setInstances(10);
vertx.deployVerticle("com.mycompany.MyVerticle", options);

서버의 기능은 동일하지만 더 많은 이벤트 루프 스레드에서 실행 될 수 있습니다.

더 많은 이벤트 루프를 사용하는 것은 더 많은 코어를 활용하는 것과 같은 효과를 낼 수 있습니다.

더보기

사실 하나의 호스트에서는 포트를 중복 사용 할 수 없습니다.

Vertx에서는 이미 생성된 서버와 동일한 호스트 및 포트를 사용하는 경우 서버를 중복 생성하지 않습니다.

내부에서는 하나의 서버를 유지하는 대신, 동일한 호스트와 포트로 연결이 발생하면 여러 서버들에서 라운드 로빈(Round-robin)으로 실행됩니다.

각 서버(스레드)는 싱글 스레드로 유지되지만, 여러 개의 코어를 동시 사용하는 효과를 낼 수 있는 이유입니다.

Step 1: TCP 서버 생성

TCP 서버를 중복 배포하고 여러 개의 이벤트 루프 스레드에서 코드가 실행 되는 것을 확인합니다.

우선 TCP 서버를 생성하는 TCPServer 버티클을 작성합니다.

public class TCPServer extends AbstractVerticle 
{
	public final static Logger logger = LoggerFactory.getLogger(TCPServer.class);

	@Override
	public void start(Promise<Void> _startPromise) throws Exception {
		NetServer server = vertx.createNetServer();
		server.connectHandler(socket -> {
			logger.info(String.format("socket connected, thread id is %d, name is %s", Thread.currentThread().getId(), Thread.currentThread().getName()));
		});
		
		server.listen(8080);
		_startPromise.complete();
	}
}
코드 비고
Line 7:10 TCP 서버 생성 TCP 서버를 생성하고 연결이 맺어지면 스레드 ID를 로깅합니다.
Line 12 listen() TCP 서버를 8080 포트에 대해서 listen 상태로 만듭니다.

Step 2: TCP 클라이언트 생성

TCP 클라이언트를 생성하는 TCPClient 버티클을 작성합니다.

public class TCPClient extends AbstractVerticle 
{
	@Override
	public void start(Promise<Void> _startPromise) throws Exception {
		NetClient client = vertx.createNetClient();
		client.connect(8080, "localhost", ar -> {
			if (ar.failed()) {
				ar.cause().printStackTrace();
			} else {
				NetSocket sock = ar.result();
				sock.close();
			}
		});
        
		_startPromise.complete();
	}
}
코드 비고
Line 5:13 TCP 클라이언트 생성 TCP 클라이언트를 생성하고 8080 포트의 TCP 서버에 연결합니다.
연결이 성공하면 소켓을 즉시 Close합니다.

Step 3: 버티클 배포 및 실행 확인

Step 1과 Step 2에서 작성한 TCPServer 버티클TCPClient 버티클을 배포합니다.

배포 옵션(DeploymentOptions)에서 인스턴스의 수량을 지정하기 위해 setInstances()를 사용합니다.

public class Launcher extends AbstractVerticle 
{
	@Override
	public void start(Promise<Void> _startPromise) throws Exception {
		DeploymentOptions opts = new DeploymentOptions().setInstances(10);
		getVertx().deployVerticle(TCPClient.class.getName(), opts);
		getVertx().deployVerticle(TCPServer.class.getName(), opts);
        
		_startPromise.complete();
	}
}
코드 비고
Line 5 setInstances() 배포 옵션(DeploymentOptions)에서 인스턴스의 수량 10개로 지정합니다.
Line 6:7 deployVerticle() TCPServer 버티클 TCPClient 버티클을 배포합니다.
각 버티클은 배포 옵션에 의해서 10개씩 배포됩니다.

실행 결과를 확인합니다. TCPServer 버티클에서 클라이언트의 연결이 맺어지며 로그가 기록됩니다.

각 로그에서 기록되는 스레드 ID가 서로 다른 것을 확인 할 수 있습니다.

동일한 호스트포트에서 실행된 TCP 서버가 클라이언트의 연결을 라운드 로빈으로 수신하고 있는 것을 알 수 있습니다.

[2022-03-14 03:16:14.559][정보]socket connected, thread id is 33, name is vert.x-eventloop-thread-15 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 34, name is vert.x-eventloop-thread-16 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 36, name is vert.x-eventloop-thread-18 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 39, name is vert.x-eventloop-thread-21 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 30, name is vert.x-eventloop-thread-12 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 32, name is vert.x-eventloop-thread-14 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 35, name is vert.x-eventloop-thread-17 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 37, name is vert.x-eventloop-thread-19 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 31, name is vert.x-eventloop-thread-13 
[2022-03-14 03:16:14.559][정보]socket connected, thread id is 38, name is vert.x-eventloop-thread-20

정리 및 복습

  • TCP 서버의 핸들러는 항상 동일한 이벤트 루프 스레드에서 실행됩니다.
  • 이벤트 루프 스레드를 하나만 사용하면 싱글 코어 프로그램과 같습니다.
  • Vertx에서는 동일한 호스트와 포트로 서버 생성을 허용합니다.
  • 이때 클라이언트의 연결은 라운드 로빈으로 여러 서버들에 분배됩니다.
  • TCP 서버의 동시성을 높이기 위해서 단순히 TCP 서버를 더 많이 생성하는 것으로 해결 할 수 있습니다.