RoadRunner: Η PHP δεν είναι φτιαγμένη για να πεθάνει ή το Golang για τη διάσωση

RoadRunner: Η PHP δεν είναι φτιαγμένη για να πεθάνει ή το Golang για τη διάσωση

Γεια σου Χαμπρ! Δραστηριοποιούμαστε στο Badoo εργάζονται για την απόδοση της PHP, αφού έχουμε ένα αρκετά μεγάλο σύστημα σε αυτή τη γλώσσα και το θέμα της απόδοσης είναι θέμα εξοικονόμησης χρημάτων. Πριν από περισσότερα από δέκα χρόνια, δημιουργήσαμε το PHP-FPM για αυτό, το οποίο στην αρχή ήταν ένα σύνολο patches για την PHP και αργότερα μπήκε στην επίσημη διανομή.

Τα τελευταία χρόνια, η PHP έχει σημειώσει μεγάλη πρόοδο: ο συλλέκτης σκουπιδιών έχει βελτιωθεί, το επίπεδο σταθερότητας έχει αυξηθεί - σήμερα μπορείτε να γράψετε δαίμονες και μακροχρόνια σενάρια στην PHP χωρίς κανένα πρόβλημα. Αυτό επέτρεψε στο Spiral Scout να προχωρήσει παραπέρα: το RoadRunner, σε αντίθεση με το PHP-FPM, δεν καθαρίζει τη μνήμη μεταξύ των αιτημάτων, γεγονός που δίνει επιπλέον κέρδος απόδοσης (αν και αυτή η προσέγγιση περιπλέκει τη διαδικασία ανάπτυξης). Αυτήν τη στιγμή πειραματιζόμαστε με αυτό το εργαλείο, αλλά δεν έχουμε ακόμη αποτελέσματα για κοινή χρήση. Για να γίνει πιο διασκεδαστική η αναμονή τους, δημοσιεύουμε τη μετάφραση της ανακοίνωσης του RoadRunner από το Spiral Scout.

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

Απολαύστε το!

Τα τελευταία δέκα χρόνια, έχουμε δημιουργήσει εφαρμογές για εταιρείες από τη λίστα Fortune 500, και για επιχειρήσεις με κοινό που δεν υπερβαίνει τους 500 χρήστες. Όλο αυτό το διάστημα, οι μηχανικοί μας ανέπτυξαν το backend κυρίως σε PHP. Αλλά πριν από δύο χρόνια, κάτι είχε μεγάλο αντίκτυπο όχι μόνο στην απόδοση των προϊόντων μας, αλλά και στην κλιμάκωσή τους - εισαγάγαμε το Golang (Go) στη στοίβα τεχνολογίας μας.

Σχεδόν αμέσως, ανακαλύψαμε ότι το Go μας επέτρεψε να δημιουργήσουμε μεγαλύτερες εφαρμογές με βελτιώσεις απόδοσης έως και 40 φορές. Με αυτό, μπορέσαμε να επεκτείνουμε υπάρχοντα προϊόντα γραμμένα σε PHP, βελτιώνοντάς τα συνδυάζοντας τα πλεονεκτήματα και των δύο γλωσσών.

Θα σας πούμε πώς ο συνδυασμός Go και PHP βοηθά στην επίλυση πραγματικών προβλημάτων ανάπτυξης και πώς έχει μετατραπεί σε ένα εργαλείο για εμάς που μπορεί να απαλλαγεί από ορισμένα από τα προβλήματα που σχετίζονται με PHP dying μοντέλο.

Το καθημερινό σας περιβάλλον ανάπτυξης PHP

Πριν μιλήσουμε για το πώς μπορείτε να χρησιμοποιήσετε το Go για να αναβιώσετε το μοντέλο PHP dying, ας ρίξουμε μια ματιά στο προεπιλεγμένο περιβάλλον ανάπτυξης PHP.

