Memcached üfüqi miqyasda mcrouter istifadə edin

Memcached üfüqi miqyasda mcrouter istifadə edin

İstənilən dildə yüksək yüklü layihələrin hazırlanması xüsusi yanaşma və xüsusi alətlərdən istifadə tələb edir, lakin PHP-də tətbiqlərə gəldikdə, vəziyyət o qədər ağırlaşa bilər ki, siz, məsələn, öz proqram serveri. Bu qeyddə paylanmış sessiya saxlama və memcached-də məlumatların keşləşdirilməsi ilə bağlı tanış ağrılardan və bu problemləri bir "palata" layihəsində necə həll etdiyimizdən danışacağıq.

Tədbirin qəhrəmanı symfony 2.3 çərçivəsinə əsaslanan və yeniləmək üçün biznes planlarına ümumiyyətlə daxil edilməyən PHP proqramıdır. Olduqca standart sessiya saxlama ilə yanaşı, bu layihə tam istifadə etdi "hər şeyi keşləmə" siyasəti memcached-də: verilənlər bazası və API serverlərinə sorğulara cavablar, müxtəlif bayraqlar, kodun icrasını sinxronlaşdırmaq üçün kilidlər və daha çox. Belə bir vəziyyətdə, memcached-in pozulması tətbiqin işləməsi üçün ölümcül olur. Bundan əlavə, keş itkisi ciddi nəticələrə gətirib çıxarır: DBMS tikişlərdə partlamağa başlayır, API xidmətləri sorğuları qadağan etməyə başlayır və s. Vəziyyəti sabitləşdirmək onlarla dəqiqə çəkə bilər və bu müddət ərzində xidmət çox yavaş və ya tamamilə əlçatmaz olacaq.

təmin etməli idik az səylə tətbiqi üfüqi miqyasda genişləndirmək imkanı, yəni. mənbə koduna minimal dəyişikliklər və tam funksionallıq qorunub saxlanılır. Keşi yalnız uğursuzluqlara qarşı davamlı deyil, həm də ondan məlumat itkisini minimuma endirməyə çalışın.

Memcached-in özündə nə olub?

Ümumiyyətlə, PHP üçün memcached genişləndirilməsi paylanmış məlumatları və qutudan kənar sessiya saxlanmasını dəstəkləyir. Ardıcıl açar hashing mexanizmi, hər bir xüsusi açarı qrupdan müəyyən bir serverə unikal şəkildə ünvanlayaraq, məlumatları bir çox serverlərdə bərabər paylamağa imkan verir və quraşdırılmış əvəzetmə alətləri keşləmə xidmətinin yüksək əlçatanlığını təmin edir (lakin, təəssüf ki, məlumat yoxdur).

Sessiya yaddaşı ilə işlər bir az daha yaxşıdır: siz konfiqurasiya edə bilərsiniz memcached.sess_number_of_replicas, bunun nəticəsində məlumatlar eyni anda bir neçə serverdə saxlanacaq və bir memcached instansiyası uğursuz olarsa, məlumatlar digərlərindən ötürüləcəkdir. Bununla belə, əgər server məlumatsız onlayn olarsa (adətən yenidən işə salındıqdan sonra baş verir), bəzi açarlar onun xeyrinə yenidən paylanacaq. Əslində bu demək olacaq sessiya məlumatlarının itirilməsi, çünki buraxılmış halda başqa replikaya “getmək” üçün heç bir yol yoxdur.

Standart kitabxana alətləri əsasən hədəflənir üfüqi miqyaslama: onlar sizə önbelleği nəhəng ölçülərə qədər artırmağa və müxtəlif serverlərdə yerləşdirilən koddan ona girişi təmin etməyə imkan verir. Bununla belə, bizim vəziyyətimizdə saxlanılan məlumatların həcmi bir neçə gigabaytı keçmir və bir və ya iki qovşağın performansı olduqca kifayətdir. Müvafiq olaraq, yeganə faydalı standart alətlər ən azı bir keş instansiyasını işlək vəziyyətdə saxlayarkən memcached-in mövcudluğunu təmin etmək ola bilər. Lakin bu fürsətdən belə yararlanmaq mümkün olmadı... Bu yerdə layihədə istifadə olunan çərçivənin qədimliyini xatırlatmaq yerinə düşər, bu səbəbdən proqramın serverlər hovuzu ilə işləməsinə nail olmaq mümkün deyildi. Sessiya məlumatlarının itirilməsini də unutmayaq: istifadəçilərin kütləvi çıxışından müştərinin gözü qıvrıldı.

İdeal olaraq tələb olunurdu memcached və bypass replikalarda qeydlərin təkrarlanması səhv və ya səhv olduqda. Bu strategiyanı həyata keçirməkdə bizə kömək etdi mcrouter.

mcrouter

Bu, Facebook tərəfindən problemlərini həll etmək üçün hazırlanmış yaddaş yaddaşlı marşrutlaşdırıcıdır. O, imkan verən memcached mətn protokolunu dəstəkləyir memcached quraşdırmaların miqyası ağılsız nisbətlərə. Mcrouter-in ətraflı təsviri ilə tanış ola bilərsiniz bu elan. Digər şeylər arasında geniş funksionallıq bizə lazım olanı edə bilər:

  • təkrar qeyd;
  • səhv baş verərsə, qrupdakı digər serverlərə geri dönün.

İşə get!

mcrouter konfiqurasiyası

Mən birbaşa konfiqurasiyaya keçəcəyəm:

{
 "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"
       ]
     }
   }
 }
}

