개발하기 전에 읽어야 할 내용

Workerman을 사용하여 애플리케이션을 개발하기 위해서는 다음 내용을 이해해야 합니다.

1. Workerman 개발과 일반적인 PHP 개발의 차이점

HTTP 프로토콜 관련 변수 및 함수를 직접 사용할 수 없다는 점을 제외하고, Workerman 개발과 일반적인 PHP 개발 간에 큰 차이가 없습니다.

1) 응용 계층 프로토콜의 차이

  • 일반적인 PHP 개발은 일반적으로 HTTP 응용 계층 프로토콜을 기반으로 하며, WebServer가 프로토콜 분석을 완료했습니다.
  • Workerman은 HTTP, WebSocket 등 다양한 프로토콜을 지원하며, 현재는 HTTP, WebSocket과 같은 프로토콜이 내장되어 있습니다. Workerman은 개발자가 더 간단한 사용자 정의 프로토콜을 권장합니다.

2) 요청 주기의 차이

  • PHP는 웹 애플리케이션에서 한 번의 요청 이후에 모든 변수 및 리소스를 해제합니다.
  • Workerman으로 개발된 응용 프로그램은 처음 로드 및 분석된 이후에 메모리에 상주하여, 클래스 정의, 전역 객체, 클래스의 정적 멤버가 해제되지 않아 나중에 재사용하기 쉽습니다.

3) 클래스 및 상수 중복 정의에 대한 주의

  • Workerman은 PHP 파일을 컴파일하여 캐시하므로 동일한 클래스 또는 상수 정의 파일을 여러 번 require/include하는 것을 피해야 합니다. require_once/include_once를 사용하는 것이 좋습니다.

4) 싱글톤 모드 연결 리소스 해제에 대한 주의

  • Workerman은 각 요청 후에 전역 객체 및 클래스의 정적 멤버를 해제하지 않기 때문에, 데이터베이스와 같은 싱글톤 모드에서 일반적으로 데이터베이스 인스턴스(내부에 데이터베이스 소켓 연결이 포함)를 데이터베이스의 정적 멤버에 저장하여 Workerman이 프로세스 라이프 사이클 동안 이 데이터베이스 소켓 연결을 재사용합니다. 다만 데이터베이스 서버가 일정 시간 동안 활동하지 않을 경우 소켓 연결을 자동으로 닫을 수 있으므로, 이 데이터베이스 인스턴스를 다시 사용하면 오류가 발생할 수 있습니다(예: mysql gone away와 같은 오류). Workerman은 데이터베이스 클래스를 제공하여 재연결 기능을 제공하므로 개발자가 직접 사용할 수 있습니다.

5) exit 및 die 문 사용에 대한 주의

  • Workerman은 PHP 명령행 모드에서 실행되며, exit 또는 die 종료 문을 호출하면 현재 프로세스가 종료됩니다. 하위 프로세스가 종료된 후에는 즉시 동일한 하위 프로세스가 다시 생성되지만 여전히 비지니스에 영향을 미칠 수 있습니다.

6) 코드를 변경한 후에는 서비스를 다시 시작해야 함

Workerman은 메모리 상주형이므로 PHP 클래스 및 함수 정의를 한 번 로드한 후에는 메모리에 상주하므로 비즈니스 코드를 변경할 때마다 다시 시작해야 합니다.

2. 이해해야 할 기본 개념

1) TCP 전송 계층 프로토콜

TCP는 IP 기반의 연결 지향적이고 안정적인 전송 계층 프로토콜입니다. TCP 전송 계층 프로토콜의 중요한 특징 중 하나는 데이터 스트림을 기반으로 하며, 클라이언트의 요청은 계속해서 서버로 전송되어 서버가 받은 데이터가 완전한 요청일 수도 있고 여러 요청이 연속되어 있을 수도 있습니다. 이에 대응하여 응용 계층 프로토콜은 요청 경계를 정의하는 규칙 집합을 정의해야 합니다.

2) 응용 계층 프로토콜

응용 계층 프로토콜(application layer protocol)은 클라이언트 및 서버와 같은 다른 종단 시스템에서 어플리케이션 프로세스 간에 메시지를 어떻게 전달할지를 정의합니다. HTTP, WebSocket 등은 응용 계층 프로토콜의 예입니다. 예를 들어, 간단한 응용 계층 프로토콜은 {"module":"user","action":"getInfo","uid":456}\n와 같을 수 있습니다. 이 프로토콜은 "\n"(여기서 "\n"은 개행을 의미함)로 요청의 끝을 표시하며, 메시지 바디는 문자열입니다.