Στις περισσότερες περιπτώσεις, εκτελείτε την εφαρμογή σας χρησιμοποιώντας έναν συνδυασμό του διακομιστή web nginx και του διακομιστή PHP-FPM. Το πρώτο εξυπηρετεί στατικά αρχεία και ανακατευθύνει συγκεκριμένα αιτήματα στο PHP-FPM, ενώ το ίδιο το PHP-FPM εκτελεί κώδικα PHP. Μπορεί να χρησιμοποιείτε τον λιγότερο δημοφιλή συνδυασμό Apache και mod_php. Αλλά αν και λειτουργεί λίγο διαφορετικά, οι αρχές είναι οι ίδιες.

Ας ρίξουμε μια ματιά στον τρόπο με τον οποίο το PHP-FPM εκτελεί τον κώδικα εφαρμογής. Όταν εισέρχεται ένα αίτημα, το PHP-FPM προετοιμάζει μια θυγατρική διαδικασία PHP και μεταβιβάζει τις λεπτομέρειες του αιτήματος ως μέρος της κατάστασής του (_GET, _POST, _SERVER, κ.λπ.).

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

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

Μειονεκτήματα και αναποτελεσματικότητα ενός κανονικού περιβάλλοντος PHP

Εάν είστε επαγγελματίας προγραμματιστής PHP, τότε ξέρετε πού να ξεκινήσετε ένα νέο έργο - με την επιλογή ενός πλαισίου. Αποτελείται από βιβλιοθήκες ένεσης εξάρτησης, ORM, μεταφράσεις και πρότυπα. Και, φυσικά, όλες οι εισαγωγές χρήστη μπορούν εύκολα να τοποθετηθούν σε ένα αντικείμενο (Symfony/HttpFoundation ή PSR-7). Τα πλαίσια είναι ωραία!

Όλα όμως έχουν το τίμημα τους. Σε οποιοδήποτε πλαίσιο σε επίπεδο επιχείρησης, για να επεξεργαστείτε ένα απλό αίτημα χρήστη ή πρόσβαση σε μια βάση δεδομένων, θα πρέπει να φορτώσετε τουλάχιστον δεκάδες αρχεία, να δημιουργήσετε πολλές κλάσεις και να αναλύσετε πολλές διαμορφώσεις. Αλλά το χειρότερο είναι ότι μετά την ολοκλήρωση κάθε εργασίας, θα χρειαστεί να επαναφέρετε τα πάντα και να ξεκινήσετε από την αρχή: όλος ο κώδικας που μόλις ξεκινήσατε γίνεται άχρηστος, με τη βοήθειά του δεν θα επεξεργαστείτε πλέον άλλο αίτημα. Πες το σε όποιον προγραμματιστή γράφει σε κάποια άλλη γλώσσα και θα δεις σύγχυση στο πρόσωπό του.

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

Μπορεί η PHP με Go να επιβιώσει σε περισσότερα από ένα αιτήματα;

Είναι δυνατό να γραφτούν σενάρια PHP που διαρκούν περισσότερο από λίγα λεπτά (έως ώρες ή ημέρες): για παράδειγμα, εργασίες cron, αναλυτές CSV, διακόπτες ουρών. Όλοι λειτουργούν σύμφωνα με το ίδιο σενάριο: ανακτούν μια εργασία, την εκτελούν και περιμένουν την επόμενη. Ο κώδικας παραμένει στη μνήμη όλη την ώρα, εξοικονομώντας πολύτιμα χιλιοστά του δευτερολέπτου, καθώς απαιτούνται πολλά πρόσθετα βήματα για τη φόρτωση του πλαισίου και της εφαρμογής.

Αλλά η ανάπτυξη μακρόβιων σεναρίων δεν είναι εύκολη. Οποιοδήποτε σφάλμα σκοτώνει εντελώς τη διαδικασία, η διάγνωση διαρροών μνήμης είναι εξοργιστικό και ο εντοπισμός σφαλμάτων F5 δεν είναι πλέον δυνατός.

