Memcached'i yatay olarak ölçeklendirmek için mcrouter kullanma

Memcached'i yatay olarak ölçeklendirmek için mcrouter kullanma

Herhangi bir dilde yüksek yüklü projeler geliştirmek, özel bir yaklaşım ve özel araçların kullanımını gerektirir, ancak konu PHP'deki uygulamalara geldiğinde durum o kadar ağırlaşabilir ki, örneğin şunu geliştirmeniz gerekir: kendi uygulama sunucusu. Bu notta, dağıtılmış oturum depolama ve memcached'de veri önbelleğe alma konusundaki tanıdık sıkıntılardan ve bu sorunları tek bir "koğuş" projesinde nasıl çözdüğümüzden bahsedeceğiz.

Bu olayın kahramanı, güncelleme iş planlarına hiç dahil olmayan, symfony 2.3 çerçevesini temel alan bir PHP uygulamasıdır. Oldukça standart oturum depolamaya ek olarak, bu projede "her şeyi önbelleğe alma" politikası memcached'de: veritabanına ve API sunucularına verilen isteklere yanıtlar, çeşitli işaretler, kod yürütmeyi senkronize etmek için kilitler ve çok daha fazlası. Böyle bir durumda memcached'in bozulması uygulamanın işleyişi açısından ölümcül hale gelir. Ek olarak, önbellek kaybı ciddi sonuçlara yol açar: DBMS dikişlerde patlamaya başlar, API hizmetleri istekleri yasaklamaya başlar vb. Durumu istikrara kavuşturmak onlarca dakika sürebilir ve bu süre zarfında hizmet çok yavaş olacak veya tamamen kullanılamayacaktır.

sağlamamız gerekiyordu uygulamayı çok az çabayla yatay olarak ölçeklendirme yeteneğiyani Kaynak kodunda minimum değişiklik yapıldı ve tam işlevsellik korundu. Önbelleği yalnızca arızalara karşı dayanıklı hale getirmekle kalmayın, aynı zamanda ondan kaynaklanan veri kaybını da en aza indirmeye çalışın.

Memcached'in kendisinde ne sorun var?

Genel olarak, PHP için memcached uzantısı, dağıtılmış verileri ve oturum depolamayı kutudan çıktığı haliyle destekler. Tutarlı anahtar karma mekanizması, verileri birçok sunucuya eşit şekilde yerleştirmenize olanak tanır, her bir anahtarı gruptaki belirli bir sunucuya benzersiz şekilde adresler ve yerleşik yük devretme araçları, önbellekleme hizmetinin yüksek düzeyde kullanılabilirliğini sağlar (ancak ne yazık ki, veri yok).

Oturum depolamada işler biraz daha iyi: yapılandırabilirsiniz memcached.sess_number_of_replicasbunun sonucunda veriler aynı anda birkaç sunucuda depolanacak ve memcached örneğinin arızalanması durumunda veriler diğerlerinden aktarılacaktır. Ancak sunucu veri olmadan tekrar çevrimiçi olursa (genellikle yeniden başlatma sonrasında olduğu gibi), bazı anahtarlar sunucunun lehine yeniden dağıtılacaktır. Aslında bu şu anlama gelecektir oturum verilerinin kaybıçünkü bir hata durumunda başka bir kopyaya "gitmenin" bir yolu yoktur.

Standart kütüphane araçları temel olarak aşağıdakilere yöneliktir: yatay ölçeklendirme: önbelleği devasa boyutlara çıkarmanıza ve farklı sunucularda barındırılan koddan ona erişim sağlamanıza olanak tanır. Ancak bizim durumumuzda depolanan verilerin hacmi birkaç gigabaytı geçmiyor ve bir veya iki düğümün performansı oldukça yeterli. Buna göre, tek yararlı standart araç, en az bir önbellek örneğini çalışır durumda tutarken memcached'in kullanılabilirliğini sağlamak olabilir. Ancak bu fırsattan bile yararlanmak mümkün olmadı... Burada projede kullanılan framework'ün eskiliğini hatırlatmakta fayda var, bu yüzden uygulamanın bir sunucu havuzuyla çalışmasını sağlamak imkansızdı. Oturum verilerinin kaybını da unutmayalım: Kullanıcıların kitlesel çıkış yapması nedeniyle müşterinin gözü seğirdi.

İdeal olarak gerekliydi Memcached'deki kayıtların kopyalanması ve kopyaların atlanması bir hata veya hata durumunda. Bu stratejiyi uygulamamıza yardımcı oldu mcrouter.

mcrouter

Bu, Facebook'un sorunlarını çözmek için geliştirdiği memcached bir yönlendiricidir. Memcached metin protokolünü destekler; memcached kurulumlarını ölçeklendirme çılgın oranlara. Mcrouter'ın ayrıntılı bir açıklamasını şu adreste bulabilirsiniz: bu duyuru. Diğer şeylerin yanı sıra geniş işlevsellik ihtiyacımız olanı yapabilir:

  • kaydı çoğaltın;
  • Bir hata meydana gelirse gruptaki diğer sunuculara geri dönüş yapın.

Sebep için!

mcrouter yapılandırması

Doğrudan yapılandırmaya gideceğim:

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

