Χρήση του mcrouter για οριζόντια κλίμακα του memcached

Χρήση του mcrouter για οριζόντια κλίμακα του memcached

Η ανάπτυξη έργων υψηλού φορτίου σε οποιαδήποτε γλώσσα απαιτεί ειδική προσέγγιση και χρήση ειδικών εργαλείων, αλλά όταν πρόκειται για εφαρμογές στην PHP, η κατάσταση μπορεί να επιδεινωθεί τόσο πολύ που πρέπει να αναπτύξετε, για παράδειγμα, δικός διακομιστής εφαρμογών. Σε αυτό το σημείωμα θα μιλήσουμε για τον γνωστό πόνο με την κατανεμημένη αποθήκευση περιόδων σύνδεσης και την προσωρινή αποθήκευση δεδομένων σε memcached και πώς λύσαμε αυτά τα προβλήματα σε ένα έργο «θάλαμος».

Ο ήρωας της περίστασης είναι μια εφαρμογή PHP που βασίζεται στο πλαίσιο symfony 2.3, η οποία δεν περιλαμβάνεται καθόλου στα επιχειρηματικά σχέδια προς ενημέρωση. Εκτός από τον αρκετά τυπικό χώρο αποθήκευσης περιόδου λειτουργίας, αυτό το έργο έκανε πλήρη χρήση πολιτική "αποθήκευση όλων". στο memcached: απαντήσεις σε αιτήματα προς τη βάση δεδομένων και τους διακομιστές API, διάφορες σημαίες, κλειδαριές για συγχρονισμό εκτέλεσης κώδικα και πολλά άλλα. Σε μια τέτοια κατάσταση, μια ανάλυση του memcached καθίσταται μοιραία για τη λειτουργία της εφαρμογής. Επιπλέον, η απώλεια προσωρινής μνήμης οδηγεί σε σοβαρές συνέπειες: το DBMS αρχίζει να σκάει στις ραφές, οι υπηρεσίες API αρχίζουν να απαγορεύουν αιτήματα κ.λπ. Η σταθεροποίηση της κατάστασης μπορεί να διαρκέσει δεκάδες λεπτά και κατά τη διάρκεια αυτής της περιόδου η υπηρεσία θα είναι τρομερά αργή ή εντελώς μη διαθέσιμη.

Έπρεπε να παρέχουμε τη δυνατότητα οριζόντιας κλίμακας της εφαρμογής με λίγη προσπάθεια, δηλ. με ελάχιστες αλλαγές στον πηγαίο κώδικα και πλήρη λειτουργικότητα διατηρημένη. Κάντε την προσωρινή μνήμη όχι μόνο ανθεκτική σε αστοχίες, αλλά και προσπαθήστε να ελαχιστοποιήσετε την απώλεια δεδομένων από αυτήν.

Τι συμβαίνει με το ίδιο το memcached;

Σε γενικές γραμμές, η επέκταση memcached για PHP υποστηρίζει κατανεμημένα δεδομένα και αποθήκευση συνεδρίας εκτός του κουτιού. Ο μηχανισμός για συνεπή κατακερματισμό κλειδιών σάς επιτρέπει να τοποθετείτε ομοιόμορφα δεδομένα σε πολλούς διακομιστές, απευθύνοντας μοναδικά κάθε συγκεκριμένο κλειδί σε έναν συγκεκριμένο διακομιστή από την ομάδα και τα ενσωματωμένα εργαλεία ανακατεύθυνσης διασφαλίζουν υψηλή διαθεσιμότητα της υπηρεσίας προσωρινής αποθήκευσης (αλλά, δυστυχώς, χωρίς δεδομένα).