3) 단절 연결

단절 연결이란 통신하는 양쪽이 데이터를 주고받을 때마다 연결을 하나 만들고, 데이터 전송이 완료되면 연결을 닫는 것입니다. HTTP 서비스의 경우 대개 단절 연결을 사용합니다.

단절 연결 응용 프로그램 개발은 기본 개발 프로세스를 참고할 수 있습니다.

4) 장시간 연결

장시간 연결은 하나의 연결에서 여러 데이터 패킷을 연속해서 보낼 수 있는 것을 의미합니다.

참고: 장시간 연결 응용은 하트비트가 필요하며, 그렇지 않으면 연결이 장시간 활성화되지 않아 방화벽에서 연결이 끊길 수 있습니다.

장시간 연결은 빈번한 조작과 point-to-point 통신의 경우 많이 사용됩니다. 각 TCP 연결은 세 번의 핸드쉐이크가 필요하며, 시간이 소요되므로 각 조작 후에 연결을 끊지 않고, 다음에 데이터 패킷을 직접 전송하여 TCP 연결을 설정할 필요가 없습니다. 예를 들어, 데이터베이스 연결은 장시간 유지되어야 하며, 단절된 연결을 사용하면 소켓 오류가 발생하게 될 뿐만 아니라 소켓을 빈번하게 만드는 것은 리소스 낭비입니다.

클라이언트에 데이터를 알릴 필요가 있는 경우, 예를 들어 채팅 애플리케이션, 실시간 게임, 모바일 푸시 등과 같은 응용에는 장시간 연결이 필요합니다.
장시간 연결 응용 프로그램 개발은 게이트웨이/워커 개발 프로세스를 참고할 수 있습니다.

5) 부드러운 재시작

일반적인 재시작은 모든 프로세스를 중지한 후 새로운 서비스 프로세스를 시작하는 과정입니다. 이 과정에서 프로세스가 일시적으로 서비스를 제공하지 않은 짧은 시간이 있을 수 있으며, 이는 고속 요청시에는 요청 실패로 이어질 수 있습니다.

반면, 부드러운 재시작은 모든 프로세스를 동시에 중지하는 대신 하나의 프로세스씩 중지한 후 즉시 새로운 프로세스로 교체하며, 모든 이전 프로세스가 교체될 때까지 계속됩니다.

Workerman은 php your_file.php reload 명령을 사용하여 서비스 품질에 영향을 미치지 않고 응용 프로그램을 업데이트할 수 있습니다.

참고: on{...} 콜백 함수가 로드된 파일은 부드러운 재시작 후 자동으로 업데이트되며, 시작 스크립트에서 직접 로드된 파일 또는 하드 코딩된 코드는 재시작 시 자동으로 업데이트되지 않습니다.

3. 메인 프로세스와 하위 프로세스의 구분

코드가 메인 프로세스에서 실행되는지 아니면 하위 프로세스에서 실행되는지를 신경써야 합니다. 일반적으로 Worker::runAll(); 호출 전에 실행되는 코드는 메인 프로세스에서 실행되며, onXXX 콜백에서 실행되는 코드는 하위 프로세스에 속합니다. Worker::runAll(); 뒤의 코드는 언제나 실행되지 않습니다.

예를 들어, 아래와 같은 코드입니다.

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

// 메인 프로세스에서 실행됩니다.
$tcp_worker = new Worker("tcp://0.0.0.0:2347");

// 할당 과정은 메인 프로세스에서 실행됩니다.
$tcp_worker->onMessage = function(TcpConnection $connection, $data)
{
    // 이 부분은 하위 프로세스에서 실행됩니다.
    $connection->send('hello ' . $data);
};

Worker::runAll();

참고: 메인 프로세스에서 데이터베이스, 메모캐시, 레디스 등 연결 리소스를 초기화해서는 안 됩니다. 왜냐하면 메인 프로세스에서 초기화된 연결은 하위 프로세스가 자동으로 상속하게 되며, 모든 프로세스가 동일한 연결을 가지게 됩니다. 서버는 같은 연결을 통해 반환된 데이터를 다른 모든 프로세스에서 읽을 수 있게 되어 데이터 혼선을 일으킬 수 있습니다. 마찬가지로 어느 한 프로세스가 연결을 닫으면(데몬 모드에서 메인 프로세스가 종료되는 경우 연결이 닫힙니다), 모든 하위 프로세스의 연결도 함께 닫히며 예상치 못한 오류(예: mysql gone away 오류)가 발생합니다.

연결 리소스를 초기화하는 것은 onWorkerStart 안에서 권장합니다.