Neden üç havuz? Sunucular neden tekrarlanıyor? Nasıl çalıştığını anlayalım.

  • Bu konfigürasyonda mcrouter request komutuna göre isteğin gönderileceği yolu seçer. Adam ona bunu söylüyor OperationSelectorRoute.
  • GET istekleri işleyiciye gider RandomRoutedizi nesneleri arasında rastgele bir havuz veya rota seçer children. Bu dizinin her öğesi sırasıyla bir işleyicidir MissFailoverRoute, istemciye döndürülecek verilerle bir yanıt alana kadar havuzdaki her sunucudan geçecek.
  • Eğer özel olarak kullanırsak MissFailoverRoute Üç sunucudan oluşan bir havuzda, tüm istekler önce ilk memcached örneğine gelir ve geri kalanlar, hiçbir veri olmadığında artık istekleri alır. Böyle bir yaklaşım şuna yol açacaktır: listedeki ilk sunucuda aşırı yük, bu nedenle farklı sıralarda adreslere sahip üç havuz oluşturulup bunların rastgele seçilmesine karar verildi.
  • Diğer tüm istekler (ve bu bir kayıttır) kullanılarak işlenir. AllMajorityRoute. Bu işleyici, havuzdaki tüm sunuculara istek gönderir ve bunlardan en az N/2 + 1'inden yanıt bekler. Kullanımdan itibaren AllSyncRoute yazma işlemlerinin terk edilmesi gerekti, çünkü bu yöntem olumlu bir yanıt gerektiriyor Tüm gruptaki sunucular - aksi halde geri dönecektir SERVER_ERROR. mcrouter verileri mevcut önbelleklere ekleyecek olsa da, çağıran PHP işlevi bir hata döndürecek ve bildirim oluşturacaktır. AllMajorityRoute o kadar katı değildir ve yukarıda açıklanan sorunlar olmadan ünitelerin yarısına kadarının hizmet dışı bırakılmasına izin verir.

Ana eksi Bu şema, önbellekte gerçekten veri yoksa, istemciden gelen her istek için memcached'e yönelik N isteğin gerçekte yürütüleceği anlamına gelir - herkes havuzdaki sunucular. Havuzlardaki sunucu sayısını örneğin ikiye düşürebiliriz: depolama güvenilirliğinden ödün vererekоisteklerden eksik anahtarlara kadar daha yüksek hız ve daha az yük.

NB: Mcrouter'ı öğrenmek için de yararlı bağlantılar bulabilirsiniz wiki'deki belgeler и proje sorunları (kapalı olanlar dahil), çeşitli konfigürasyonlardan oluşan bir deponun tamamını temsil eder.

Mcrouter'ı kurma ve çalıştırma

Uygulamamız (ve memcached'in kendisi) Kubernetes'te çalışıyor - buna göre mcrouter da orada bulunuyor. İçin konteyner montajı kullanırız Werf, yapılandırması şu şekilde görünecektir:

NB: Makalede verilen listeler arşivde yayınlanmaktadır. 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)

... ve taslağını çizin Dümen tablosu. İlginç olan, yalnızca kopya sayısına dayalı bir yapılandırma oluşturucunun bulunmasıdır. (Daha özlü ve zarif bir seçeneği olan varsa yorumlarda 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)

Bunu test ortamına yayıyoruz ve şunları kontrol ediyoruz:

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

Hata metninin aranması herhangi bir sonuç vermedi ancak “ sorgusu içinmcrouter php"Projenin en eski çözülmemiş sorunu ön plandaydı - destek eksikliği Memcached ikili protokolü.

NB: Memcached'deki ASCII protokolü ikili protokolden daha yavaştır ve tutarlı anahtar karma işleminin standart araçları yalnızca ikili protokolle çalışır. Ancak bu belirli bir durum için sorun yaratmaz.

İşin püf noktası çantada: tek yapmanız gereken ASCII protokolüne geçmek ve her şey işe yarayacak.... Ancak bu durumda cevapları arama alışkanlığı php.net'teki belgeler acımasız bir şaka yaptı. Orada doğru cevabı bulamazsınız... tabii ki bölümün sonuna kadar kaydırmadığınız sürece "Kullanıcının katkıda bulunduğu notlar" sadık olacak ve haksız yere reddedilen cevap.

Evet, doğru seçenek adı memcached.sess_binary_protocol. Devre dışı bırakılması gerekir, bundan sonra oturumlar çalışmaya başlayacaktır. Geriye kalan tek şey, mcrouter'lı kabı PHP'li bir pod'a koymak!

Sonuç

Böylece yalnızca altyapısal değişikliklerle sorunu çözmeyi başardık: Memcached hata toleransı sorunu çözüldü ve önbellek depolamanın güvenilirliği artırıldı. Uygulamanın bariz avantajlarına ek olarak, bu, platform üzerinde çalışırken manevra alanı sağladı: tüm bileşenlerin bir rezervi olduğunda, yöneticinin ömrü büyük ölçüde basitleştirildi. Evet, bu yöntemin de dezavantajları var, bir "koltuk değneği" gibi görünebilir, ancak paradan tasarruf sağlarsa, sorunu gömer ve yenilerine neden olmazsa - neden olmasın?

PS

Blogumuzda da okuyun:

Kaynak: habr.com

Yorum ekle