Menggunakan mcrouter untuk menskala memcached secara mendatar

Menggunakan mcrouter untuk menskala memcached secara mendatar

Membangunkan projek beban tinggi dalam mana-mana bahasa memerlukan pendekatan khas dan penggunaan alat khas, tetapi apabila ia berkaitan dengan aplikasi dalam PHP, keadaan boleh menjadi lebih teruk sehingga anda perlu membangunkan, sebagai contoh, pelayan aplikasi sendiri. Dalam nota ini kita akan bercakap tentang kesakitan biasa dengan storan sesi teragih dan caching data dalam memcached dan cara kami menyelesaikan masalah ini dalam satu projek "wad".

Wira acara ini ialah aplikasi PHP berdasarkan rangka kerja symfony 2.3, yang tidak termasuk sama sekali dalam rancangan perniagaan untuk dikemas kini. Selain storan sesi yang agak standard, projek ini menggunakan sepenuhnya dasar "caching segala-galanya". dalam memcached: respons kepada permintaan kepada pangkalan data dan pelayan API, pelbagai bendera, kunci untuk menyegerakkan pelaksanaan kod dan banyak lagi. Dalam keadaan sedemikian, pecahan memcached menjadi maut kepada pengendalian aplikasi. Di samping itu, kehilangan cache membawa kepada akibat yang serius: DBMS mula pecah, perkhidmatan API mula melarang permintaan, dsb. Menstabilkan keadaan mungkin mengambil masa berpuluh-puluh minit, dan pada masa ini perkhidmatan akan menjadi sangat perlahan atau tidak tersedia sepenuhnya.

Kami perlu menyediakan keupayaan untuk skala mendatar aplikasi dengan sedikit usaha, iaitu dengan perubahan minimum pada kod sumber dan fungsi penuh dipelihara. Jadikan cache bukan sahaja tahan terhadap kegagalan, tetapi juga cuba meminimumkan kehilangan data daripadanya.

Apa yang salah dengan memcached sendiri?

Secara umum, sambungan memcached untuk PHP menyokong data teragih dan storan sesi di luar kotak. Mekanisme untuk pencincangan kunci yang konsisten membolehkan anda meletakkan data secara sama rata pada banyak pelayan, secara unik menangani setiap kunci khusus kepada pelayan tertentu daripada kumpulan, dan alatan failover terbina dalam memastikan ketersediaan perkhidmatan caching yang tinggi (tetapi, malangnya, tiada data).

Perkara menjadi lebih baik sedikit dengan storan sesi: anda boleh mengkonfigurasi memcached.sess_number_of_replicas, akibatnya data akan disimpan pada beberapa pelayan sekaligus, dan sekiranya berlaku kegagalan satu contoh memcached, data akan dipindahkan daripada yang lain. Walau bagaimanapun, jika pelayan kembali dalam talian tanpa data (seperti biasanya berlaku selepas dimulakan semula), beberapa kunci akan diedarkan semula memihak kepadanya. Sebenarnya ini akan bermakna kehilangan data sesi, kerana tiada cara untuk "pergi" ke replika lain sekiranya berlaku kesilapan.

Alat perpustakaan standard ditujukan terutamanya kepada mendatar penskalaan: mereka membenarkan anda meningkatkan cache kepada saiz yang sangat besar dan memberikan akses kepadanya daripada kod yang dihoskan pada pelayan yang berbeza. Walau bagaimanapun, dalam keadaan kami, jumlah data yang disimpan tidak melebihi beberapa gigabait, dan prestasi satu atau dua nod adalah cukup. Sehubungan itu, satu-satunya alat standard yang berguna adalah untuk memastikan ketersediaan memcached sambil mengekalkan sekurang-kurangnya satu contoh cache dalam keadaan berfungsi. Walau bagaimanapun, peluang ini tidak dapat dimanfaatkan... Di sini adalah wajar untuk mengingati kerangka kerja yang lama yang digunakan dalam projek, itulah sebabnya adalah mustahil untuk mendapatkan aplikasi berfungsi dengan sekumpulan pelayan. Jangan lupa juga tentang kehilangan data sesi: mata pelanggan berkedut akibat log keluar besar-besaran pengguna.

Sebaik-baiknya ia diperlukan replikasi rekod dalam replika memcach dan memintas sekiranya berlaku kesilapan atau kesilapan. Membantu kami melaksanakan strategi ini mcrouter.

mcrouter

Ini adalah penghala memcached yang dibangunkan oleh Facebook untuk menyelesaikan masalahnya. Ia menyokong protokol teks memcached, yang membolehkan pemasangan memcached skala kepada perkadaran gila. Penerangan terperinci tentang mcrouter boleh didapati dalam pengumuman ini. Antara lain fungsi yang luas ia boleh melakukan apa yang kita perlukan:

  • rekod replika;
  • lakukan sandaran kepada pelayan lain dalam kumpulan jika ralat berlaku.

Dapatkan perniagaan!

konfigurasi mcrouter

Saya akan pergi terus ke konfigurasi:

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