Niyə üç hovuz? Niyə serverlər təkrarlanır? Bunun necə işlədiyini anlayaq.

  • Bu konfiqurasiyada mcrouter sorğu əmri əsasında sorğunun göndəriləcəyi yolu seçir. Oğlan bunu ona deyir OperationSelectorRoute.
  • GET sorğuları işləyiciyə gedir RandomRoutemassiv obyektləri arasında təsadüfi olaraq hovuz və ya marşrut seçir children. Bu massivin hər bir elementi öz növbəsində işləyicidir MissFailoverRoute, müştəriyə qaytarılacaq məlumatlarla cavab alana qədər hovuzdakı hər bir serverdən keçəcək.
  • Yalnız istifadə etsək MissFailoverRoute üç serverdən ibarət bir hovuzla, bütün sorğular ilk olaraq ilk yaddaşda saxlanılan nümunəyə gələcək, qalanları isə heç bir məlumat olmadıqda qalıq əsasda sorğuları qəbul edəcək. Belə bir yanaşma gətirib çıxaracaq siyahıdakı ilk serverdə həddindən artıq yük, buna görə də müxtəlif ardıcıllıqla ünvanları olan üç hovuz yaratmaq və onları təsadüfi seçmək qərara alındı.
  • Bütün digər sorğular (və bu rekorddur) istifadə edilərək işlənir AllMajorityRoute. Bu işləyici hovuzdakı bütün serverlərə sorğular göndərir və onlardan ən azı N/2 + 1-dən cavab gözləyir. İstifadədən AllSyncRoute yazmaq üçün əməliyyatlardan imtina edilməli idi, çünki bu metoddan müsbət cavab tələb olunur Bütün qrupdakı serverlər - əks halda geri qayıdacaq SERVER_ERROR. mcrouter verilənləri mövcud keşlərə əlavə etsə də, çağıran PHP funksiyası xəta qaytaracaq və bildiriş yaradacaq. AllMajorityRoute o qədər də sərt deyil və yuxarıda təsvir olunan problemlər olmadan bölmələrin yarısına qədərini istismardan çıxarmağa imkan verir.

Əsas çatışmazlıq Bu sxem ondan ibarətdir ki, əgər keşdə həqiqətən heç bir məlumat yoxdursa, müştərinin hər sorğusu üçün memcached üçün N sorğusu həqiqətən yerinə yetiriləcəkdir - bütün hovuzdakı serverlər. Hovuzlardakı serverlərin sayını, məsələn, ikiyə endirə bilərik: saxlama etibarlılığını qurban verərək, əldə edirikоdaha yüksək sürət və sorğulardan itkin düymələrə qədər daha az yük.

NB: Siz həmçinin mcrouter öyrənmək üçün faydalı bağlantılar tapa bilərsiniz vikidə sənədlər и layihə məsələləri (qapalı olanlar da daxil olmaqla), müxtəlif konfiqurasiyaların bütün anbarını təmsil edir.

mcrouter qurmaq və işlətmək

Tətbiqimiz (və memcached özü) Kubernetes-də işləyir - müvafiq olaraq, mcrouter də orada yerləşir. üçün konteyner montajı istifadə edirik werf, konfiqurasiyası belə görünəcək:

NB: Məqalədə verilmiş siyahılar repozitoriyada dərc olunur 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)

... və eskizini çəkin Sükan diaqramı. Maraqlısı odur ki, yalnız replikaların sayına əsaslanan konfiqurasiya generatoru var (kimin daha lakonik və eleqant variantı varsa, şərhlərdə paylaşın):

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

Onu sınaq mühitinə yayırıq və yoxlayırıq:

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

Səhv mətnini axtarmaq heç bir nəticə vermədi, ancaq sorğu üçün "microuter php“Ön planda layihənin ən qədim həll olunmamış problemi idi - dəstəyin olmaması memcached ikili protokol.

NB: Memcached-də ASCII protokolu binar protokoldan daha yavaşdır və ardıcıl açar hashing üçün standart vasitələr yalnız ikili protokolla işləyir. Amma bu konkret hal üçün problem yaratmır.

Hiylə çantadadır: yalnız ASCII protokoluna keçmək lazımdır və hər şey işləyəcək.... Ancaq bu vəziyyətdə cavab axtarma vərdişi php.net-də sənədlər qəddar zarafat etdi. Düzgün cavabı orada tapa bilməyəcəksiniz... əlbəttə ki, bölmənin sonuna qədər hərəkət etməsəniz "İstifadəçinin töhfə verdiyi qeydlər" sadiq olacaq və ədalətsiz aşağı səs verilən cavab.

Bəli, düzgün seçim adıdır memcached.sess_binary_protocol. Onu söndürmək lazımdır, bundan sonra seanslar işə başlayacaq. Qalır ki, mcrouter ilə konteyneri PHP ilə podda yerləşdirin!

Nəticə

Beləliklə, sadəcə infrastruktur dəyişiklikləri ilə biz problemi həll edə bildik: memcached nasazlığa dözümlülük problemi həll edildi və keş yaddaşının etibarlılığı artırıldı. Tətbiq üçün aşkar üstünlüklərə əlavə olaraq, bu, platformada işləyərkən manevr üçün yer verdi: bütün komponentlər ehtiyata malik olduqda, administratorun həyatı xeyli sadələşir. Bəli, bu metodun da çatışmazlıqları var, o, "köpək" kimi görünə bilər, amma pula qənaət edirsə, problemi basdırırsa və yenilərinə səbəb olmursa - niyə də olmasın?

PS

Bloqumuzda da oxuyun:

Mənbə: www.habr.com

Добавить комментарий