Τα πράγματα είναι λίγο καλύτερα με την αποθήκευση περιόδου λειτουργίας: μπορείτε να ρυθμίσετε τις παραμέτρους memcached.sess_number_of_replicas, ως αποτέλεσμα του οποίου τα δεδομένα θα αποθηκευτούν σε πολλούς διακομιστές ταυτόχρονα και σε περίπτωση αποτυχίας μιας παρουσίας memcached, τα δεδομένα θα μεταφερθούν από άλλους. Ωστόσο, εάν ο διακομιστής επανέλθει στο διαδίκτυο χωρίς δεδομένα (όπως συμβαίνει συνήθως μετά από επανεκκίνηση), ορισμένα από τα κλειδιά θα ανακατανεμηθούν υπέρ του. Στην πραγματικότητα αυτό θα σημαίνει απώλεια δεδομένων συνεδρίας, αφού δεν υπάρχει τρόπος να «πάμε» σε άλλο αντίγραφο σε περίπτωση αστοχίας.

Τα τυπικά εργαλεία βιβλιοθήκης στοχεύουν κυρίως οριζόντιος κλιμάκωση: σας επιτρέπουν να αυξήσετε τη μνήμη cache σε γιγαντιαία μεγέθη και να παρέχετε πρόσβαση σε αυτήν από κώδικα που φιλοξενείται σε διαφορετικούς διακομιστές. Ωστόσο, στην περίπτωσή μας, ο όγκος των αποθηκευμένων δεδομένων δεν υπερβαίνει τα πολλά gigabyte και η απόδοση ενός ή δύο κόμβων είναι αρκετά. Αντίστοιχα, τα μόνα χρήσιμα τυπικά εργαλεία θα μπορούσαν να είναι η διασφάλιση της διαθεσιμότητας του memcached διατηρώντας ταυτόχρονα τουλάχιστον μία παρουσία προσωρινής μνήμης σε κατάσταση λειτουργίας. Ωστόσο, δεν ήταν δυνατό να εκμεταλλευτεί κανείς ούτε αυτή την ευκαιρία... Εδώ αξίζει να θυμηθούμε την αρχαιότητα του πλαισίου που χρησιμοποιήθηκε στο έργο, γι' αυτό και ήταν αδύνατο να λειτουργήσει η εφαρμογή με μια ομάδα διακομιστών. Ας μην ξεχνάμε επίσης την απώλεια δεδομένων συνεδρίας: το βλέμμα του πελάτη τραβήχτηκε από τη μαζική αποσύνδεση των χρηστών.

Στην ιδανική περίπτωση απαιτούνταν αναπαραγωγή εγγραφών σε memcached και παράκαμψη αντιγράφων σε περίπτωση λάθους ή λάθους. Μας βοήθησε να εφαρμόσουμε αυτή τη στρατηγική mcrouter.

mcrouter

Αυτός είναι ένας δρομολογητής memcached που αναπτύχθηκε από το Facebook για να λύσει τα προβλήματά του. Υποστηρίζει το πρωτόκολλο κειμένου memcached, το οποίο επιτρέπει κλίμακα εγκαταστάσεων memcached σε τρελές διαστάσεις. Μια λεπτομερής περιγραφή του mcrouter μπορείτε να βρείτε στο αυτή η ανακοίνωση. Μεταξύ άλλων ευρεία λειτουργικότητα μπορεί να κάνει αυτό που χρειαζόμαστε:

  • αντίγραφο εγγραφής?
  • κάντε εναλλακτική σε άλλους διακομιστές της ομάδας εάν παρουσιαστεί σφάλμα.

Ας πιάσουμε δουλειά!

διαμόρφωση mcrouter

Θα πάω κατευθείαν στις ρυθμίσεις:

{
 "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που επιλέγει τυχαία μια ομάδα ή μια διαδρομή μεταξύ των αντικειμένων του πίνακα children. Κάθε στοιχείο αυτού του πίνακα είναι με τη σειρά του ένας χειριστής MissFailoverRoute, το οποίο θα περάσει από κάθε διακομιστή στο pool μέχρι να λάβει μια απάντηση με δεδομένα, τα οποία θα επιστραφούν στον πελάτη.
  • Αν χρησιμοποιούσαμε αποκλειστικά MissFailoverRoute με μια ομάδα τριών διακομιστών, τότε όλα τα αιτήματα θα έρχονταν πρώτα στην πρώτη παρουσία προσωρινής αποθήκευσης και τα υπόλοιπα θα λάμβαναν αιτήματα σε υπολειπόμενη βάση όταν δεν υπάρχουν δεδομένα. Μια τέτοια προσέγγιση θα οδηγούσε σε υπερβολικό φορτίο στον πρώτο διακομιστή της λίστας, έτσι αποφασίστηκε να δημιουργηθούν τρεις ομάδες με διευθύνσεις σε διαφορετικές ακολουθίες και να επιλεγούν τυχαία.
  • Όλα τα άλλα αιτήματα (και αυτό είναι ένα αρχείο) υποβάλλονται σε επεξεργασία χρησιμοποιώντας AllMajorityRoute. Αυτός ο χειριστής στέλνει αιτήματα σε όλους τους διακομιστές της ομάδας και περιμένει απαντήσεις από τουλάχιστον N/2 + 1 από αυτούς. Από χρήση AllSyncRoute για τις λειτουργίες εγγραφής έπρεπε να εγκαταλειφθούν, καθώς αυτή η μέθοδος απαιτεί θετική απάντηση από όλα διακομιστές στην ομάδα - διαφορετικά θα επιστρέψει SERVER_ERROR. Αν και το mcrouter θα προσθέσει τα δεδομένα στις διαθέσιμες κρυφές μνήμες, η λειτουργία κλήσης PHP θα επιστρέψει ένα σφάλμα και θα δημιουργήσει ειδοποίηση. AllMajorityRoute δεν είναι τόσο αυστηρό και επιτρέπει έως και τις μισές μονάδες να τεθούν εκτός λειτουργίας χωρίς τα προβλήματα που περιγράφονται παραπάνω.

Κύριο μειονέκτημα Αυτό το σχήμα είναι ότι εάν πραγματικά δεν υπάρχουν δεδομένα στην κρυφή μνήμη, τότε για κάθε αίτημα από τον πελάτη θα εκτελούνται πραγματικά N αιτήματα προς memcached - σε όλα διακομιστές στην πισίνα. Μπορούμε να μειώσουμε τον αριθμό των διακομιστών σε ομάδες, για παράδειγμα, σε δύο: θυσιάζοντας την αξιοπιστία αποθήκευσης, έχουμεоυψηλότερη ταχύτητα και λιγότερο φορτίο από αιτήματα σε κλειδιά που λείπουν.

NB: Μπορείτε επίσης να βρείτε χρήσιμους συνδέσμους για την εκμάθηση του mcrouter τεκμηρίωση στο wiki и ζητήματα έργου (συμπεριλαμβανομένων των κλειστών), που αντιπροσωπεύουν μια ολόκληρη αποθήκη διαφόρων διαμορφώσεων.

Κατασκευή και λειτουργία mcrouter

Η εφαρμογή μας (και η ίδια η memcached) εκτελείται στο Kubernetes - κατά συνέπεια, το mcrouter βρίσκεται επίσης εκεί. Για συγκρότημα δοχείου χρησιμοποιούμε werf, η διαμόρφωση του οποίου θα μοιάζει με αυτό:

NB: Οι καταχωρίσεις που δίνονται στο άρθρο δημοσιεύονται στο αποθετήριο flat/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 σε ένα pod με PHP!

Συμπέρασμα

Έτσι, με απλώς αλλαγές υποδομής μπορέσαμε να λύσουμε το πρόβλημα: το πρόβλημα με την ανοχή σφαλμάτων memcached έχει επιλυθεί και η αξιοπιστία της αποθήκευσης κρυφής μνήμης έχει αυξηθεί. Εκτός από τα προφανή πλεονεκτήματα για την εφαρμογή, αυτό έδωσε περιθώρια ελιγμών κατά την εργασία στην πλατφόρμα: όταν όλα τα στοιχεία έχουν απόθεμα, η ζωή του διαχειριστή απλοποιείται σημαντικά. Ναι, αυτή η μέθοδος έχει επίσης τα μειονεκτήματά της, μπορεί να μοιάζει με «δεκανίκι», αλλά αν εξοικονομεί χρήματα, θάβει το πρόβλημα και δεν προκαλεί νέα - γιατί όχι;

PS

Διαβάστε επίσης στο blog μας:

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο