OpenResty에서 자주 사용되는 세 가지 Lua Resty 라이브러리
API7.ai
January 13, 2023
프로그래밍 언어와 플랫폼에 대해 배우는 것은 종종 구문 자체보다는 표준 및 서드파티 라이브러리를 이해하는 문제입니다. API와 성능 최적화 기술을 배운 후에는 다양한 lua-resty
라이브러리의 사용법을 배워 OpenResty의 기능을 더 많은 시나리오로 확장해야 합니다.
lua-resty
라이브러리를 어디서 찾을 수 있나요?
PHP, Python, JavaScript에 비해 현재 OpenResty의 표준 및 서드파티 라이브러리는 여전히 상대적으로 빈약하며, 적절한 lua-resty
라이브러리를 찾는 것은 쉽지 않습니다. 그러나 여전히 더 빠르게 찾을 수 있도록 도와줄 두 가지 추천 소스가 있습니다.
첫 번째 추천은 Aapo가 유지 관리하는 awesome-resty
저장소입니다. 이 저장소는 OpenResty 관련 라이브러리를 카테고리별로 정리하고 있으며, NGINX C 모듈, lua-resty
라이브러리, 웹 프레임워크, 라우팅 라이브러리, 템플릿, 테스트 프레임워크 등을 모두 포함하고 있습니다. OpenResty 리소스를 찾기 위한 첫 번째 선택지입니다.
Aapo의 저장소에서 적절한 라이브러리를 찾지 못했다면, luarocks
, topm
, 또는 GitHub을 살펴볼 수도 있습니다. 오픈소스로 공개된 지 얼마 되지 않아 주목을 받지 못한 라이브러리가 있을 수 있습니다.
이전 글에서 우리는 lua-resty-mlcache
, lua-resty-traffic
, lua-resty-shell
등과 같은 유용한 라이브러리에 대해 배웠습니다. 오늘은 OpenResty 성능 최적화 섹션의 마지막 글에서 커뮤니티 개발자들이 기여한 3개의 독특한 주변 라이브러리를 소개합니다.
ngx.var
의 성능 개선
먼저, C 모듈인 lua-var-nginx-module
을 살펴보겠습니다. 앞서 언급했듯이, ngx.var
은 상대적으로 성능 소모가 큰 작업입니다. 따라서 실제로는 ngx.ctx
를 캐시 계층으로 사용해야 합니다.
그렇다면 ngx.var
의 성능 문제를 완전히 해결할 방법이 있을까요?
이 C 모듈은 이 분야에서 몇 가지 실험을 했으며, 결과는 놀라웠습니다. ngx.var
보다 5배의 성능 향상을 보였습니다. 이 모듈은 FFI
방식을 사용하므로, 먼저 다음 컴파일 옵션으로 OpenResty를 컴파일해야 합니다.
./configure --prefix=/opt/openresty \
--add-module=/path/to/lua-var-nginx-module
그런 다음 luarocks
를 사용하여 lua 라이브러리를 다음과 같이 설치합니다:
luarocks install lua-resty-ngxvar
여기서 호출하는 방법도 매우 간단하며, fetch
함수 한 줄만 필요합니다. 이는 원래의 ngx.var.remote_addr
과 동일하게 클라이언트의 IP 주소를 가져옵니다.
content_by_lua_block {
local var = require("resty.ngxvar")
ngx.say(var.fetch("remote_addr"))
}
이 기본 작업을 이해한 후, 이 모듈이 어떻게 상당한 성능 향상을 달성했는지 더 궁금해할 수 있습니다. 우리가 항상 말하듯이, "소스 코드 앞에는 비밀이 없습니다". 그래서 remote_addr
변수를 어떻게 가져오는지 알아보겠습니다.
ngx_int_t
ngx_http_lua_var_ffi_remote_addr(ngx_http_request_t *r, ngx_str_t *remote_addr)
{
remote_addr->len = r->connection->addr_text.len;
remote_addr->data = r->connection->addr_text.data;
return NGX_OK;
}
이 코드를 읽어보면, 이 Lua FFI
방식은 lua-resty-core
방식과 동일합니다. FFI
를 사용하여 변수를 직접 가져오는 것은 ngx.var
의 원래 조회 로직을 우회하는 명백한 장점이 있습니다. 단점도 명백합니다: 각 변수를 가져오기 위해 C 함수와 FFI
호출을 추가해야 하므로 시간과 노력이 많이 듭니다.
어떤 사람들은 "왜 이렇게 시간과 노력이 많이 드는 것일까요? 위의 C 코드는 꽤 실질적으로 보이지 않나요?"라고 물을 수 있습니다. 이 코드의 출처를 살펴보겠습니다. 이 코드는 NGINX 코드의 src/http/ngx_http_variables.c
에서 가져온 것입니다.
static ngx_int_t
ngx_http_variable_remote_addr(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
v->len = r->connection->addr_text.len;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = r->connection->addr_text.data;
return NGX_OK;
}
소스 코드를 본 후, 비밀이 밝혀졌습니다! lua-var-nginx-module
은 NGINX 변수 코드의 포터이며, 외부에 FFI 래핑을 한 방식으로 성능 최적화를 달성했습니다. 이는 좋은 아이디어이며 최적화의 좋은 방향입니다.
라이브러리나 도구를 배울 때, 단순히 작동 수준에서 멈추지 말고 왜 그렇게 하는지 질문하고 소스 코드를 살펴보아야 합니다. 물론, 더 많은 NGINX 변수를 지원하기 위해 코드를 기여하는 것도 강력히 권장합니다.
JSON 스키마
여기서 lua-resty
라이브러리인 lua-rapidjson
을 소개합니다. 이는 Tencent의 오픈소스 JSON 라이브러리인 rapidjson
을 래핑한 것으로, 성능으로 유명합니다. 여기서 우리는 cjson
과의 차이점인 JSON Schema
지원에 초점을 맞춥니다.
JSON Schema
는 인터페이스의 매개변수 형식과 검증 방법을 정확히 설명할 수 있는 일반적인 표준입니다. 다음은 간단한 예입니다:
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
이 JSON은 stringArray
매개변수가 문자열 배열 타입이며, 배열이 비어 있을 수 없고 배열 요소가 중복될 수 없음을 정확히 설명합니다.
lua-rapidjson
은 OpenResty에서 JSON 스키마를 사용할 수 있게 해주며, 인터페이스 검증에 큰 편의를 제공합니다. 예를 들어, 앞서 설명한 제한 카운트 인터페이스에 대해 다음 스키마를 사용하여 설명할 수 있습니다:
local schema = {
type = "object",
properties = {
count = {type = "integer", minimum = 0},
time_window = {type = "integer", minimum = 0},
key = {type = "string", enum = {"remote_addr", "server_addr"}},
rejected_code = {type = "integer", minimum = 200, maximum = 600},
},
additionalProperties = false,
required = {"count", "time_window", "key", "rejected_code"},
}
이렇게 하면 두 가지 매우 명백한 이점이 있습니다:
- 프론트엔드의 경우, 프론트엔드는 이 스키마 설명을 직접 재사용하여 프론트엔드 페이지 개발과 매개변수 검증을 할 수 있으며, 백엔드를 신경 쓸 필요가 없습니다.
- 백엔드의 경우, 백엔드는
lua-rapidjson
의 스키마 검증 기능인SchemaValidator
를 직접 사용하여 인터페이스의 합법성을 판단할 수 있으며, 추가 코드를 작성할 필요가 없습니다.
Worker
간 통신
마지막으로, OpenResty에서 Worker
간 통신을 가능하게 하는 lua-resty
라이브러리에 대해 이야기하겠습니다. OpenResty에는 Worker
프로세스 간 직접 통신 메커니즘이 없어 많은 문제를 야기합니다. 다음 시나리오를 상상해보세요:
OpenResty 서비스에 24개의 worker 프로세스가 있고, 관리자가 REST HTTP API를 통해 시스템 구성을 업데이트하면 하나의
Worker
만이 관리자의 업데이트를 받아 데이터베이스에 결과를 쓰고, 자신의 Worker 내의shared dict
와lru cache
를 업데이트합니다. 그렇다면 다른 23개의 worker에게 이 구성을 업데이트하도록 알리는 방법은 무엇일까요?
여러 Worker
간의 알림 메커니즘이 필요합니다. OpenResty가 이를 지원하지 않는 경우, shared dict
데이터를 통해 여러 worker 간에 데이터를 공유해야 합니다.
lua-resty-worker-events
는 이 아이디어의 구체적인 구현입니다. 이는 shared dict
에 버전 번호를 유지하며, 새 메시지가 게시되면 버전 번호를 하나 증가시키고 메시지 내용을 버전 번호를 key
로 하여 사전에 넣습니다.
event_id, err = _dict:incr(KEY_LAST_ID, 1)
success, err = _dict:add(KEY_DATA .. tostring(event_id), json)
또한, ngx.timer
를 사용하여 기본 간격이 1초인 polling
루프를 백그라운드에서 생성하여 버전 번호의 변경을 지속적으로 확인합니다:
local event_id, err = get_event_id()
if event_id == _last_event then
return "done"
end
이렇게 하면 새로운 이벤트 알림이 처리되어야 함을 발견하면 버전 번호를 기반으로 shared dict
에서 메시지 내용을 검색합니다:
while _last_event < event_id do
count = count + 1
_last_event = _last_event + 1
data, err = _dict:get(KEY_DATA..tostring(_last_event))
end
전체적으로, lua-resty-worker-events
는 1초의 지연이 있지만, 여전히 Worker-to-Worker 이벤트 알림 메커니즘을 구현했습니다.
그러나 실시간 시나리오, 예를 들어 메시지 푸시의 경우, OpenResty의 Worker
프로세스 간 직접 통신 부재로 인해 문제가 발생할 수 있습니다. 이에 대한 더 나은 해결책은 없지만, 좋은 아이디어가 있다면 Github에서 자유롭게 논의해주세요. OpenResty의 많은 기능은 커뮤니티에 의해 구축된 선순환 생태계에 의해 주도됩니다.
요약
오늘 소개한 세 가지 라이브러리는 독특하며 OpenResty 애플리케이션에 더 많은 가능성을 제공합니다. 마지막으로, OpenResty 주변에서 흥미로운 라이브러리를 찾았나요? 또는 오늘 언급된 라이브러리에 대해 어떤 점을 발견하거나 궁금한 점이 있나요? 이 글을 주변의 OpenResty 사용자에게 보내 함께 교류하고 발전해 나가길 바랍니다.