Η κατάσταση βελτιώθηκε με την κυκλοφορία της PHP 7: εμφανίστηκε ένας αξιόπιστος συλλέκτης απορριμμάτων, έγινε ευκολότερος ο χειρισμός σφαλμάτων και οι επεκτάσεις πυρήνα είναι πλέον στεγανές. Είναι αλήθεια ότι οι μηχανικοί πρέπει ακόμα να είναι προσεκτικοί με τη μνήμη και να γνωρίζουν τα ζητήματα κατάστασης στον κώδικα (υπάρχει κάποια γλώσσα που μπορεί να αγνοήσει αυτά τα πράγματα;). Ωστόσο, η PHP 7 μας επιφυλάσσει λιγότερες εκπλήξεις.

Είναι δυνατόν να ακολουθήσουμε το μοντέλο της εργασίας με μακρόβια σενάρια PHP, να το προσαρμόσουμε σε πιο ασήμαντες εργασίες όπως η επεξεργασία αιτημάτων HTTP και έτσι να απαλλαγούμε από την ανάγκη να φορτώνουμε τα πάντα από την αρχή με κάθε αίτημα;

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

Γνωρίζαμε ότι μπορούσαμε να γράψουμε έναν διακομιστή ιστού σε καθαρή PHP (PHP-PM) ή χρησιμοποιώντας μια επέκταση C (Swoole). Και παρόλο που κάθε μέθοδος έχει τα δικά της πλεονεκτήματα, και οι δύο επιλογές δεν μας ταιριάζουν - θέλαμε κάτι περισσότερο. Χρειαζόμασταν κάτι περισσότερο από έναν web server - περιμέναμε να λάβουμε μια λύση που θα μπορούσε να μας σώσει από τα προβλήματα που σχετίζονται με μια «σκληρή εκκίνηση» στην PHP, η οποία ταυτόχρονα θα μπορούσε εύκολα να προσαρμοστεί και να επεκταθεί για συγκεκριμένες εφαρμογές. Δηλαδή χρειαζόμασταν έναν διακομιστή εφαρμογών.

Μπορεί η Go να βοηθήσει με αυτό; Ξέραμε ότι μπορούσε επειδή η γλώσσα μεταγλωττίζει τις εφαρμογές σε μεμονωμένα δυαδικά αρχεία. είναι cross-platform? χρησιμοποιεί το δικό του, πολύ κομψό, μοντέλο παράλληλης επεξεργασίας (concurrency) και μια βιβλιοθήκη για εργασία με HTTP. και τέλος, χιλιάδες βιβλιοθήκες και ενσωματώσεις ανοιχτού κώδικα θα είναι διαθέσιμες σε εμάς.

Οι Δυσκολίες του Συνδυασμού δύο Γλωσσών Προγραμματισμού

Πρώτα απ 'όλα, ήταν απαραίτητο να καθοριστεί πώς δύο ή περισσότερες εφαρμογές θα επικοινωνούν μεταξύ τους.

Για παράδειγμα, χρησιμοποιώντας εξαιρετική βιβλιοθήκη Alex Palaestras, ήταν δυνατή η κοινή χρήση μνήμης μεταξύ των διεργασιών PHP και Go (παρόμοια με το mod_php στον Apache). Αλλά αυτή η βιβλιοθήκη έχει χαρακτηριστικά που περιορίζουν τη χρήση της για την επίλυση του προβλήματός μας.

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

Αρχικά, δημιουργήσαμε ένα απλό δυαδικό πρωτόκολλο για την ανταλλαγή δεδομένων μεταξύ διεργασιών και τον χειρισμό σφαλμάτων μετάδοσης. Στην απλούστερη μορφή του, αυτός ο τύπος πρωτοκόλλου είναι παρόμοιος με δικτυωτό κορδόνι с κεφαλίδα πακέτου σταθερού μεγέθους (στην περίπτωσή μας 17 bytes), το οποίο περιέχει πληροφορίες για τον τύπο του πακέτου, το μέγεθός του και μια δυαδική μάσκα για τον έλεγχο της ακεραιότητας των δεδομένων.

