استفاده از mcrouter برای مقیاس بندی memcached به صورت افقی

استفاده از mcrouter برای مقیاس بندی memcached به صورت افقی

توسعه پروژه‌های پر بار در هر زبانی نیازمند رویکردی خاص و استفاده از ابزارهای خاص است، اما وقتی صحبت از برنامه‌های کاربردی در PHP به میان می‌آید، ممکن است وضعیت آنقدر بدتر شود که مجبور شوید مثلاً توسعه دهید: سرور برنامه شخصی. در این یادداشت در مورد درد آشنا با ذخیره‌سازی جلسات توزیع شده و ذخیره‌سازی داده‌ها در حافظه پنهان و نحوه حل این مشکلات در یک پروژه "بخش" صحبت خواهیم کرد.

قهرمان مناسبت یک برنامه PHP مبتنی بر فریمورک symfony 2.3 است که اصلاً در برنامه های تجاری برای به روز رسانی گنجانده نشده است. علاوه بر فضای ذخیره‌سازی جلسه کاملاً استاندارد، این پروژه به طور کامل از آن استفاده کرد سیاست "کش کردن همه چیز". در memcached: پاسخ به درخواست‌ها به پایگاه داده و سرورهای API، پرچم‌های مختلف، قفل‌هایی برای همگام‌سازی اجرای کد و موارد دیگر. در چنین شرایطی، خرابی memcached برای عملکرد برنامه کشنده می شود. علاوه بر این، از دست دادن حافظه پنهان منجر به عواقب جدی می شود: DBMS شروع به ترکیدن در درزها می کند، سرویس های API شروع به ممنوع کردن درخواست ها می کنند و غیره. تثبیت وضعیت ممکن است ده ها دقیقه طول بکشد و در این مدت سرویس به شدت کند یا کاملاً در دسترس نخواهد بود.

نیاز داشتیم تهیه کنیم توانایی مقیاس افقی برنامه با کمی تلاش، یعنی با حداقل تغییرات در کد منبع و عملکرد کامل حفظ شده است. کش را نه تنها در برابر خرابی ها مقاوم کنید، بلکه سعی کنید از دست دادن داده ها از آن به حداقل برسانید.

چه اشکالی دارد خود memcached؟

به طور کلی، پسوند memcached برای PHP از داده های توزیع شده و ذخیره سازی جلسه خارج از جعبه پشتیبانی می کند. مکانیزم برای هش کردن یکنواخت کلید به شما این امکان را می دهد که داده ها را به طور یکنواخت در بسیاری از سرورها قرار دهید، به طور منحصر به فرد هر کلید خاص را به یک سرور خاص از گروه آدرس دهی کنید، و ابزارهای failover داخلی، دسترسی بالای سرویس کش را تضمین می کند (اما، متأسفانه، اطلاعاتی وجود ندارد).

همه چیز با ذخیره‌سازی جلسه کمی بهتر است: می‌توانید پیکربندی کنید memcached.sess_number_of_replicas، در نتیجه داده ها به طور همزمان بر روی چندین سرور ذخیره می شوند و در صورت خرابی یک نمونه memcached، داده ها از سایرین منتقل می شود. با این حال، اگر سرور بدون داده آنلاین شود (همانطور که معمولاً پس از راه‌اندازی مجدد اتفاق می‌افتد)، برخی از کلیدها به نفع او توزیع می‌شوند. در واقع این به این معنی خواهد بود از دست دادن داده های جلسه، زیرا در صورت از دست دادن راهی برای "رفتن" به ماکت دیگری وجود ندارد.

ابزارهای استاندارد کتابخانه عمدتاً هدف قرار می گیرند افقی مقیاس بندی: آنها به شما اجازه می دهند حافظه پنهان را به اندازه های غول پیکر افزایش دهید و از کدهای میزبانی شده در سرورهای مختلف به آن دسترسی پیدا کنید. با این حال، در شرایط ما، حجم داده های ذخیره شده از چند گیگابایت فراتر نمی رود و عملکرد یک یا دو گره کاملاً کافی است. بر این اساس، تنها ابزار استاندارد مفید می تواند اطمینان از در دسترس بودن حافظه پنهان در حالی که حداقل یک نمونه کش در شرایط کار حفظ شود. با این حال، حتی امکان استفاده از این فرصت وجود نداشت... در اینجا لازم است که قدمت فریمورک مورد استفاده در پروژه را یادآوری کنیم، به همین دلیل است که نمی توان برنامه را با مجموعه ای از سرورها کار کرد. بیایید از دست دادن داده های جلسه را نیز فراموش نکنیم: چشم مشتری از خروج گسترده کاربران تکان خورد.

در حالت ایده آل مورد نیاز بود تکثیر رکوردها در memcached و بایpass replicas در صورت اشتباه یا اشتباه به ما در اجرای این استراتژی کمک کرد مکروتر.

مکروتر

این یک روتر memcached است که توسط فیس بوک برای حل مشکلات آن توسعه یافته است. این پروتکل متن memcached را پشتیبانی می کند، که اجازه می دهد مقیاس نصب memcached به نسبت های دیوانه کننده شرح مفصلی از mcrouter را می توان در اینجا یافت این اطلاعیه. در میان چیز های دیگر عملکرد گسترده می تواند کاری را که ما نیاز داریم انجام دهد:

  • Replicate Record;
  • در صورت بروز خطا به سایر سرورهای گروه بازگشتی مجدد انجام دهید.

دست به کار شوید!

پیکربندی مکروتر

من مستقیماً به کانفیگ می روم:

{
 "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که به طور تصادفی یک Pool یا مسیر را از بین اشیاء آرایه انتخاب می کند children. هر عنصر این آرایه به نوبه خود یک کنترل کننده است MissFailoverRoute، که از طریق هر سرور در استخر می گذرد تا زمانی که پاسخی با داده دریافت کند که به مشتری بازگردانده می شود.
  • اگر به طور انحصاری استفاده کنیم MissFailoverRoute با مجموعه‌ای از سه سرور، سپس همه درخواست‌ها ابتدا به اولین نمونه حافظه پنهان می‌آیند، و بقیه درخواست‌ها را بر اساس باقیمانده زمانی که داده‌ای وجود ندارد، دریافت می‌کنند. چنین رویکردی منجر به بار بیش از حد در اولین سرور در لیستبنابراین تصمیم بر این شد که سه استخر با آدرس‌هایی در توالی‌های مختلف تولید و به‌طور تصادفی انتخاب شوند.
  • تمام درخواست های دیگر (و این یک رکورد است) با استفاده از پردازش می شوند AllMajorityRoute. این کنترل کننده درخواست ها را به همه سرورهای موجود در استخر ارسال می کند و منتظر پاسخ از حداقل N/2 + 1 از آنها می باشد. از استفاده AllSyncRoute برای نوشتن عملیات باید رها می شد، زیرا این روش نیاز به پاسخ مثبت دارد از همه سرورهای گروه - در غیر این صورت باز خواهد گشت SERVER_ERROR. اگرچه mcrouter داده ها را به کش های موجود اضافه می کند، تابع فراخوانی PHP یک خطا برمی گرداند و اخطار ایجاد خواهد کرد. AllMajorityRoute چندان سختگیرانه نیست و اجازه می دهد تا نیمی از واحدها بدون مشکلاتی که در بالا توضیح داده شد از سرویس خارج شوند.

نقطه ضعف اصلی این طرح به این صورت است که اگر واقعاً هیچ داده ای در حافظه پنهان وجود نداشته باشد، برای هر درخواست از مشتری N درخواست به memcached در واقع اجرا می شود - به همه سرورها در استخر ما می‌توانیم تعداد سرورها را در استخرها، به عنوان مثال، به دو کاهش دهیم: فدا کردن قابلیت اطمینان ذخیره‌سازی،оسرعت بالاتر و بار کمتر از درخواست ها به کلیدهای گم شده.

NB: همچنین ممکن است پیوندهای مفیدی برای یادگیری mcrouter پیدا کنید مستندات در ویکی и مسائل پروژه (از جمله موارد بسته)، که نشان دهنده یک انبار کامل از پیکربندی های مختلف است.

ساخت و راه اندازی مکروتر

برنامه ما (و خود memcached) در Kubernetes اجرا می شود - بر این اساس، mcrouter نیز در آنجا قرار دارد. برای مونتاژ کانتینر ما استفاده می کنیم ورف، پیکربندی که برای آن به صورت زیر خواهد بود:

NB: لیست های ارائه شده در مقاله در مخزن منتشر شده است flant/mcrouter.

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' ]

(werf.yaml)

... و آن را ترسیم کنید نمودار هلم. نکته جالب این است که تنها یک سازنده پیکربندی بر اساس تعداد کپی وجود دارد (اگر کسی گزینه لاکونیک و ظریف تری دارد، آن را در نظرات به اشتراک بگذارد):

{{- $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 }}
         }
       }
     }
   }

(10-mcrouter.yaml)

ما آن را در محیط تست قرار می دهیم و بررسی می کنیم:

# 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 >

جست‌وجوی متن خطا نتیجه‌ای نداشت، اما برای پرس و جو «mcrouter php"در خط مقدم قدیمی ترین مشکل حل نشده پروژه بود - ضعف پشتیبانی پروتکل باینری memcached

NB: پروتکل ASCII در memcached کندتر از پروتکل باینری است و ابزارهای استاندارد هش کلید منسجم فقط با پروتکل باینری کار می کنند. اما این برای یک مورد خاص مشکلی ایجاد نمی کند.

ترفند در کیف است: تنها کاری که باید انجام دهید این است که به پروتکل ASCII بروید و همه چیز کار خواهد کرد. با این حال، در این مورد، عادت به دنبال پاسخ در مستندات در php.net شوخی بی رحمانه ای بازی کرد پاسخ صحیح را در آنجا پیدا نخواهید کرد... مگر اینکه، البته، به انتها، جایی که در بخش است، بروید "یادداشت های ارائه شده توسط کاربر" وفادار خواهد بود و پاسخ ناعادلانه منفی.

بله، نام گزینه صحیح است memcached.sess_binary_protocol. باید غیرفعال شود، پس از آن جلسات شروع به کار خواهند کرد. تنها چیزی که باقی می ماند این است که ظرف با mcrouter را با PHP در یک پاد قرار دهید!

نتیجه

بنابراین، فقط با تغییرات زیرساختی، ما توانستیم مشکل را حل کنیم: مشکل تحمل خطای memcached حل شده است، و قابلیت اطمینان ذخیره سازی کش افزایش یافته است. علاوه بر مزایای آشکار برای برنامه، این امکان را برای مانور در هنگام کار بر روی پلت فرم فراهم می کند: زمانی که همه اجزا دارای یک ذخیره هستند، عمر مدیر تا حد زیادی ساده می شود. بله، این روش معایب خود را نیز دارد، ممکن است شبیه یک "عصا" به نظر برسد، اما اگر در هزینه صرفه جویی می کند، مشکل را دفن می کند و باعث ایجاد موارد جدید نمی شود - چرا که نه؟

PS

در وبلاگ ما نیز بخوانید:

منبع: www.habr.com

اضافه کردن نظر