Mengapa tiga kolam? Mengapa pelayan diulang? Mari kita fikirkan bagaimana ia berfungsi.

  • Dalam konfigurasi ini, mcrouter memilih laluan ke mana permintaan akan dihantar berdasarkan arahan permintaan. Lelaki itu memberitahunya ini OperationSelectorRoute.
  • Dapatkan permintaan pergi kepada pengendali RandomRouteyang secara rawak memilih kumpulan atau laluan antara objek tatasusunan children. Setiap elemen tatasusunan ini pula merupakan pengendali MissFailoverRoute, yang akan melalui setiap pelayan dalam kolam sehingga ia menerima respons dengan data, yang akan dikembalikan kepada pelanggan.
  • Jika kita menggunakan secara eksklusif MissFailoverRoute dengan kumpulan tiga pelayan, maka semua permintaan akan datang dahulu ke contoh memcach pertama, dan selebihnya akan menerima permintaan secara baki apabila tiada data. Pendekatan sedemikian akan membawa kepada beban berlebihan pada pelayan pertama dalam senarai, jadi diputuskan untuk menjana tiga kumpulan dengan alamat dalam urutan yang berbeza dan memilihnya secara rawak.
  • Semua permintaan lain (dan ini adalah rekod) diproses menggunakan AllMajorityRoute. Pengendali ini menghantar permintaan kepada semua pelayan dalam kumpulan dan menunggu respons daripada sekurang-kurangnya N/2 + 1 daripada mereka. Daripada penggunaan AllSyncRoute untuk operasi tulis terpaksa ditinggalkan, kerana kaedah ini memerlukan tindak balas positif daripada Semua pelayan dalam kumpulan - jika tidak ia akan kembali SERVER_ERROR. Walaupun mcrouter akan menambah data ke cache yang tersedia, fungsi PHP panggilan akan mengembalikan ralat dan akan menjana notis. AllMajorityRoute tidak begitu ketat dan membenarkan sehingga separuh daripada unit dikeluarkan daripada perkhidmatan tanpa masalah yang diterangkan di atas.

Kelemahan utama Skim ini ialah jika benar-benar tiada data dalam cache, maka untuk setiap permintaan daripada klien N permintaan untuk memcached sebenarnya akan dilaksanakan - untuk semua pelayan di kolam. Kami boleh mengurangkan bilangan pelayan dalam kumpulan, sebagai contoh, kepada dua: mengorbankan kebolehpercayaan storan, kami mendapatΠΎkelajuan yang lebih tinggi dan kurang beban daripada permintaan kepada kunci yang hilang.

NB: Anda juga boleh mencari pautan berguna untuk mempelajari mcrouter dokumentasi di wiki ΠΈ isu projek (termasuk yang tertutup), mewakili keseluruhan gudang pelbagai konfigurasi.

Membina dan menjalankan mcrouter

Aplikasi kami (dan memcached sendiri) berjalan dalam Kubernetes - sewajarnya, mcrouter juga terletak di sana. Untuk pemasangan kontena kami guna werf, konfigurasi yang akan kelihatan seperti ini:

NB: Penyenaraian yang diberikan dalam artikel diterbitkan dalam repositori 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)

... dan lukiskannya Carta helm. Perkara yang menarik ialah hanya terdapat penjana konfigurasi berdasarkan bilangan replika (jika sesiapa mempunyai pilihan yang lebih singkat dan elegan, kongsikannya dalam komen):

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

Kami melancarkannya ke dalam persekitaran ujian dan menyemak:

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

Mencari teks ralat tidak memberikan sebarang hasil, tetapi untuk pertanyaan "mikrouter php"Di barisan hadapan adalah masalah tertua yang belum diselesaikan projek itu - kekurangan sokongan protokol binari memcached.

NB: Protokol ASCII dalam memcached adalah lebih perlahan daripada yang binari, dan cara standard pencincangan kunci yang konsisten hanya berfungsi dengan protokol binari. Tetapi ini tidak menimbulkan masalah untuk kes tertentu.

Caranya ada di dalam beg: apa yang anda perlu lakukan ialah beralih kepada protokol ASCII dan semuanya akan berfungsi.... Walau bagaimanapun, dalam kes ini, tabiat mencari jawapan dalam dokumentasi di php.net bermain jenaka yang kejam. Anda tidak akan menemui jawapan yang betul di sana... melainkan, sudah tentu, anda tatal ke penghujung, di mana dalam bahagian "Nota sumbangan pengguna" akan setia dan jawapan yang ditolak secara tidak adil.

Ya, nama pilihan yang betul ialah memcached.sess_binary_protocol. Ia mesti dilumpuhkan, selepas itu sesi akan mula berfungsi. Apa yang tinggal ialah meletakkan bekas dengan mcrouter ke dalam pod dengan PHP!

Kesimpulan

Oleh itu, dengan hanya perubahan infrastruktur kami dapat menyelesaikan masalah: isu dengan toleransi kesalahan memcached telah diselesaikan, dan kebolehpercayaan storan cache telah ditingkatkan. Sebagai tambahan kepada kelebihan yang jelas untuk aplikasi, ini memberi ruang untuk bergerak semasa bekerja di platform: apabila semua komponen mempunyai rizab, kehidupan pentadbir sangat dipermudahkan. Ya, kaedah ini juga mempunyai kelemahannya, ia mungkin kelihatan seperti "tongkat", tetapi jika ia menjimatkan wang, menguburkan masalah dan tidak menyebabkan yang baru - mengapa tidak?

PS

Baca juga di blog kami:

Sumber: www.habr.com

Tambah komen