
يتطلب تطوير المشاريع عالية التحميل بأي لغة نهجًا خاصًا واستخدام أدوات خاصة، ولكن عندما يتعلق الأمر بالتطبيقات في PHP، يمكن أن يتفاقم الوضع لدرجة أنه يتعين عليك التطوير، على سبيل المثال، . سنتحدث في هذه المذكرة عن الصعوبات المألوفة في تخزين الجلسة الموزعة والتخزين المؤقت للبيانات في memcached وكيف قمنا بحل هذه المشكلات في مشروع "جناح" واحد.
بطل المناسبة هو تطبيق PHP يعتمد على إطار عمل Symfony 2.3، والذي لم يتم تضمينه على الإطلاق في خطط العمل المراد تحديثها. بالإضافة إلى تخزين الجلسة القياسي تمامًا، استفاد هذا المشروع بشكل كامل من سياسة "التخزين المؤقت لكل شيء". في memcached: استجابات لطلبات قاعدة البيانات وخوادم واجهة برمجة التطبيقات (API) والأعلام المختلفة والأقفال لمزامنة تنفيذ التعليمات البرمجية وغير ذلك الكثير. في مثل هذه الحالة، يصبح انهيار memcached قاتلاً لتشغيل التطبيق. بالإضافة إلى ذلك، يؤدي فقدان ذاكرة التخزين المؤقت إلى عواقب وخيمة: يبدأ نظام إدارة قواعد البيانات (DBMS) في الانفجار عند اللحامات، وتبدأ خدمات واجهة برمجة التطبيقات (API) في حظر الطلبات، وما إلى ذلك. قد يستغرق استقرار الوضع عشرات الدقائق، وخلال هذا الوقت ستكون الخدمة بطيئة للغاية أو غير متوفرة تمامًا.
كنا بحاجة لتقديم القدرة على توسيع نطاق التطبيق أفقيًا بجهد قليل، أي. مع الحد الأدنى من التغييرات في الكود المصدري والحفاظ على الوظائف الكاملة. لا تجعل ذاكرة التخزين المؤقت مقاومة للفشل فحسب، بل حاول أيضًا تقليل فقدان البيانات منها.
ما هو الخطأ في memcached نفسها؟
بشكل عام، يدعم ملحق memcached الخاص بـ PHP البيانات الموزعة وتخزين الجلسات خارج الصندوق. تسمح لك آلية تجزئة المفاتيح المتسقة بتوزيع البيانات بالتساوي على العديد من الخوادم، ومعالجة كل مفتاح محدد بشكل فريد إلى خادم معين من المجموعة، كما تضمن أدوات تجاوز الفشل المضمنة توفرًا عاليًا لخدمة التخزين المؤقت (ولكن، لسوء الحظ، لايوجد بيانات).
أصبحت الأمور أفضل قليلاً مع تخزين الجلسة: يمكنك تكوينها memcached.sess_number_of_replicasونتيجة لذلك سيتم تخزين البيانات على عدة خوادم في وقت واحد، وفي حالة فشل مثيل memcached واحد، سيتم نقل البيانات من الآخرين. ومع ذلك، إذا عاد الخادم متصلاً بالإنترنت بدون بيانات (كما يحدث عادةً بعد إعادة التشغيل)، فسيتم إعادة توزيع بعض المفاتيح لصالحه. في الواقع هذا سوف يعني فقدان بيانات الجلسة، لأنه لا توجد طريقة "للانتقال" إلى نسخة طبق الأصل أخرى في حالة حدوث خطأ.
تهدف أدوات المكتبة القياسية بشكل أساسي إلى عرضي القياس: تسمح لك بزيادة ذاكرة التخزين المؤقت إلى أحجام هائلة وتوفير الوصول إليها من التعليمات البرمجية المستضافة على خوادم مختلفة. ومع ذلك، في حالتنا، لا يتجاوز حجم البيانات المخزنة عدة غيغابايت، وأداء عقدة واحدة أو عقدتين يكفي تماما. وفقًا لذلك، يمكن أن تكون الأدوات القياسية المفيدة الوحيدة هي ضمان توفر memcached مع الحفاظ على نسخة ذاكرة تخزين مؤقت واحدة على الأقل في حالة صالحة للعمل. ومع ذلك، لم يكن من الممكن الاستفادة حتى من هذه الفرصة... هنا تجدر الإشارة إلى قدم إطار العمل المستخدم في المشروع، ولهذا السبب كان من المستحيل جعل التطبيق يعمل مع مجموعة من الخوادم. دعونا لا ننسى أيضًا فقدان بيانات الجلسة: ارتعشت عين العميل من تسجيل الخروج الهائل للمستخدمين.
من الناحية المثالية كان مطلوبا النسخ المتماثل للسجلات في memcached وتجاوز النسخ المتماثلة في حالة وجود خطأ أو خطأ. ساعدنا في تنفيذ هذه الاستراتيجية .
com.mcrouter
هذا جهاز توجيه memcached تم تطويره بواسطة Facebook لحل مشاكله. وهو يدعم بروتوكول النص memcached، والذي يسمح توسيع نطاق عمليات التثبيت memcached إلى أبعاد جنونية يمكن العثور على وصف تفصيلي لـ mcrouter في . ضمن أشياء أخرى يمكنها أن تفعل ما نحتاج إليه:
- تكرار السجل؛
- قم بالرجوع إلى الخوادم الأخرى في المجموعة في حالة حدوث خطأ.
ابدأ بالعمل!
تكوين مكرووتر
سأذهب مباشرة إلى التكوين:
{
"pools": {
"pool00": {
"servers": [
"mc-0.mc:11211",
"mc-1.mc:11211",
"mc-2.mc:11211"
},
"pool01": {
"servers": [
"mc-1.mc:11211",
"mc-2.mc:11211",
"mc-0.mc:11211"
},
"pool02": {
"servers": [
"mc-2.mc:11211",
"mc-0.mc:11211",
"mc-1.mc:11211"
},
"route": {
"type": "OperationSelectorRoute",
"default_policy": "AllMajorityRoute|Pool|pool00",
"operation_policies": {
"get": {
"type": "RandomRoute",
"children": [
"MissFailoverRoute|Pool|pool02",
"MissFailoverRoute|Pool|pool00",
"MissFailoverRoute|Pool|pool01"
]
}
}
}
}لماذا ثلاثة حمامات؟ لماذا تتكرر السيرفرات؟ دعونا معرفة كيف يعمل.
- في هذا التكوين، يحدد mcrouter المسار الذي سيتم إرسال الطلب إليه بناءً على أمر الطلب. الرجل يقول له هذا
OperationSelectorRoute. - تذهب طلبات GET إلى المعالج
RandomRouteالذي يختار بشكل عشوائي تجمعًا أو مسارًا بين كائنات المصفوفةchildren. كل عنصر في هذه المصفوفة هو بدوره معالجMissFailoverRoute، والتي سوف تمر عبر كل خادم في التجمع حتى تتلقى استجابة بالبيانات، والتي سيتم إرجاعها إلى العميل. - إذا استخدمنا حصرا
MissFailoverRouteمع مجموعة من ثلاثة خوادم، ستأتي جميع الطلبات أولاً إلى مثيل memcached الأول، وسيتلقى الباقي الطلبات على أساس متبقي في حالة عدم وجود بيانات. مثل هذا النهج من شأنه أن يؤدي إلى التحميل الزائد على الخادم الأول في القائمةلذلك تقرر إنشاء ثلاث مجموعات بعناوين بتسلسلات مختلفة واختيارها عشوائيًا. - تتم معالجة جميع الطلبات الأخرى (وهذا سجل) باستخدام
AllMajorityRoute. يرسل هذا المعالج الطلبات إلى كافة الخوادم الموجودة في التجمع وينتظر الردود من N/2 + 1 منها على الأقل. من الاستخدامAllSyncRouteلأنه كان لا بد من التخلي عن عمليات الكتابة، لأن هذه الطريقة تتطلب استجابة إيجابية من جميع خوادم في المجموعة - وإلا فإنه سيعودSERVER_ERROR. على الرغم من أن mcrouter سيضيف البيانات إلى ذاكرات التخزين المؤقت المتاحة، فإن وظيفة استدعاء PHP سوف يعود خطأ وسوف تولد إشعار.AllMajorityRouteليست صارمة للغاية وتسمح بإخراج ما يصل إلى نصف الوحدات من الخدمة دون المشاكل الموضحة أعلاه.
العيب الرئيسي هذا المخطط هو أنه إذا لم تكن هناك بيانات بالفعل في ذاكرة التخزين المؤقت، فسيتم بالفعل تنفيذ طلبات N إلى memcached لكل طلب من العميل - جميع خوادم في المجمع. يمكننا تقليل عدد الخوادم في المجمعات، على سبيل المثال، إلى اثنين: التضحية بموثوقية التخزين، نحصل عليهاоسرعة أعلى وتحميل أقل من الطلبات إلى المفاتيح المفقودة.
NB: قد تجد أيضًا روابط مفيدة لتعلم المكرووتر и (بما في ذلك المغلقة)، وهو مخزن كامل من التكوينات المختلفة.
بناء وتشغيل المكرووتر
يعمل تطبيقنا (وmemcached نفسه) في Kubernetes - وبالتالي، يوجد mcrouter هناك أيضًا. ل تجميع الحاوية نحن نستخدم ، سيبدو التكوين الخاص به كما يلي:
NB: يتم نشر القوائم الواردة في المقالة في المستودع .
configVersion: 1
project: mcrouter
deploy:
namespace: '[[ env ]]'
helmRelease: '[[ project ]]-[[ env ]]'
---
image: mcrouter
from: ubuntu:16.04
mount:
- from: tmp_dir
to: /var/lib/apt/lists
- from: build_dir
to: /var/cache/apt
ansible:
beforeInstall:
- name: Install prerequisites
apt:
name: [ 'apt-transport-https', 'tzdata', 'locales' ]
update_cache: yes
- name: Add mcrouter APT key
apt_key:
url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
- name: Add mcrouter Repo
apt_repository:
repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
filename: mcrouter
update_cache: yes
- name: Set timezone
timezone:
name: "Europe/Moscow"
- name: Ensure a locale exists
locale_gen:
name: en_US.UTF-8
state: present
install:
- name: Install mcrouter
apt:
name: [ 'mcrouter' ]()
...ورسمها مخطط الخوذة. الشيء المثير للاهتمام هو أنه لا يوجد سوى منشئ التكوين بناءً على عدد النسخ المتماثلة (إذا كان لدى أي شخص خيار أكثر إيجازًا وأناقة، شاركه في التعليقات):
{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Заполняем массив двумя копиями серверов: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
{{- range $i, $_ := until $count -}}
{{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
{{- end -}}
{{- end -}}
{{- /* Смещаясь по массиву, получаем N срезов: "[0 1 2] [1 2 0] [2 0 1]" */ -}}
{{- range $i, $_ := until $count -}}
{{- $pool := dict "servers" (slice $servers $i (add $i $count)) -}}
{{- $_ := set $pools (printf "MissFailoverRoute|Pool|pool%02d" $i) $pool -}}
{{- end -}}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mcrouter
data:
config.json: |
{
"pools": {{- $pools | toJson | replace "MissFailoverRoute|Pool|" "" -}},
"route": {
"type": "OperationSelectorRoute",
"default_policy": "AllMajorityRoute|Pool|pool00",
"operation_policies": {
"get": {
"type": "RandomRoute",
"children": {{- keys $pools | toJson }}
}
}
}
}()
نطرحه في بيئة الاختبار ونتحقق من:
# php -a
Interactive mode enabled
php > # Проверяем запись и чтение
php > $m = new Memcached();
php > $m->addServer('mcrouter', 11211);
php > var_dump($m->set('test', 'value'));
bool(true)
php > var_dump($m->get('test'));
string(5) "value"
php > # Работает! Тестируем работу сессий:
php > ini_set('session.save_handler', 'memcached');
php > ini_set('session.save_path', 'mcrouter:11211');
php > var_dump(session_start());
PHP Warning: Uncaught Error: Failed to create session ID: memcached (path: mcrouter:11211) in php shell code:1
Stack trace:
#0 php shell code(1): session_start()
#1 {main}
thrown in php shell code on line 1
php > # Не заводится… Попробуем задать session_id:
php > session_id("zzz");
php > var_dump(session_start());
PHP Warning: session_start(): Cannot send session cookie - headers already sent by (output started at php shell code:1) in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Unable to clear session lock record in php shell code on line 1
PHP Warning: session_start(): Failed to read session data: memcached (path: mcrouter:11211) in php shell code on line 1
bool(false)
php >البحث عن نص الخطأ لم يعطي أي نتائج سوى للاستعلام “"في المقدمة كانت أقدم مشكلة لم يتم حلها في المشروع - بروتوكول memcached الثنائي.
NB: بروتوكول ASCII في memcached أبطأ من البروتوكول الثنائي، والوسائل القياسية لتجزئة المفاتيح المتسقة تعمل فقط مع البروتوكول الثنائي. ولكن هذا لا يخلق مشاكل لحالة معينة.
الخدعة موجودة: كل ما عليك فعله هو التبديل إلى بروتوكول ASCII وسيعمل كل شيء.... ومع ذلك، في هذه الحالة، عادة البحث عن إجابات في لعبت مزحة قاسية. لن تجد الإجابة الصحيحة هناك... إلا إذا قمت بالطبع بالتمرير إلى النهاية، حيث يوجد القسم "ملاحظات ساهم بها المستخدم" سوف يكون وفيا و .
نعم، اسم الخيار الصحيح هو memcached.sess_binary_protocol. يجب تعطيله، وبعد ذلك ستبدأ الجلسات في العمل. كل ما تبقى هو وضع الحاوية التي تحتوي على mcrouter في حجرة PHP!
اختتام
وبالتالي، من خلال تغييرات البنية التحتية فقط، تمكنا من حل المشكلة: تم حل مشكلة التسامح مع خطأ memcached، وتمت زيادة موثوقية تخزين ذاكرة التخزين المؤقت. بالإضافة إلى المزايا الواضحة للتطبيق، فقد أعطى هذا مجالًا للمناورة عند العمل على النظام الأساسي: عندما يكون لدى جميع المكونات احتياطي، يتم تبسيط حياة المسؤول إلى حد كبير. نعم، هذه الطريقة أيضًا لها عيوبها، فقد تبدو وكأنها "عكاز"، لكنها إذا كانت توفر المال وتدفن المشكلة ولا تسبب مشاكل جديدة - فلماذا لا؟
PS
اقرأ أيضًا على مدونتنا:
- "التدرب مع dapp" (باستخدام Symfony-Demo كمثال): и ;
- «".
المصدر: www.habr.com
