MySQL 네이티브 드라이버 플러그인 아키텍처

이 섹션에서는 mysqlnd 플러그인 아키텍처에 대한 개요를 제공합니다.

MySQL 기본 드라이버 개요

mysqlnd 플러그인을 개발하기 전에 mysqlnd 자체가 어떻게 구성되어 있는지 약간 아는 것이 유용합니다. Mysqlnd는 다음 모듈로 구성됩니다.

모듈당 mysqlnd 조직도

Modules Statistics mysqlnd_statistics.c
Connection mysqlnd.c
Resultset mysqlnd_result.c
Resultset Metadata mysqlnd_result_meta.c
Statement mysqlnd_ps.c
Network mysqlnd_net.c
Wire protocol mysqlnd_wireprotocol.c

C 객체 지향 패러다임

코드 수준에서 mysqlnd는 객체 지향을 구현하기 위해 C 패턴을 사용합니다.

C에서는 struct를 사용하여 객체를 나타냅니다. 구조체의 멤버는 개체 속성을 나타냅니다. 함수를 가리키는 구조체 멤버는 메서드를 나타냅니다.

C++ 또는 Java와 같은 다른 언어와 달리 C 객체 지향 패러다임에는 상속에 대한 고정 규칙이 없습니다. 그러나 나중에 논의될 일부 규칙을 따라야 합니다.

PHP 라이프 사이클

PHP 수명 주기를 고려할 때 두 가지 기본 주기가 있습니다.

  • PHP 엔진 시작 및 종료 주기
  • 요청 주기

PHP 엔진이 시작되면 등록된 각 확장의 모듈 초기화(MINIT) 함수를 호출합니다. 이를 통해 각 모듈은 변수를 설정하고 PHP 엔진 프로세스의 수명 동안 존재할 리소스를 할당할 수 있습니다. PHP 엔진이 종료되면 각 확장의 모듈 종료(MSHUTDOWN) 함수를 호출합니다.

PHP 엔진의 수명 동안 많은 요청을 받습니다. 각 요청은 또 다른 수명 주기를 구성합니다. 각 요청에서 PHP 엔진은 각 확장의 요청 초기화 함수를 호출합니다. 확장은 요청 처리에 필요한 모든 변수 설정 및 리소스 할당을 수행할 수 있습니다. 요청 주기가 끝나면 엔진이 각 확장의 요청 종료(RSHUTDOWN) 함수를 호출하여 확장이 필요한 정리를 수행할 수 있도록 합니다.

플러그인 작동 방식

mysqlnd 플러그인은 mysqlnd를 사용하는 확장에 의해 mysqlnd에 대한 호출을 가로채서 작동합니다. 이것은 mysqlnd 함수 테이블을 가져와서 백업하고 필요에 따라 플러그인의 함수를 호출하는 사용자 정의 함수 테이블로 교체함으로써 달성됩니다.

다음 코드는 mysqlnd 함수 테이블이 대체되는 방법을 보여줍니다.

/* a place to store original function table */
struct st_mysqlnd_conn_methods org_methods;

void minit_register_hooks(TSRMLS_D) {
  /* active function table */
  struct st_mysqlnd_conn_methods * current_methods
    = mysqlnd_conn_get_methods();

  /* backup original function table */
  memcpy(&org_methods, current_methods,
    sizeof(struct st_mysqlnd_conn_methods);

  /* install new methods */
  current_methods->query = MYSQLND_METHOD(my_conn_class, query);
}
                

연결 함수 테이블 조작은 모듈 초기화(MINIT) 중에 수행되어야 합니다. 함수 테이블은 전역 공유 리소스입니다. TSRM 빌드를 사용하는 다중 스레드 환경에서 요청 처리 중 전역 공유 리소스를 조작하면 거의 확실히 충돌이 발생합니다.

메모: mysqlnd 함수 테이블을 조작할 때 고정 크기 논리를 사용하지 마십시오. 새 메소드가 함수 테이블 끝에 추가될 수 있습니다. 함수 테이블은 향후 언제든지 변경될 수 있습니다.

부모 메서드 호출

원래 함수 테이블 항목이 백업된 경우에도 원래 함수 테이블 항목(부모 메서드)을 호출할 수 있습니다.

Connection::stmt_init()과 같은 일부 경우에는 파생된 메서드의 다른 활동보다 먼저 부모 메서드를 호출하는 것이 중요합니다.

MYSQLND_METHOD(my_conn_class, query)(MYSQLND *conn,
  const char *query, unsigned int query_len TSRMLS_DC) {

  php_printf("my_conn_class::query(query = %s)\n", query);

  query = "SELECT 'query rewritten' FROM DUAL";
  query_len = strlen(query);

  return org_methods.query(conn, query, query_len); /* return with call to parent */
}
                

속성 확장

mysqlnd 객체는 C 구조체로 표현됩니다. 런타임에 C 구조체에 멤버를 추가할 수 없습니다. mysqlnd 객체의 사용자는 단순히 객체에 속성을 추가할 수 없습니다.

mysqlnd_plugin_get_plugin_<object>_data() 패밀리의 적절한 함수를 사용하여 임의의 데이터(속성)를 mysqlnd 객체에 추가할 수 있습니다. 객체를 할당할 때 mysqlnd는 임의의 데이터에 대한 void * 포인터를 보유하기 위해 객체 끝에 공간을 예약합니다. mysqlnd는 플러그인당 하나의 void * 포인터를 위한 공간을 예약합니다.

다음 표는 특정 플러그인에 대한 포인터 위치를 계산하는 방법을 보여줍니다.

mysqlnd에 대한 포인터 계산

Memory address Contents
0 mysqlnd 객체 C 구조체의 시작
n mysqlnd 객체 C 구조체의 끝
n + (m x sizeof(void*)) m 번째 플러그인의 객체 데이터에 대한 void*

허용되는 mysqlnd 객체 생성자 중 하나를 하위 클래스로 만들 계획이라면 이 점을 염두에 두어야 합니다!

다음 코드는 속성 확장을 보여줍니다.

/* any data we want to associate */
typedef struct my_conn_properties {
  unsigned long query_counter;
} MY_CONN_PROPERTIES;

/* plugin id */
unsigned int my_plugin_id;

void minit_register_hooks(TSRMLS_D) {
  /* obtain unique plugin ID */
  my_plugin_id = mysqlnd_plugin_register();
  /* snip - see Extending Connection: methods */
}

static MY_CONN_PROPERTIES** get_conn_properties(const MYSQLND *conn TSRMLS_DC) {
  MY_CONN_PROPERTIES** props;
  props = (MY_CONN_PROPERTIES**)mysqlnd_plugin_get_plugin_connection_data(
    conn, my_plugin_id);
  if (!props || !(*props)) {
    *props = mnd_pecalloc(1, sizeof(MY_CONN_PROPERTIES), conn->persistent);
    (*props)->query_counter = 0;
  }
  return props;
}
                

플러그인 개발자는 플러그인 데이터 메모리 관리를 담당합니다.

플러그인 데이터에는 mysqlnd 메모리 할당자를 사용하는 것이 좋습니다. 이러한 함수는 mnd_*loc() 규칙을 사용하여 이름이 지정됩니다. mysqlnd 할당자는 디버그가 아닌 빌드에서 디버그 할당자를 사용하는 기능과 같은 몇 가지 유용한 기능을 가지고 있습니다.

서브클래싱 시기와 방법

  서브클래스는 언제? 각 인스턴스에는 자체 전용 함수 테이블이 있습니까? 어떻게 서브클래스를 만들 것인가?
Connection (MYSQLND) MINIT No mysqlnd_conn_get_methods()
Resultset (MYSQLND_RES) MINIT or later Yes mysqlnd_result_get_methods() or object method function table manipulation
Resultset Meta (MYSQLND_RES_METADATA) MINIT No mysqlnd_result_metadata_get_methods()
Statement (MYSQLND_STMT) MINIT No mysqlnd_stmt_get_methods()
Network (MYSQLND_NET) MINIT or later Yes mysqlnd_net_get_methods() or object method function table manipulation
Wire protocol (MYSQLND_PROTOCOL) MINIT or later Yes mysqlnd_protocol_get_methods() or object method function table manipulation

위의 표에 따라 허용되지 않는 경우 MINIT 이후에는 함수 테이블을 조작해서는 안 됩니다.

일부 클래스에는 메서드 함수 테이블에 대한 포인터가 포함되어 있습니다. 이러한 클래스의 모든 인스턴스는 동일한 함수 테이블을 공유합니다. 특히 스레드 환경에서 혼돈을 피하기 위해 이러한 함수 테이블은 MINIT 중에만 조작되어야 합니다.

다른 클래스는 전역적으로 공유되는 함수 테이블의 복사본을 사용합니다. 클래스 함수 테이블 사본이 객체와 함께 생성됩니다. 각 개체는 자체 함수 테이블을 사용합니다. 이렇게 하면 두 가지 옵션이 제공됩니다. MINIT에서 개체의 기본 함수 테이블을 조작할 수 있고 동일한 클래스의 다른 인스턴스에 영향을 주지 않고 개체의 메서드를 추가로 구체화할 수 있습니다.

공유 함수 테이블 접근 방식의 장점은 성능입니다. 각각의 모든 개체에 대해 함수 테이블을 복사할 필요가 없습니다.

생성자 상태

Type Allocation, construction, reset Can be modified? Caller
Connection (MYSQLND) mysqlnd_init() No mysqlnd_connect()
Resultset(MYSQLND_RES)

Allocation:

  • Connection::result_init()

Reset and re-initialized during:

  • Result::use_result()
  • Result::store_result
Yes, but call parent!
  • Connection::list_fields()
  • Statement::get_result()
  • Statement::prepare() (Metadata only)
  • Statement::resultMetaData()
Resultset Meta (MYSQLND_RES_METADATA) Connection::result_meta_init() Yes, but call parent! Result::read_result_metadata()
Statement (MYSQLND_STMT) Connection::stmt_init() Yes, but call parent! Connection::stmt_init()
Network (MYSQLND_NET) mysqlnd_net_init() No Connection::init()
Wire protocol (MYSQLND_PROTOCOL) mysqlnd_protocol_init() No Connection::init()

생성자를 완전히 바꾸지 않는 것이 좋습니다. 생성자는 메모리 할당을 수행합니다. 메모리 할당은 mysqlnd 플러그인 API와 mysqlnd의 객체 로직에 매우 중요합니다. 경고에 신경 쓰지 않고 생성자를 연결해야 한다고 주장하는 경우 생성자에서 작업을 수행하기 전에 최소한 부모 생성자를 호출해야 합니다.

모든 경고에 관계없이 생성자를 하위 클래스로 지정하는 것이 유용할 수 있습니다. 생성자는 결과 집합, 네트워크, 유선 프로토콜과 같은 비공유 개체 테이블이 있는 개체의 함수 테이블을 수정하기에 완벽한 장소입니다.

Destruction status

Type Derived method must call parent? Destructor
Connection yes, after method execution free_contents(), end_psession()
Resultset yes, after method execution free_result()
Resultset Meta yes, after method execution free()
Statement yes, after method execution dtor(), free_stmt_content()
Network yes, after method execution free()
Wire protocol yes, after method execution free()

소멸자는 속성을 해제하는 적절한 위치인 mysqlnd_plugin_get_plugin_<object>_data()입니다.

나열된 소멸자는 객체 자체를 해제하는 실제 mysqlnd 메소드와 동일하지 않을 수 있습니다. 그러나 플러그인 데이터를 연결하고 해제할 수 있는 가장 좋은 장소입니다. 생성자와 마찬가지로 메서드를 완전히 바꿀 수 있지만 권장하지 않습니다. 위의 표에 여러 메서드가 나열되어 있으면 나열된 메서드를 모두 연결하고 mysqlnd에서 먼저 호출한 메서드에서 플러그인 데이터를 해제해야 합니다.

플러그인에 권장되는 방법은 단순히 메서드를 연결하고 메모리를 해제한 다음 바로 다음에 부모 구현을 호출하는 것입니다.