Ini adalah salah satu relief proyek paling keren. Dalam gambar - grafik total waktu yang dihabiskan oleh CPU untuk memproses semua permintaan pengguna. Pada akhirnya Anda dapat melihat transisi ke PHP 7.0. sejak versi 5.6. Ini adalah tahun 2016, berganti sore dari 24 November.Dari sudut pandang perhitungan, Tutu.ru terutama merupakan kesempatan untuk membeli tiket dari titik A ke titik B. Untuk melakukan ini, kami menggiling banyak jadwal, menyimpan respons dari banyak sistem maskapai dan secara berkala membuat kueri penggabungan yang sangat panjang ke dalam basis data. Secara umum, kami ditulis dalam PHP dan sampai saat ini kami benar-benar di atasnya (jika bahasa disiapkan dengan benar, Anda bahkan dapat membangun sistem real-time di atasnya). Baru-baru ini, area yang sangat kritis terhadap kinerja telah menjadi refactored di Go.
Kami selalu memiliki hutang teknis . Dan ini terjadi lebih cepat dari yang kita inginkan. Berita baiknya: Anda tidak perlu membahas semuanya. Buruk: dengan meningkatnya fungsi yang didukung, hutang teknis juga tumbuh secara proporsional.
Secara umum, hutang teknis adalah biaya untuk membuat kesalahan dalam mengambil keputusan. Anda tidak memprediksi sesuatu seperti arsitek, yaitu, Anda membuat kesalahan perkiraan atau membuat keputusan dalam kondisi informasi yang tidak mencukupi. Pada titik tertentu, Anda memahami bahwa Anda perlu mengubah sesuatu dalam kode (seringkali pada tingkat arsitektur). Maka Anda dapat segera berubah, tetapi Anda bisa menunggu. Jika Anda menunggu - bunga bergegas ke utang teknologi. Oleh karena itu, praktik yang baik untuk merestrukturisasi dari waktu ke waktu. Ya, atau nyatakan diri Anda bangkrut dan tulis lagi seluruh blok.
Bagaimana semuanya dimulai: fungsi monolit dan umum
Proyek Tutu.ru dimulai pada tahun 2003 sebagai situs web Runet reguler pada masa itu. Artinya, itu adalah banyak file, bukan database, halaman PHP di bagian depan HTML + JS. Ada beberapa retasan hebat dari kolega saya Yuri, tetapi dia lebih baik memberitahunya suatu hari nanti. Saya bergabung dengan proyek ini pada tahun 2006, pertama sebagai konsultan eksternal yang dapat membantu dengan saran dan kode, dan kemudian, pada tahun 2009, saya dipindahkan ke negara bagian sebagai direktur teknis. Pertama-tama, perlu untuk mengatur hal-hal dalam arah tiket pesawat: itu adalah bagian yang paling dimuat dan paling kompleks dalam arsitektur.
Pada tahun 2006, saya mengingatkan Anda, ada jadwal kereta api dan ada kesempatan untuk membeli tiket kereta api. Kami memutuskan untuk membuat bagian tiket pesawat sebagai proyek terpisah, yaitu, semua ini dipersatukan hanya di depan. Ketiga proyek (jadwal kereta api, kereta api dan udara) akhirnya ditulis dengan cara mereka sendiri. Pada saat itu, kode itu tampak normal bagi kami, tetapi agak belum selesai. Non-perfeksionis. Kemudian ia menjadi tua, menutupi dirinya dengan kruk dan arah kereta api berubah menjadi labu pada tahun 2010.
Di kereta api, kami tidak punya waktu untuk memberikan utang teknis. Refactoring tidak realistis: masalahnya ada di arsitektur. Kami memutuskan untuk menghancurkan dan mengulang semuanya lagi, tetapi itu juga sulit pada proyek live. Akibatnya, hanya URL lama yang tersisa di depan, dan kemudian blok demi blok ditulis ulang. Sebagai dasar, kami mengambil pendekatan yang digunakan setahun sebelumnya dalam pengembangan sektor penerbangan.
Ditulis ulang dalam PHP. Maka jelaslah bahwa ini bukan satu-satunya cara, tetapi tidak ada alternatif yang masuk akal bagi kami. Mereka memilihnya karena mereka sudah memiliki pengalaman dan prestasi, jelas bahwa ini adalah bahasa yang baik di tangan pengembang senior. Dari alternatif tersebut, C dan C ++ benar-benar produktif, tetapi setiap pembangunan kembali atau memperkenalkan perubahan pada mereka kemudian tampak seperti mimpi buruk. Oke, tidak diingatkan. Itu mimpi buruk.
MS dan semua. NET dari sudut pandang proyek beban tinggi bahkan tidak dipertimbangkan. Lalu tidak ada pilihan selain berbasis Linux sama sekali. Java adalah pilihan yang baik, tetapi menuntut sumber daya dari memori, tidak pernah memaafkan kesalahan junior dan kemudian tidak memungkinkan untuk merilis rilis dengan cepat - baik, atau kita tidak tahu itu. Bahkan sekarang kita tidak menganggap Python sebagai backend, hanya untuk tugas manipulasi data. JS - murni di bawah depan. Tidak ada pengembang Ruby on Rails dulu (dan sekarang). Go sudah pergi. Masih ada Perl, tetapi para ahli menilai itu tidak menjanjikan untuk pengembangan web, jadi mereka juga mengabaikannya. PHP ditinggalkan.
Kisah holiv berikutnya adalah PostgreSQL vs MySQL. Di tempat yang lebih baik, di tempat lain. Secara umum, maka itu praktik yang baik untuk memilih apa yang ternyata lebih baik, jadi kami memilih MySQL dan fork-nya.
Pendekatan pengembangan bersifat monolitik, maka tidak ada pendekatan lain, tetapi dengan struktur ortogonal perpustakaan. Ini adalah awal dari pendekatan API-sentris modern, ketika setiap perpustakaan memiliki fasad di luar, di mana Anda dapat menarik langsung ke dalam kode dari bagian lain dari proyek. Perpustakaan ditulis dalam "lapisan" ketika setiap level memiliki format spesifik pada input dan melewati format tertentu lebih lanjut ke kode, dan tes unit berputar di antara mereka. Artinya, sesuatu seperti test-driven-development, tetapi pixelated dan menakutkan.
Semua ini di-host di beberapa server, yang memungkinkan untuk skala di bawah beban. Tetapi pada saat yang sama, basis kode dari berbagai proyek berpotongan cukup kuat pada tingkat sistem. Ini sebenarnya berarti bahwa perubahan dalam proyek kereta api dapat mempengaruhi pesawat kita sendiri. Dan sering disentuh. Misalnya, di kereta api perlu memperluas pekerjaan dengan pembayaran - ini adalah revisi dari perpustakaan bersama. Dan pesawat bekerja dengannya, oleh karena itu, pengujian bersama diperlukan. Kami memeriksa ketergantungan dengan tes, dan ini lebih atau kurang normal. Bahkan pada tahun 2009, metode ini cukup maju. Tapi tetap saja, beban bisa menambahkan yang lain dari satu sumber. Ada persimpangan di basis data, yang menyebabkan efek tidak menyenangkan dalam bentuk rem di seluruh situs dengan masalah lokal dalam satu produk. Kereta api membunuh pesawat itu beberapa kali dalam disk karena pertanyaan yang banyak ke database.
Kami diskalakan dengan menambahkan instance dan menyeimbangkan di antara mereka. Monolith apa adanya.
Usia ban
Lalu kami pergi melalui jalan yang agak marjinal. Di satu sisi, kami mulai mengalokasikan layanan (hari ini pendekatan ini disebut microservice, tetapi kami tidak tahu kata "mikro"), tetapi untuk interaksi kami mulai menggunakan bus untuk transfer data, bukan REST atau gRPC, seperti yang mereka lakukan sekarang. Kami memilih AMQP sebagai protokol, dan RabbitMQ sebagai broker pesan. Pada saat itu, kami telah cukup terkenal menguasai peluncuran daemon untuk PHP (ya, ada implementasi fork () yang berfungsi penuh dan segala sesuatu yang lain untuk bekerja dengan proses), karena untuk waktu yang lama di monolith kami menggunakan hal seperti Gearman untuk menyejajarkan permintaan dengan sistem reservasi. .
Mereka membuat broker di atas kelinci, dan ternyata semua ini tidak benar-benar hidup di bawah beban. Beberapa jenis kehilangan jaringan, transmisi ulang, keterlambatan. Misalnya, sekelompok beberapa broker "out of the box" berperilaku agak berbeda dari yang dinyatakan oleh pengembang (itu tidak pernah terjadi sebelumnya, dan lagi). Secara umum, mereka belajar banyak. Namun pada akhirnya, kami mendapatkan SLA yang diperlukan untuk layanan tersebut. Sebagai contoh, layanan RPS paling banyak memiliki kecepatan 400 rps, bolak-balik persentil ke-99 dari klien ke klien termasuk bus dan pemrosesan layanan dengan urutan 35 ms. Sekarang total dalam bus kami amati sekitar 18 krps.
Lalu datanglah arahan bus. Kami segera menulisnya tanpa monolit pada arsitektur layanan. Karena semuanya ditulis dari awal, ternyata sangat baik, cepat dan mudah, meskipun itu perlu untuk terus-menerus memperbaiki alat untuk pendekatan baru. Ya, semua ini berputar pada mesin virtual, di dalamnya daemon PHP berkomunikasi melalui bus. Setan mulai di dalam wadah Docker, tetapi tidak ada solusi untuk orkestrasi seperti Openshift atau Kubernetes. Pada 2014, kami baru saja mulai membicarakannya, tetapi kami tidak mempertimbangkan pendekatan penjualan seperti itu.
Jika Anda membandingkan berapa banyak tiket bus yang dijual dibandingkan dengan tiket pesawat atau kereta api, Anda mendapat setetes dalam ember. Dan di kereta dan pesawat, pindah ke arsitektur baru itu sulit, karena ada fungsi yang berfungsi, beban nyata, dan selalu ada pilihan antara melakukan sesuatu yang baru atau menghabiskan uang untuk melunasi hutang teknis.
Pindah ke layanan adalah hal yang baik, tetapi lama, tetapi Anda harus berurusan dengan beban dan keandalan sekarang. Oleh karena itu, secara paralel, mereka mulai mengambil langkah-langkah yang ditargetkan untuk meningkatkan kehidupan monolit. Backend dibagi menjadi jenis produk, yaitu, mereka mulai lebih fleksibel mengontrol perutean permintaan tergantung pada jenisnya: udara secara terpisah dari kereta api, dll. Dimungkinkan untuk memprediksi beban, skala secara mandiri. Ketika mereka tahu bahwa di jalur kereta api, misalnya, puncak penjualan Tahun Baru, maka beberapa mesin virtual ditambahkan. Kemudian dimulai tepat 45 hari sebelum hari kerja terakhir tahun ini, dan pada 14-15 November kami memiliki beban ganda. Sekarang FPK dan operator lain telah membuat banyak tiket dengan awal penjualan selama 60, 90 dan bahkan 120 hari, dan puncak ini telah menyebar. Tetapi pada hari kerja terakhir bulan April akan selalu ada beban di kereta listrik sebelum Mei, dan masih ada puncak. Tetapi tentang musiman tiket dan cara migrasi demobilisasi, kolega saya dari kereta api akan memberi tahu lebih baik, dan saya akan melanjutkan tentang arsitekturnya.
Di suatu tempat di 2014, mereka mulai derban database besar banyak yang kecil. Ini penting karena itu tumbuh berbahaya, dan kejatuhannya sangat penting. Kami mulai mengalokasikan basis data kecil yang terpisah (untuk 5-10 tabel) untuk fungsi tertentu, sehingga layanan lain akan memengaruhi lebih sedikit gangguan, dan agar semua ini dapat diskalakan lebih mudah. Perlu dicatat bahwa untuk load balancing dan scaling kami menggunakan replika untuk membaca. Memulihkan replika untuk basis besar setelah kegagalan replikasi bisa memakan waktu berjam-jam, dan selama ini saya harus "terbang dengan bebas bersyarat dan dengan satu sayap." Kenangan periode seperti itu masih menyebabkan rasa dingin yang tidak menyenangkan di suatu tempat di antara telinga. Sekarang kami memiliki sekitar 200 contoh pangkalan yang berbeda, dan mengelola begitu banyak instalasi dengan tangan kami adalah bisnis yang melelahkan dan tidak dapat diandalkan. Oleh karena itu, kami menggunakan Github Orchestrator, yang mengotomatiskan pekerjaan dengan replika dan proxySql untuk mendistribusikan beban dan melindungi terhadap kegagalan database tertentu.
Seperti sekarang
Secara umum, kami secara bertahap mulai mengalokasikan tugas asinkron dan memisahkan peluncurannya di event handler sehingga yang satu tidak mengganggu yang lain.
Ketika PHP 7 keluar, kami melihat dalam tes kemajuan yang sangat besar dalam kinerja dan mengurangi konsumsi sumber daya. Pindah ke sana terjadi dengan sedikit wasir, seluruh proyek dari awal tes ke terjemahan lengkap dari seluruh produksi memakan waktu sedikit lebih dari enam bulan, tetapi setelah itu konsumsi sumber daya turun hampir setengahnya. CPU load timeline - di bagian atas pos.
Monolith telah bertahan hingga hari ini dan, menurut perkiraan saya, adalah sekitar 40% dari basis kode. Perlu dikatakan bahwa tugas mengganti seluruh monolith dengan layanan tidak ditetapkan secara eksplisit. Kami bergerak secara pragmatis: semua yang baru dilakukan pada layanan microser, tetapi jika Anda perlu memperbaiki fungsionalitas lama dalam monolit, maka kami mencoba mentransfernya ke arsitektur layanan, jika saja penyempurnaan tidak benar-benar sangat kecil. Pada saat yang sama, monolith dicakup dalam pengujian sehingga kami dapat menggunakan dua kali seminggu dengan tingkat kualitas yang memadai. Fitur dicakup dalam berbagai cara, pengujian unit cukup lengkap, tes UI dan tes Penerimaan mencakup hampir seluruh fungsionalitas portal (kami memiliki sekitar 15.000 kasus uji), tes API kurang lebih lengkap. Kami hampir tidak melakukan pengujian beban. Lebih tepatnya, pementasan kami mirip dengan prod dalam struktur, tetapi tidak dalam kekuasaan, dan dibatasi dengan pemantauan yang sama. Kami menghasilkan beban, jika kami melihat bahwa menjalankan sebelumnya pada rilis lama berbeda dalam timing, kami melihat betapa pentingnya itu. Jika rilis baru dan lama kira-kira sama, maka kami merilisnya di prod. Bagaimanapun, semua fitur mati di bawah saklar sehingga Anda dapat mematikannya kapan saja jika terjadi kesalahan.
Fitur berat selalu diuji untuk 1% pengguna. Lalu kami beralih ke 2%, 5%, 10% dan kami menjangkau semua pengguna. Artinya, kita selalu dapat melihat beban yang tidak lazim sebelum lonjakan yang membunuh server dan memutuskan sambungan terlebih dahulu.
Bila perlu, kami mengambil (dan akan mengambil) 4-5 bulan untuk proyek rekayasa ulang, ketika tim fokus pada tugas tertentu. Ini adalah cara yang baik untuk memotong simpul Gordian ketika refactoring lokal tidak lagi membantu. Jadi kami melakukannya beberapa tahun yang lalu dengan udara: kami mereduksi arsitekturnya, melakukannya - segera mendapatkan akselerasi instan dalam pengembangan, mampu meluncurkan banyak fitur baru. Dua bulan setelah rekayasa ulang, mereka tumbuh dengan urutan besar bagi pelanggan karena fitur. Mereka mulai mengelola harga dengan lebih akurat, menghubungkan mitra, semuanya menjadi lebih cepat. Sukacita Saya harus mengatakan, sekarang saatnya untuk melakukan hal yang sama lagi, tetapi ini adalah takdir: cara membangun aplikasi berubah, solusi baru, pendekatan, alat muncul. Untuk bertahan dalam bisnis, Anda harus tumbuh.
Tugas utama rekayasa ulang bagi kita adalah untuk mempercepat pengembangan lebih lanjut. Jika tidak ada yang baru diperlukan, maka rekayasa ulang tidak diperlukan. Tidak perlu menemukan yang baru: tidak masuk akal untuk berinvestasi dalam modernisasi. Jadi, sambil mempertahankan tumpukan dan arsitektur modern, orang-orang masuk ke pekerjaan lebih cepat, yang baru menghubungkan lebih cepat, sistem berperilaku lebih dapat diprediksi, pengembang lebih tertarik untuk mengerjakan proyek. Sekarang ada tugas untuk menyelesaikan monolith tanpa membuangnya sepenuhnya, sehingga setiap produk dapat mengunggah pembaruan, tidak tergantung pada yang lain. Artinya, dapatkan CI / CD tertentu dalam monolit.
Hari ini kami tidak hanya menggunakan kelinci, tetapi juga REST, dan gRPC untuk bertukar informasi antar layanan. Beberapa layanan Microsoft ditulis dalam Golang: kecepatan komputasi dan penanganan memori sangat baik di sana. Ada panggilan untuk mengimplementasikan dukungan nodeJS, tetapi pada akhirnya kami meninggalkan node hanya untuk rendering server, dan logika bisnis ditinggalkan di PHP dan Go. Pada prinsipnya, pendekatan yang dipilih memungkinkan kami untuk mengembangkan layanan dalam hampir semua bahasa, tetapi kami memutuskan untuk membatasi kebun binatang agar tidak menambah kompleksitas sistem.
Sekarang kita pergi ke layanan microser yang akan bekerja dalam wadah Docker di bawah orkestrasi OpenShift. Tugas dalam satu setengah tahun - 90% dari semua putaran di dalam platform. Mengapa Jadi lebih cepat untuk digunakan, lebih cepat untuk memeriksa versi, ada sedikit perbedaan dalam penjualan dari lingkungan devel. Pengembang dapat lebih memikirkan fitur yang ia implementasikan, dan bukan tentang bagaimana menyebarkan lingkungan, cara mengkonfigurasinya, di mana memulainya, yaitu, lebih banyak manfaat. Sekali lagi - masalah operasional: ada banyak layanan mikro, mereka perlu diotomatisasi oleh manajemen. Secara manual - biaya yang sangat tinggi, risiko kesalahan dengan kontrol manual, dan platform memberikan skala normal.
Setiap tahun kami mengalami peningkatan beban kerja - hingga 30-40%: semakin banyak orang menguasai trik-trik dengan Internet, berhenti pergi ke meja kas fisik, kami menambahkan produk dan fitur baru ke yang sudah ada. Sekarang sekitar 1 juta pengguna sehari datang ke portal. Tentu saja, tidak semua pengguna menghasilkan beban yang sama. Sesuatu tidak memerlukan sumber daya komputasi sama sekali, dan, misalnya, pencarian adalah komponen yang agak intensif sumber daya. Di sana, tanda centang tunggal โplus atau minus tiga hariโ dalam penerbangan meningkatkan beban sebanyak 49 kali (ketika melihat ke depan dan ke belakang, matriksnya adalah 7 kali 7). Segala sesuatu yang lain dibandingkan dengan mencari tiket di dalam sistem kereta api dan udara cukup sederhana. Hal yang paling mudah dalam sumber daya adalah
pencarian petualangan dan
wisata (tidak ada cache termudah dalam hal arsitektur, tetapi masih ada tur yang jauh lebih sedikit daripada kombinasi tiket), kemudian
jadwal kereta api (mudah di-cache dengan cara standar), dan hanya kemudian segala sesuatu yang lain .
Tentu saja, utang teknis masih menumpuk. Dari semua sisi. Hal utama adalah untuk memahami pada waktu di mana Anda dapat mengatur untuk refactor, dan semuanya akan baik-baik saja, di mana Anda tidak harus menyentuh apa pun (kadang-kadang terjadi: kita hidup dengan Legacy jika tidak ada perubahan yang direncanakan), tetapi di suatu tempat kita perlu terburu-buru dan merekayasa ulang diri kita sendiri, karena tanpa masa depan ini tidak akan. Tentu saja, kami membuat kesalahan, tetapi secara umum, Tutu.ru telah ada selama 16 tahun, dan saya suka dinamika proyek.