Menggunakan mcrouter untuk menskalakan memcached secara horizontal

Menggunakan mcrouter untuk menskalakan memcached secara horizontal

Mengembangkan proyek dengan beban tinggi dalam bahasa apa pun memerlukan pendekatan khusus dan penggunaan alat khusus, tetapi jika menyangkut aplikasi dalam PHP, situasinya bisa menjadi sangat buruk sehingga Anda harus mengembangkan, misalnya, server aplikasi sendiri. Dalam catatan ini kita akan berbicara tentang masalah yang biasa terjadi dengan penyimpanan sesi terdistribusi dan cache data di memcached dan bagaimana kami memecahkan masalah ini dalam satu proyek β€œbangsal”.

Pahlawan pada kesempatan ini adalah aplikasi PHP berdasarkan kerangka symfony 2.3, yang sama sekali tidak termasuk dalam rencana bisnis untuk diperbarui. Selain penyimpanan sesi yang cukup standar, proyek ini memanfaatkan sepenuhnya kebijakan "menyimpan semuanya dalam cache". di memcached: respons terhadap permintaan ke database dan server API, berbagai flag, kunci untuk menyinkronkan eksekusi kode, dan banyak lagi. Dalam situasi seperti ini, kerusakan memcached berakibat fatal bagi pengoperasian aplikasi. Selain itu, hilangnya cache menyebabkan konsekuensi serius: DBMS mulai meledak, layanan API mulai melarang permintaan, dll. Menstabilkan situasi mungkin memerlukan waktu puluhan menit, dan selama waktu ini layanan akan sangat lambat atau tidak tersedia sama sekali.

Kami perlu menyediakan kemampuan untuk menskalakan aplikasi secara horizontal dengan sedikit usaha, yaitu. dengan sedikit perubahan pada kode sumber dan fungsionalitas penuh dipertahankan. Jadikan cache tidak hanya tahan terhadap kegagalan, tetapi juga cobalah meminimalkan kehilangan data darinya.

Apa yang salah dengan memcached itu sendiri?

Secara umum, ekstensi memcached untuk PHP mendukung data terdistribusi dan penyimpanan sesi secara langsung. Mekanisme hashing kunci yang konsisten memungkinkan Anda menempatkan data secara merata di banyak server, secara unik mengalamatkan setiap kunci tertentu ke server tertentu dari grup, dan alat failover bawaan memastikan ketersediaan layanan caching yang tinggi (tetapi, sayangnya, tidak ada data).

Segalanya menjadi sedikit lebih baik dengan penyimpanan sesi: Anda dapat mengonfigurasinya memcached.sess_number_of_replicas, akibatnya data akan disimpan di beberapa server sekaligus, dan jika terjadi kegagalan pada satu instance memcached, data akan ditransfer dari server lain. Namun, jika server kembali online tanpa data (seperti yang biasanya terjadi setelah restart), beberapa kunci akan didistribusikan kembali sesuai keinginannya. Sebenarnya ini berarti hilangnya data sesi, karena tidak ada cara untuk β€œpergi” ke replika lain jika terjadi kesalahan.

Alat perpustakaan standar ditujukan terutama untuk horisontal penskalaan: mereka memungkinkan Anda meningkatkan cache ke ukuran raksasa dan menyediakan akses ke sana dari kode yang dihosting di server berbeda. Namun, dalam situasi kami, jumlah data yang disimpan tidak melebihi beberapa gigabyte, dan kinerja satu atau dua node sudah cukup. Oleh karena itu, satu-satunya alat standar yang berguna adalah memastikan ketersediaan memcached sambil mempertahankan setidaknya satu instance cache dalam kondisi berfungsi. Namun, peluang ini pun tidak dapat dimanfaatkan... Di sini perlu diingat kekunoan kerangka kerja yang digunakan dalam proyek, itulah sebabnya tidak mungkin membuat aplikasi berfungsi dengan kumpulan server. Jangan lupa juga tentang hilangnya data sesi: mata pelanggan berkedut karena banyaknya pengguna yang logout.

Idealnya hal itu diperlukan replikasi catatan dalam memcached dan melewati replika jika terjadi kesalahan atau kekeliruan. Membantu kami menerapkan strategi ini mcrouter.

mcrouter

Ini adalah router memcached yang dikembangkan oleh Facebook untuk mengatasi masalahnya. Ini mendukung protokol teks memcached, yang memungkinkan skala instalasi memcached dengan proporsi yang gila. Penjelasan rinci tentang mcrouter dapat ditemukan di pengumuman ini. Antara lain fungsionalitas yang luas itu dapat melakukan apa yang kita butuhkan:

  • mereplikasi catatan;
  • lakukan fallback ke server lain di grup jika terjadi error.

Mulai bekerja!

konfigurasi mcrouter

Saya akan langsung ke konfigurasinya:

{
 "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 server diulang? Mari kita cari tahu cara kerjanya.

  • Dalam konfigurasi ini, mcrouter memilih jalur tujuan pengiriman permintaan berdasarkan perintah request. Pria itu memberitahunya hal ini OperationSelectorRoute.
  • GET permintaan pergi ke handler RandomRouteyang secara acak memilih kumpulan atau rute di antara objek array children. Setiap elemen array ini pada gilirannya merupakan penangan MissFailoverRoute, yang akan melewati setiap server di kumpulan hingga menerima respons dengan data, yang akan dikembalikan ke klien.
  • Jika kita menggunakan secara eksklusif MissFailoverRoute dengan kumpulan tiga server, maka semua permintaan akan didahulukan ke instance memcached pertama, dan sisanya akan menerima permintaan secara sisa ketika tidak ada data. Pendekatan seperti ini akan mengarah pada beban berlebihan pada server pertama dalam daftar, jadi diputuskan untuk membuat tiga kumpulan dengan alamat dalam urutan berbeda dan memilihnya secara acak.
  • Semua permintaan lainnya (dan ini adalah catatan) diproses menggunakan AllMajorityRoute. Penangan ini mengirimkan permintaan ke semua server di kumpulan dan menunggu tanggapan dari setidaknya N/2 + 1 server. Dari penggunaan AllSyncRoute untuk operasi tulis harus ditinggalkan, karena metode ini memerlukan respon positif dari Semua server dalam grup - jika tidak maka akan kembali SERVER_ERROR. Meskipun mcrouter akan menambahkan data ke cache yang tersedia, pemanggilan fungsi PHP akan mengembalikan kesalahan dan akan menghasilkan pemberitahuan. AllMajorityRoute tidak terlalu ketat dan memungkinkan hingga setengah unit tidak dapat digunakan tanpa masalah yang dijelaskan di atas.

Kerugian utama Skema ini adalah jika memang tidak ada data di cache, maka untuk setiap permintaan dari klien N permintaan ke memcached akan benar-benar dieksekusi - ke semua server di kolam. Kita dapat mengurangi jumlah server dalam kumpulan, misalnya, menjadi dua: kita mengorbankan keandalan penyimpananΠΎkecepatan lebih tinggi dan lebih sedikit beban dari permintaan hingga kunci yang hilang.

NB: Anda juga dapat menemukan tautan berguna untuk mempelajari mcrouter dokumentasi di wiki ΠΈ masalah proyek (termasuk yang tertutup), mewakili seluruh gudang dengan berbagai konfigurasi.

Membangun dan menjalankan mcrouter

Aplikasi kita (dan memcached itu sendiri) berjalan di Kubernetes - karenanya, mcrouter juga terletak di sana. Untuk perakitan kontainer kita gunakan wer, konfigurasinya akan terlihat seperti ini:

NB: Daftar yang diberikan dalam artikel dipublikasikan di repositori datar/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 buat sketsanya Bagan helm. Menariknya, yang ada hanya config generator berdasarkan jumlah replika (jika ada yang punya opsi yang lebih ringkas dan elegan, bagikan di komentar):

{{- $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 meluncurkannya ke lingkungan pengujian dan memeriksa:

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

Pencarian teks kesalahan tidak memberikan hasil apa pun, tetapi untuk kueri β€œmikrouter php"Yang terdepan adalah masalah proyek tertua yang belum terpecahkan - kurang dukungan protokol biner memcached.

NB: Protokol ASCII dalam memcached lebih lambat daripada protokol biner, dan cara standar hashing kunci yang konsisten hanya berfungsi dengan protokol biner. Namun hal ini tidak menimbulkan masalah untuk kasus tertentu.

Triknya ada di dalam tas: yang perlu Anda lakukan hanyalah beralih ke protokol ASCII dan semuanya akan berfungsi.... Namun, dalam hal ini, kebiasaan mencari jawaban masuk dokumentasi di php.net memainkan lelucon yang kejam. Anda tidak akan menemukan jawaban yang benar di sana... kecuali, tentu saja, Anda menggulir ke akhir, di bagian mana "Catatan kontribusi pengguna" akan setia dan jawaban yang diberi suara negatif secara tidak adil.

Ya, nama opsi yang benar adalah memcached.sess_binary_protocol. Itu harus dinonaktifkan, setelah itu sesi akan mulai bekerja. Yang tersisa hanyalah memasukkan container dengan mcrouter ke dalam pod dengan PHP!

Kesimpulan

Oleh karena itu, hanya dengan perubahan infrastruktur saja kami dapat menyelesaikan masalah ini: masalah toleransi kesalahan memcached telah teratasi, dan keandalan penyimpanan cache telah ditingkatkan. Selain keuntungan nyata bagi aplikasi, hal ini memberikan ruang untuk bermanuver saat bekerja di platform: ketika semua komponen memiliki cadangan, kehidupan administrator menjadi sangat disederhanakan. Ya, cara ini juga memiliki kekurangan, mungkin terlihat seperti β€œpenopang”, tetapi jika menghemat uang, mengubur masalah dan tidak menimbulkan masalah baru - mengapa tidak?

PS

Baca juga di blog kami:

Sumber: www.habr.com

Tambah komentar