Στην πλευρά της PHP χρησιμοποιήσαμε λειτουργία πακέτου, και στην πλευρά Go, η βιβλιοθήκη κωδικοποίηση/δυαδικό.

Μας φάνηκε ότι ένα πρωτόκολλο δεν ήταν αρκετό - και προσθέσαμε τη δυνατότητα κλήσης υπηρεσίες net/rpc go απευθείας από την PHP. Αργότερα, αυτό μας βοήθησε πολύ στην ανάπτυξη, αφού μπορούσαμε να ενσωματώσουμε εύκολα τις βιβλιοθήκες Go σε εφαρμογές PHP. Το αποτέλεσμα αυτής της εργασίας μπορεί να φανεί, για παράδειγμα, στο άλλο προϊόν ανοιχτού κώδικα Γκόριτζ.

Κατανομή εργασιών σε πολλούς εργαζόμενους της PHP

Μετά την εφαρμογή του μηχανισμού αλληλεπίδρασης, αρχίσαμε να σκεφτόμαστε τον πιο αποτελεσματικό τρόπο μεταφοράς εργασιών σε διαδικασίες PHP. Όταν φτάσει μια εργασία, ο διακομιστής εφαρμογής πρέπει να επιλέξει έναν ελεύθερο εργαζόμενο για να την εκτελέσει. Εάν ένας εργαζόμενος/διαδικασία βγει με σφάλμα ή "πεθάνει", το ξεφορτωθούμε και δημιουργούμε ένα νέο για να το αντικαταστήσουμε. Και εάν ο εργαζόμενος/η διαδικασία έχει ολοκληρωθεί με επιτυχία, το επιστρέφουμε στη δεξαμενή των εργαζομένων που είναι διαθέσιμοι για την εκτέλεση εργασιών.

RoadRunner: Η PHP δεν είναι φτιαγμένη για να πεθάνει ή το Golang για τη διάσωση

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

Ως αποτέλεσμα, αποκτήσαμε έναν λειτουργικό διακομιστή PHP ικανό να επεξεργάζεται τυχόν αιτήματα που παρουσιάζονται σε δυαδική μορφή.

Προκειμένου η εφαρμογή μας να αρχίσει να λειτουργεί ως διακομιστής ιστού, έπρεπε να επιλέξουμε ένα αξιόπιστο πρότυπο PHP για να αντιπροσωπεύει τυχόν εισερχόμενα αιτήματα HTTP. Στην περίπτωσή μας, εμείς απλά μεταμορφώνω αίτημα net/http από Μετάβαση σε μορφή PSR-7ώστε να είναι συμβατό με τα περισσότερα από τα πλαίσια PHP που είναι διαθέσιμα σήμερα.

Επειδή το PSR-7 θεωρείται αμετάβλητο (μερικοί θα έλεγαν τεχνικά δεν είναι), οι προγραμματιστές πρέπει να γράψουν εφαρμογές που δεν αντιμετωπίζουν το αίτημα ως παγκόσμια οντότητα κατ' αρχήν. Αυτό ταιριάζει πολύ με την έννοια των μακρόβιων διαδικασιών PHP. Η τελική μας υλοποίηση, η οποία δεν έχει ακόμη ονομαστεί, έμοιαζε ως εξής:

RoadRunner: Η PHP δεν είναι φτιαγμένη για να πεθάνει ή το Golang για τη διάσωση

Παρουσιάζοντας το RoadRunner - διακομιστής εφαρμογών PHP υψηλής απόδοσης

Η πρώτη μας δοκιμαστική εργασία ήταν ένα backend API, το οποίο εκρήγνυται περιοδικά απρόβλεπτα (πολύ πιο συχνά από το συνηθισμένο). Αν και το nginx ήταν αρκετό στις περισσότερες περιπτώσεις, συναντούσαμε τακτικά 502 σφάλματα επειδή δεν μπορούσαμε να εξισορροπήσουμε το σύστημα αρκετά γρήγορα για την αναμενόμενη αύξηση του φορτίου.

