ميزة OpenResty القاتلة: الديناميكية
API7.ai
January 12, 2023
حتى الآن، نحن على وشك الانتهاء من المحتوى المتعلق بأداء OpenResty. إتقان وتطبيق هذه التقنيات التحسينية بشكل مرن يمكن أن يحسن بشكل كبير من أداء الكود الخاص بنا. اليوم، في الجزء الأخير من تحسين الأداء، دعونا نتعلم عن قدرة غالبًا ما يتم التقليل من شأنها في OpenResty: "الديناميكية".
لنبدأ بالنظر إلى ما تعنيه الديناميكية وكيف ترتبط بالأداء. الديناميكية في هذا السياق تعني أن البرامج يمكنها تعديل المعلمات، التكوينات، وحتى الكود الخاص بها أثناء التشغيل، دون الحاجة إلى إعادة التحميل. على وجه التحديد، في NGINX وOpenResty، يمكنك تغيير الـ upstream، شهادات SSL، وعتبات الحد من المعدل دون إعادة تشغيل الخدمة، مما يحقق الديناميكية. أما بالنسبة للعلاقة بين الديناميكية والأداء، فمن الواضح أنه إذا لم يكن من الممكن تنفيذ هذه الأنواع من العمليات بشكل ديناميكي، فإن إعادة تحميل خدمات NGINX بشكل متكرر سيؤدي بشكل طبيعي إلى فقدان الأداء.
ومع ذلك، نعلم أن النسخة المفتوحة المصدر من NGINX لا تدعم الميزات الديناميكية، لذا يتعين عليك تغيير شهادات SSL للـ upstream عن طريق تعديل ملف التكوين وإعادة تشغيل الخدمة لجعلها فعالة. توفر NGINX Plus (النسخة التجارية من NGINX) بعض القدرات الديناميكية، ويمكنك استخدام REST API للتحديث، ولكن هذا تحسين أقل جذرية في أحسن الأحوال.
في OpenResty، هذه القيود غير موجودة، والديناميكية هي الميزة القاتلة لـ OpenResty. قد تتساءل لماذا يمكن لـ OpenResty، المبنية على NGINX، أن تدعم الديناميكية. السبب بسيط: منطق NGINX يتم عبر وحدات C، بينما OpenResty يتم عبر Lua، وهي لغة برمجة نصية. إحدى مزايا لغات البرمجة النصية هي أنها يمكن أن تتغير بشكل ديناميكي أثناء التشغيل.
تحميل الكود بشكل ديناميكي
إليك كيفية تحميل كود Lua بشكل ديناميكي في OpenResty.
resty -e 'local s = [[ngx.say("hello world")]]
local func, err = loadstring(s)
func()'
يمكننا أن نرى أنه في بضعة أسطر من الكود، يمكننا تحويل سلسلة نصية إلى دالة Lua وجعلها تعمل. دعونا نلقي نظرة أقرب على هذه الأسطر من الكود:
- أولاً، نعلن عن سلسلة نصية محتواها جزء من كود Lua يطبع
hello world
؛ - ثم، باستخدام دالة
loadstring
في Lua، نحول كائن السلسلة إلى كائن الدالةfunc
. - أخيرًا، نضيف الأقواس إلى اسم الدالة لتنفيذ
func
وطباعةhello world
.
بالطبع، يمكننا أيضًا توسيع وظائف أكثر إثارة للاهتمام وعملية بناءً على هذا الكود. بعد ذلك، سأأخذك لتجربة ذلك.
الوظيفة 1: FaaS
أولاً FaaS (الوظيفة كخدمة)، والتي كانت مؤخرًا اتجاهًا تقنيًا شائعًا جدًا. دعونا نرى كيفية تنفيذها في OpenResty. في الكود المذكور للتو، السلسلة النصية هي كود Lua. يمكننا أيضًا تغييرها إلى دالة Lua:
local s = [[
return function()
ngx.say("hello world")
end
]]
كما قلنا، الدوال هي مواطنون من الدرجة الأولى في Lua، وهذا الكود يعيد دالة مجهولة. عند تنفيذ هذه الدالة المجهولة، نستخدم pcall
لتوفير طبقة من الحماية. pcall
ستعمل الدالة في وضع محمي وتلتقط الاستثناء. إذا كان طبيعيًا، ستعود true
ونتيجة التنفيذ. إذا فشلت، ستعود false
ومعلومات الخطأ، وهو الكود التالي:
local func1, err = loadstring(s)
local ret, func = pcall(func1)
بشكل طبيعي، إذا جمعت الجزأين أعلاه، ستحصل على مثال كامل وقابل للتشغيل:
resty -e 'local s = [[
return function()
ngx.say("hello world")
end
]]
local func1 = loadstring(s)
local ret, func = pcall(func1)
func()'
للتقدم خطوة أخرى، يمكننا تغيير السلسلة النصية s
التي تحتوي على دوال إلى نموذج يمكن للمستخدمين تحديده وإضافة شروط تنفيذه. هذا هو النموذج الأولي لـ FaaS. هنا، أقدم تنفيذًا كاملاً. إذا كنت مهتمًا بـ FaaS وتريد مواصلة بحثك، انتقل عبر الرابط لمعرفة المزيد.
الوظيفة 2: الحوسبة الطرفية
يمكن استخدام الديناميكية في OpenResty لـ FaaS، مما يجعل ديناميكية لغة البرمجة النصية دقيقة إلى مستوى الدالة، وتلعب دورًا ديناميكيًا في الحوسبة الطرفية.
بسبب هذه المزايا، يمكننا توسيع نطاق OpenResty من مجالات بوابة API، جدار حماية تطبيقات الويب (WAF)، خادم الويب، وغيرها من الأطراف الخادمة إلى العقد الطرفية الأقرب إلى المستخدمين، مثل أجهزة IoT، عقد CDN الطرفية، الموجهات وغيرها.
هذا ليس مجرد خيال. OpenResty تم استخدامها على نطاق واسع في المجالات المذكورة أعلاه. على سبيل المثال، Cloudflare، أكبر مستخدم لـ OpenResty، قد حقق التحكم الديناميكي في عقد CDN الطرفية بمساعدة ميزات الديناميكية في OpenResty لفترة طويلة.
طريقة Cloudflare مشابهة لمبدأ تحميل الكود بشكل ديناميكي المذكور أعلاه، والتي يمكن تقسيمها إلى الخطوات التالية:
- أولاً، الحصول على ملفات الكود المتغيرة من مجموعة قواعد البيانات الرئيسية-القيمة. يمكن أن تكون الطريقة استطلاعًا خلفيًا مؤقتًا أو وضع "النشر-الاشتراك" للرصد؛
- ثم، استبدال الملف القديم على القرص المحلي بملف الكود المحدث وتحديث الذاكرة المحملة باستخدام طرق
loadstring
وpcall
؛
بهذه الطريقة، سيتم معالجة طلب العميل التالي من خلال منطق الكود المحدث. بالطبع، التطبيق العملي يجب أن يأخذ في الاعتبار تفاصيل أكثر من الخطوات المذكورة أعلاه، مثل التحكم في الإصدار والتراجع، معالجة الاستثناءات، انقطاع الشبكة، إعادة تشغيل العقد الطرفية، إلخ، ولكن العملية العامة تبقى كما هي.
إذا نقلنا طريقة Cloudflare من عقد CDN الطرفية إلى سيناريوهات طرفية أخرى، يمكننا تعيين الكثير من قوة الحوسبة بشكل ديناميكي لأجهزة العقد الطرفية. هذا لا يمكن أن يستفيد بشكل كامل من قوة الحوسبة للعقد الطرفية فحسب، بل يمكن أيضًا أن يحصل المستخدمون على استجابات أسرع للطلبات لأن العقدة الطرفية ستقوم بمعالجة البيانات الأصلية ثم تلخيصها إلى الخادم البعيد، مما يقلل بشكل كبير من كمية نقل البيانات.
ومع ذلك، للقيام بعمل ممتاز في FaaS والحوسبة الطرفية، الديناميكية في OpenResty هي فقط أساس جيد. تحتاج أيضًا إلى التفكير في تحسين البيئة المحيطة بك ومشاركة الشركات المصنعة، وهذا ليس فقط فئة تقنية.
الـ Upstream الديناميكي
الآن، دعونا نعيد أفكارنا إلى OpenResty لنرى كيفية تحقيق الـ upstream الديناميكي. lua-resty-core
توفر مكتبة ngx.balancer
لإعداد الـ upstream. يجب وضعها في مرحلة balancer
من OpenResty لتشغيلها:
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local host = "127.0.0.2"
local port = 8080
local ok, err = balancer.set_current_peer(host, port)
if not ok then
ngx.log(ngx.ERR, "failed to set the current peer: ", err)
return ngx.exit(500)
end
}
دالة set_current_peer
تقوم بإعداد عنوان IP والمنفذ للـ upstream. ومع ذلك، نود أن نشير إلى أن اسم النطاق غير مدعوم هنا. نحتاج إلى استخدام مكتبة lua-resty-dns
لإجراء طبقة من التحليل لاسم النطاق وIP.
ومع ذلك، ngx.balancer
منخفض المستوى نسبيًا. على الرغم من أنه يمكن استخدامه لإعداد الـ upstream، فإن تحقيق الـ upstream الديناميكي بعيد عن البساطة. لذلك، هناك حاجة إلى وظيفتين أمام ngx.balancer
:
- أولاً، تحديد ما إذا كانت خوارزمية اختيار الـ upstream هي
consistent hash
أوroundrobin
؛ - الثانية هي آلية فحص صحة الـ upstream، والتي تحتاج إلى إزالة الـ upstream غير الصحي وإعادة إضافته عندما يصبح الـ upstream غير الصحي صحيًا.
مكتبة OpenResty الرسمية lua-resty-balancer
تحتوي على نوعين من الخوارزميات: resty.chash
و resty.roundrobin
لإكمال الوظيفة الأولى، ولديها lua-resty-upstream-healthcheck
لمحاولة إكمال الوظيفة الثانية.
ومع ذلك، لا تزال هناك مشكلتان.
النقطة الأولى هي نقص التنفيذ الكامل للخطوة الأخيرة. تحويل ngx.balancer
، lua-resty-balancer
، و lua-resty-upstream-healthcheck
إلى الجمع بين وظائف الـ upstream الديناميكي، ولكن لا يزال هناك حاجة إلى بعض العمل، مما يوقف معظم المطورين.
ثانيًا، تنفيذ lua-resty-upstream-healthcheck
غير كامل. هناك فقط فحص صحة سلبي ولكن لا يوجد فحص صحة نشط.
طلبات العملاء تطلق الفحص الصحي السلبي هنا ثم تحليل قيمة الإرجاع كشرط لتحديد ما إذا كانت الصحة جيدة. إذا لم يكن هناك طلب من العميل، فإن صحة الـ upstream غير معروفة. يمكن للفحص الصحي النشط أن يعالج هذا العيب. يستخدم ngx.timer
للاستطلاع الدوري لواجهة الـ upstream المحددة للكشف عن حالة الصحة.
لذلك، في الممارسة الفعلية، نوصي عادةً باستخدام lua-resty-healthcheck
لإكمال فحص صحة الـ upstream. ميزتها هي أنها تشمل الفحص الصحي النشط والسلبي، وقد تم التحقق منها في مشاريع متعددة مع موثوقية أعلى.
علاوة على ذلك، بوابة API الصغيرة الناشئة Apache APISIX قد قامت بتنفيذ كامل للـ upstream الديناميكي بناءً على lua-resty-upstream-healthcheck
. يمكننا الرجوع إلى تنفيذها. هناك فقط 400 سطر من الكود في المجموع. يمكنك بسهولة فصلها ووضعها في مشروعك للاستخدام.
الخلاصة
فيما يتعلق بالديناميكية في OpenResty، في أي مجالات وسيناريوهات يمكنك الاستفادة منها؟ يمكنك أيضًا توسيع محتويات كل جزء تم تقديمه في هذا الفصل لتحليل أكثر تفصيلاً وعمقًا.
نرحب بمشاركة هذه المقالة والتعلم والتقدم مع المزيد من الأشخاص.