ثلاث مكتبات Lua Resty شائعة الاستخدام في OpenResty

API7.ai

January 13, 2023

OpenResty (NGINX + Lua)

تعلم لغات البرمجة والمنصات غالبًا ما يكون مسألة فهم المكتبات القياسية والجهات الخارجية بدلاً من التركيز على بناء الجملة نفسه. بعد تعلم واجهة برمجة التطبيقات (API) الخاصة بها وتقنيات تحسين الأداء، نحتاج إلى تعلم استخدام مكتبات lua-resty المختلفة لتوسيع قدرات OpenResty لدينا لتشمل المزيد من السيناريوهات.

أين يمكن العثور على مكتبة lua-resty؟

مقارنةً بـ PHP و Python و JavaScript، فإن المكتبات القياسية والجهات الخارجية الحالية لـ OpenResty لا تزال محدودة نسبيًا، والعثور على مكتبات lua-resty المناسبة ليس بالأمر السهل. ومع ذلك، هناك مصدران موصى بهما لمساعدتك في العثور عليها بشكل أسرع.

أول توصية هي مستودع awesome-resty الذي يحتفظ به Aapo. يقوم هذا المستودع بتنظيم المكتبات المتعلقة بـ 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 هذه ببعض التجارب في هذا المجال، والنتائج ملحوظة، حيث تحقق تحسينًا في الأداء بمقدار 5 أضعاف مقارنة بـ ngx.var. تستخدم هذه الوحدة طريقة 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 أعلاه جيدًا؟" دعنا نلقي نظرة على مصدر هذه الأسطر من الكود، والتي تأتي من src/http/ngx_http_variables.c في كود NGINX.

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 Schema

هنا أقدم مكتبة lua-resty: lua-rapidjson. إنها غلاف حول rapidjson، مكتبة JSON مفتوحة المصدر من Tencent، والمعروفة بأدائها. هنا، نركز على الفرق بينها وبين cjson: دعم JSON Schema.

JSON Schema هو معيار شائع يسمح لنا بوصف دقيق لتنسيق المعلمات في واجهة وكيفية التحقق منها. هنا مثال بسيط:

"stringArray": {
    "type": "array",
    "items": { "type": "string" },
    "minItems": 1,
    "uniqueItems": true
}

يصف هذا JSON بدقة أن المعلمة stringArray هي من نوع مصفوفة النصوص وأن المصفوفة لا يمكن أن تكون فارغة، ولا يمكن أن تتكرر عناصر المصفوفة.

lua-rapidjson تسمح لنا باستخدام JSON Schema في OpenResty، مما يمكن أن يجلب راحة كبيرة للتحقق من الواجهات. على سبيل المثال، لواجهة حد العد التي وصفناها سابقًا، يمكننا استخدام المخطط التالي لوصفها:

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"},
}

ستجد أن هذا يمكن أن يؤدي إلى فائدتين واضحتين:

  1. بالنسبة للواجهة الأمامية، يمكن للواجهة الأمامية إعادة استخدام وصف المخطط هذا مباشرة لتطوير الصفحات الأمامية والتحقق من المعلمات دون الحاجة إلى الاهتمام بالواجهة الخلفية.
  2. بالنسبة للواجهة الخلفية، تستخدم الواجهة الخلفية مباشرة وظيفة التحقق من المخطط SchemaValidator في lua-rapidjson لتحديد شرعية الواجهة، ولا حاجة لكتابة كود إضافي.

اتصال Worker

أخيرًا، أود التحدث عن مكتبة lua-resty التي تمكن الاتصال بين العمال في OpenResty، حيث لا توجد آلية للاتصال المباشر بين العمال، مما يسبب الكثير من المشاكل. دعنا نتخيل سيناريو:

خدمة OpenResty لديها 24 عملية عامل، وعندما يقوم المسؤول بتحديث تكوين النظام من خلال واجهات برمجة التطبيقات REST HTTP، يتلقى عامل واحد فقط التحديث من المسؤول ويكتب النتيجة في قاعدة البيانات، ويقوم بتحديث shared dict و lru cache داخل العامل الخاص به. إذن، كيف يمكن إخطار العمال الـ 23 الآخرين لتحديث هذا التكوين؟

نحتاج إلى آلية إخطار بين عدة عمال لإنجاز المهمة أعلاه. في حالة عدم دعم OpenResty لذلك، علينا إنقاذ الموقف باستخدام بيانات shared dict عبر العمال.

lua-resty-worker-events هي تنفيذ ملموس لهذه الفكرة. تحتفظ برقم إصدار في shared dict، وعند نشر رسالة جديدة، تضيف واحدًا إلى رقم الإصدار وتضع محتوى الرسالة في القاموس مع رقم الإصدار كمفتاح.

event_id, err = _dict:incr(KEY_LAST_ID, 1)
success, err = _dict:add(KEY_DATA .. tostring(event_id), json)

أيضًا، يتم إنشاء حلقة polling بفاصل زمني افتراضي قدره ثانية واحدة في الخلفية باستخدام ngx.timer للتحقق باستمرار من تغييرات رقم الإصدار:

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 لديها تأخير لمدة ثانية واحدة، إلا أنها لا تزال تنفذ آلية إخطار بين العمال.

ومع ذلك، في بعض السيناريوهات في الوقت الفعلي، مثل دفع الرسائل، قد يسبب لك عدم وجود اتصال مباشر بين عمليات Worker في OpenResty بعض المشاكل. لا يوجد حل أفضل لهذا، ولكن إذا كانت لديك أفكار جيدة، فلا تتردد في مناقشتها على Github. العديد من ميزات OpenResty مدفوعة من المجتمع لبناء دورة بيئية حميدة.

الخلاصة

المكتبات الثلاث التي قدمناها اليوم فريدة وتجلب المزيد من الإمكانيات لتطبيقات OpenResty. أخيرًا، موضوع تفاعلي، هل وجدت أي مكتبات مثيرة للاهتمام حول OpenResty؟ أو ما الذي تجده أو تتساءل عنه في المكتبات المذكورة اليوم؟ أنت مرحب بإرسال هذه المقالة إلى مستخدمي OpenResty حولك لتبادل الأفكار والتقدم معًا.