Για να αντικαταστήσουμε αυτήν τη λύση, αναπτύξαμε τον πρώτο διακομιστή εφαρμογών PHP/Go στις αρχές του 2018. Και πήρε αμέσως απίστευτο αποτέλεσμα! Όχι μόνο απαλλαγήκαμε εντελώς από το σφάλμα 502, αλλά μπορέσαμε να μειώσουμε τον αριθμό των διακομιστών κατά τα δύο τρίτα, εξοικονομώντας πολλά χρήματα και χάπια πονοκεφάλου για μηχανικούς και διαχειριστές προϊόντων.

Μέχρι τα μέσα του έτους, είχαμε βελτιώσει τη λύση μας, τη δημοσιεύσαμε στο GitHub με άδεια MIT και την ονομάσαμε RoadRunner, τονίζοντας έτσι την απίστευτη ταχύτητα και αποτελεσματικότητά του.

Πώς το RoadRunner μπορεί να βελτιώσει τη στοίβα ανάπτυξης σας

Εφαρμογή RoadRunner μας επέτρεψε να χρησιμοποιήσουμε το Middleware net/http στην πλευρά Go για να εκτελέσουμε επαλήθευση JWT πριν φτάσει το αίτημα στην PHP, καθώς και να χειριστούμε WebSockets και παγκόσμια συγκεντρωτική κατάσταση στον Prometheus.

Χάρη στο ενσωματωμένο RPC, μπορείτε να ανοίξετε το API οποιασδήποτε βιβλιοθήκης Go για PHP χωρίς να γράφετε περιτυλίγματα επεκτάσεων. Το πιο σημαντικό, με το RoadRunner μπορείτε να αναπτύξετε νέους διακομιστές που δεν είναι HTTP. Παραδείγματα περιλαμβάνουν χειριστές εκτέλεσης στην PHP AWS Lambda, δημιουργώντας αξιόπιστους διακόπτες ουράς, ακόμη και προσθέτοντας gRPC στις εφαρμογές μας.

Με τη βοήθεια των κοινοτήτων PHP και Go, βελτιώσαμε τη σταθερότητα της λύσης, αυξήσαμε την απόδοση της εφαρμογής έως και 40 φορές σε ορισμένες δοκιμές, βελτιώσαμε τα εργαλεία εντοπισμού σφαλμάτων, υλοποιήσαμε την ενοποίηση με το πλαίσιο Symfony και προσθέσαμε υποστήριξη για HTTPS, HTTP/2, plugins και PSR-17.

Συμπέρασμα

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

Σε όλα αυτά θέλω να απαντήσω: ξανασκεφτείτε το. Πιστεύουμε ότι μόνο εσείς ορίζετε περιορισμούς για την PHP. Μπορείτε να περάσετε ολόκληρη τη ζωή σας μεταβαίνοντας από τη μια γλώσσα στην άλλη, προσπαθώντας να βρείτε το τέλειο ταίρι για τις ανάγκες σας ή μπορείτε να αρχίσετε να σκέφτεστε τις γλώσσες ως εργαλεία. Τα υποτιθέμενα ελαττώματα μιας γλώσσας όπως η PHP μπορεί στην πραγματικότητα να είναι ο λόγος της επιτυχίας της. Και αν το συνδυάσετε με μια άλλη γλώσσα όπως το Go, τότε θα δημιουργήσετε πολύ πιο ισχυρά προϊόντα από ό,τι αν περιοριζόσασταν στη χρήση οποιασδήποτε γλώσσας.

Έχοντας δουλέψει με ένα σωρό Go και PHP, μπορούμε να πούμε ότι τα αγαπάμε. Δεν σκοπεύουμε να θυσιάσουμε το ένα για το άλλο - αντίθετα, θα αναζητήσουμε τρόπους να πάρουμε ακόμη μεγαλύτερη αξία από αυτό το dual stack.

UPD: καλωσορίζουμε τον δημιουργό του RoadRunner και τον συν-συγγραφέα του αρχικού άρθρου - Λάχεσις

Πηγή: www.habr.com

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