Keluar dari jaringan Tarantool. Sinkronisasi simpul saat memfilter lalu lintas

gambar

Variti berspesialisasi dalam perlindungan terhadap serangan bot dan DDoS, dan juga melakukan pengujian tegangan dan beban. Karena kami bekerja sebagai layanan internasional, sangat penting bagi kami untuk memastikan pertukaran informasi yang tidak terputus antara server dan cluster secara real time. Pada konferensi Saint HighLoad ++ 2019, pengembang Variti Anton Barabanov mengatakan bagaimana kami menggunakan UDP dan Tarantool, mengapa kami mengambil banyak, dan bagaimana kami harus menulis ulang modul Tarantool dari Lua ke C.

Anda juga dapat membaca abstrak laporan melalui tautan, dan melihat video di bawah ini di spoiler.

Laporkan video


Ketika kami mulai membuat layanan penyaringan lalu lintas, kami segera memutuskan untuk tidak berurusan dengan transit IP, tetapi untuk melindungi HTTP, API, dan layanan game. Dengan demikian, kami menghentikan lalu lintas di tingkat L7 dalam protokol TCP dan meneruskannya. Perlindungan pada L3 & 4 pada saat yang sama terjadi secara otomatis. Diagram di bawah ini menunjukkan diagram layanan: permintaan dari orang-orang melalui sebuah cluster, yaitu, server dan peralatan jaringan, dan bot (ditampilkan sebagai hantu) difilter.



Untuk memfilter, perlu untuk memecah lalu lintas menjadi permintaan yang terpisah, menganalisis sesi secara akurat dan cepat, dan karena kita tidak memblokir berdasarkan alamat IP, tentukan bot dan orang-orang di dalam koneksi dari alamat IP yang sama.

Apa yang terjadi di dalam cluster


Di dalam cluster, kami memiliki node filter independen, yaitu, setiap node bekerja sendiri dan hanya dengan sepotong lalu lintas sendiri. Lalu lintas didistribusikan secara acak di antara node: jika, misalnya, 10 koneksi diterima dari satu pengguna, maka mereka semua berbeda di server yang berbeda.

Kami memiliki persyaratan kinerja yang sangat ketat karena pelanggan kami berada di berbagai negara. Dan jika, misalnya, pengguna dari Swiss mengunjungi situs Prancis, maka ia sudah dihadapkan dengan 15 milidetik keterlambatan jaringan karena peningkatan rute lalu lintas. Oleh karena itu, kami tidak berhak menambahkan 15-20 milidetik lagi di dalam pusat pemrosesan kami - permintaan akan berlangsung lama sekali. Selain itu, jika kami memproses setiap permintaan HTTP selama 15-20 milidetik, maka serangan sederhana sebesar 20 ribu RPS akan menambah seluruh cluster. Ini, tentu saja, tidak dapat diterima.

Persyaratan lain bagi kami adalah tidak hanya melacak permintaan, tetapi juga memahami konteksnya. Misalkan pengguna membuka halaman web dan mengirimkan permintaan garis miring. Setelah itu, halaman dimuat, dan jika itu HTTP / 1.1, maka browser membuka 10 koneksi ke backend dan dalam 10 stream permintaan statika dan dinamika, buat permintaan dan subqueries ajax. Jika, alih-alih membuat proxy subquery, dalam proses mengembalikan halaman, Anda mulai berinteraksi dengan browser dan mencoba memberikannya, katakanlah, JS Challenge untuk subquery, maka kemungkinan besar Anda akan merusak halaman tersebut. Pada permintaan pertama, Anda dapat memberikan CAPTCHA (meskipun ini buruk) atau tantangan JS, melakukan pengalihan, dan kemudian browser apa pun akan memproses semuanya dengan benar. Setelah pengujian, perlu untuk menyebarluaskan informasi tentang semua cluster bahwa sesi tersebut sah. Jika tidak ada pertukaran informasi antar cluster, maka node lain akan menerima sesi dari tengah dan tidak akan tahu apakah akan melewatkannya atau tidak.

Penting juga untuk merespons dengan cepat semua lonjakan beban dan perubahan lalu lintas. Jika sesuatu melompat pada satu simpul, maka, setelah 50-100 milidetik, lompatan akan terjadi pada semua simpul lainnya. Oleh karena itu, lebih baik jika node tahu tentang perubahan sebelumnya dan mengatur parameter perlindungan terlebih dahulu sehingga tidak ada lompatan terjadi pada semua node lainnya.
Layanan tambahan untuk melindungi dari bot adalah layanan pasca-markup: kami menempatkan piksel di situs, menulis informasi bot / orang dan mengirim data ini melalui API. Putusan-putusan ini harus disimpan di suatu tempat. Artinya, jika sebelumnya kita berbicara tentang sinkronisasi dalam sebuah cluster, sekarang kita juga menambahkan sinkronisasi informasi antar cluster. Di bawah ini kami menunjukkan skema layanan di tingkat L7.



Antar cluster


Setelah kami membuat cluster, kami mulai meningkatkan skala. Kami bekerja melalui siaran BGP, yaitu, subnet kami diumumkan dari semua cluster dan lalu lintas sampai ke yang terdekat. Sederhananya, permintaan dikirim dari Perancis ke sebuah cluster di Frankfurt, dan dari St. Petersburg ke sebuah cluster di Moskow. Cluster harus independen. Aliran jaringan diizinkan independen.

Mengapa ini penting? Misalkan seseorang mengendarai mobil, bekerja dengan situs web dari internet seluler dan melintasi Rubicon tertentu, setelah itu lalu lintas tiba-tiba beralih ke kluster lain. Atau kasus lain: rute lalu lintas dibangun kembali karena di suatu tempat saklar atau router terbakar, sesuatu jatuh, segmen jaringan terputus. Dalam hal ini, kami memberikan informasi yang memadai kepada browser (misalnya, dalam cookie) sehingga ketika beralih ke cluster lain, dimungkinkan untuk menginformasikan parameter yang diperlukan tentang tes yang lulus atau gagal.
Selain itu, Anda harus menyinkronkan mode perlindungan antar kluster. Ini penting dalam kasus serangan volume rendah, yang paling sering dilakukan di bawah naungan banjir. Karena serangan berjalan secara paralel, orang berpikir bahwa situs mereka menghancurkan banjir dan tidak melihat serangan volume rendah. Untuk kasus ketika volume rendah datang ke satu cluster, dan banjir ke yang lain, sinkronisasi mode perlindungan diperlukan.

Dan seperti yang telah disebutkan, kami mensinkronkan antara kluster dengan putusan yang mengakumulasi dan diberikan oleh API. Dalam hal ini, mungkin ada banyak putusan dan harus disinkronkan dengan andal. Dalam mode perlindungan, Anda bisa kehilangan sesuatu di dalam cluster, tetapi tidak di antara cluster.

Perlu dicatat bahwa ada latensi besar antara cluster: dalam kasus Moskow dan Frankfurt, ini adalah 20 milidetik. Permintaan sinkron tidak dapat dibuat di sini, semua interaksi harus dilakukan dalam mode asinkron.

Di bawah ini kami menunjukkan interaksi antara cluster. M, l, p adalah beberapa parameter teknis untuk pertukaran. U1, u2 adalah markup pengguna sebagai tidak sah dan sah.



Interaksi internal antar node


Awalnya, ketika kami membuat layanan, pemfilteran di level L7 dimulai hanya pada satu simpul. Ini bekerja dengan baik untuk dua klien, tetapi tidak lebih. Saat melakukan penskalaan, kami ingin mencapai respons maksimum dan latensi minimum.

Penting untuk meminimalkan sumber daya CPU yang dihabiskan untuk memproses paket, sehingga interaksi melalui, misalnya, HTTP tidak akan sesuai. Itu juga perlu untuk memastikan konsumsi overhead minimum tidak hanya sumber daya komputasi, tetapi juga tingkat paket. Namun demikian, kita berbicara tentang penyaringan serangan, dan ini adalah situasi di mana jelas tidak ada kinerja yang cukup. Biasanya, ketika membangun proyek web, x3 atau x4 sudah cukup untuk memuat, tetapi kami selalu memiliki x1, karena serangan skala besar selalu bisa datang.

Persyaratan lain untuk antarmuka interaksi adalah keberadaan tempat di mana kita akan menulis informasi dan dari mana kita dapat menghitung keadaan kita sekarang. Bukan rahasia lagi bahwa C ++ sering digunakan untuk mengembangkan sistem penyaringan. Namun sayangnya, program yang ditulis dalam C ++ terkadang macet. Terkadang program seperti itu perlu di-restart untuk memperbarui, atau, misalnya, karena konfigurasi tidak dibaca kembali. Dan jika kita me-restart node yang sedang diserang, maka kita perlu mengambil suatu konteks di mana node ini ada. Artinya, layanan tidak boleh stateless, harus diingat bahwa ada sejumlah orang yang kami blokir, yang kami periksa. Harus ada komunikasi internal yang sama sehingga layanan dapat menerima serangkaian informasi utama. Kami memiliki pemikiran untuk meletakkan di dekat database tertentu, misalnya, SQLite, tetapi kami dengan cepat membuang solusi seperti itu, karena aneh untuk menulis Input-Output pada setiap server, ini akan bekerja dengan buruk dalam memori.

Faktanya, kami bekerja hanya dengan tiga operasi. Fungsi pertama adalah "kirim" ke semua node. Ini berlaku, misalnya, untuk pesan tentang sinkronisasi beban saat ini: setiap node harus mengetahui beban total pada sumber daya dalam gugus untuk melacak puncak. Operasi kedua adalah untuk "menyelamatkan", ini menyangkut putusan verifikasi. Dan operasi ketiga adalah kombinasi "kirim ke semua orang" dan "simpan". Di sini kita berbicara tentang perubahan status pesan yang kami kirim ke semua node dan kemudian simpan untuk dapat mengurangi. Di bawah ini adalah skema interaksi yang dihasilkan, di mana kita perlu menambahkan parameter untuk disimpan.



Opsi dan Hasil


Pilihan apa untuk melestarikan vonis yang telah kita lihat? Pertama, kami memikirkan klasik, RabbitMQ, RedisMQ dan layanan berbasis TCP kami sendiri. Kami menolak keputusan ini karena mereka bekerja lambat. TCP yang sama menambahkan x2 ke tingkat paket. Selain itu, jika kita mengirim pesan dari satu simpul ke simpul lainnya, maka kita perlu memiliki banyak simpul pengirim, atau simpul ini dapat meracuni 1/16 dari pesan-pesan yang dapat dikirim oleh 16 mesin. Jelas bahwa ini tidak dapat diterima.

Sebagai hasilnya, kami mengambil multicast UDP, karena dalam hal ini pusat pengiriman adalah peralatan jaringan, yang tidak terbatas dalam kinerja dan memungkinkan Anda untuk sepenuhnya menyelesaikan masalah dengan kecepatan pengiriman dan penerimaan. Jelas bahwa dalam kasus UDP, kami tidak berpikir tentang format teks, tetapi mengirim data biner.

Selain itu, kami segera menambahkan pengemasan dan basis data. Kami mengambil Tarantool, karena, pertama, ketiga pendiri perusahaan memiliki pengalaman bekerja dengan database ini, dan kedua, sefleksibel mungkin, yaitu, juga semacam layanan aplikasi. Selain itu, Tarantool memiliki CAPI, dan kemampuan untuk menulis dalam C adalah masalah prinsip bagi kami karena perlindungan maksimum diperlukan untuk melindungi terhadap DDoS. Tidak ada bahasa yang ditafsirkan dapat memberikan kinerja yang cukup, tidak seperti C.

Dalam diagram di bawah ini, kami menambahkan database di dalam cluster, di mana status untuk komunikasi internal disimpan.



Tambahkan basis data


Dalam database, kami menyimpan negara dalam bentuk log panggilan. Ketika kami menemukan cara menyimpan informasi, ada dua opsi. Dimungkinkan untuk menyimpan beberapa keadaan dengan pembaruan dan perubahan yang konstan, tetapi agak sulit untuk diterapkan. Karena itu, kami menggunakan pendekatan yang berbeda.

Faktanya adalah bahwa struktur data yang dikirim melalui UDP disatukan: ada waktu, semacam kode, tiga atau empat bidang data. Jadi kami mulai menulis struktur ini di ruang Tarantool dan menambahkan catatan TTL di sana, yang membuatnya jelas bahwa strukturnya sudah usang dan perlu dihapus. Dengan demikian, log pesan diakumulasikan di Tarantool, yang kami hapus dengan waktu yang ditentukan. Untuk menghapus data lama, kami awalnya mengambil kadaluwarsa. Selanjutnya, kami harus meninggalkannya, karena itu menimbulkan masalah tertentu, yang akan kami bahas di bawah ini. Sejauh ini, skema: di atasnya dua database ditambahkan ke struktur kami.



Seperti yang telah kami sebutkan, selain menyimpan status kluster, juga perlu untuk menyinkronkan putusan. Putusan kami selaraskan interluster. Oleh karena itu, perlu untuk menambahkan instalasi tambahan Tarantool. Akan aneh untuk menggunakan solusi lain, karena Tarantool sudah ada dan sangat ideal untuk layanan kami. Dalam instalasi baru, kami mulai menulis vonis dan mereplikasi mereka dengan cluster lain. Dalam hal ini, kami menggunakan bukan master / slave, tetapi master / master. Sekarang di Tarantool hanya ada master / master yang tidak sinkron, yang bagi banyak kasus tidak cocok, tetapi bagi kami model ini optimal. Dengan latensi minimal antar cluster, replikasi sinkron akan menghalangi, sementara replikasi asinkron tidak menyebabkan masalah.

Masalahnya


Tapi kami punya banyak masalah. Blok kompleksitas pertama terkait dengan UDP : bukan rahasia bahwa protokol dapat mengalahkan dan kehilangan paket. Kami memecahkan masalah ini dengan metode burung unta, yaitu, kami hanya menyembunyikan kepala kami di pasir. Namun demikian, kerusakan paket dan penataan kembali tempat mereka tidak mungkin terjadi pada kami, karena komunikasi terjadi dalam kerangka kerja sakelar tunggal, dan tidak ada koneksi yang tidak stabil dan peralatan jaringan yang tidak stabil.

Mungkin ada masalah packet loss jika mesin macet, sebuah Input-Output terjadi di suatu tempat, atau sebuah node kelebihan beban. Jika hang seperti itu terjadi untuk waktu yang singkat, katakanlah, 50 milidetik, maka ini mengerikan, tetapi diselesaikan dengan peningkatan antrian sysctl. Yaitu, kita ambil sysctl, konfigurasikan ukuran antrian dan dapatkan buffer di mana semuanya terletak sampai node mulai bekerja kembali. Jika pembekuan yang lebih lama terjadi, maka masalahnya bukan kehilangan konektivitas, tetapi bagian dari lalu lintas yang menuju ke node. Sejauh ini, kami tidak punya kasus seperti itu.

Masalah replikasi asinkron Tarantool jauh lebih kompleks. Awalnya, kami tidak mengambil master / master, tetapi model yang lebih tradisional untuk mengoperasikan master / slave. Dan semuanya bekerja persis sampai budak mengambil alih beban master untuk waktu yang lama. Akibatnya, kedaluwarsa bekerja dan menghapus data pada master, tetapi pada slave tidak. Karena itu, ketika kami beralih beberapa kali dari master ke slave dan back, begitu banyak data terakumulasi pada slave sehingga pada titik tertentu semuanya rusak. Jadi untuk toleransi kesalahan penuh, saya harus beralih ke replikasi master / master asynchronous.

Dan di sini lagi muncul kesulitan. Pertama, kunci dapat berpotongan antara replika yang berbeda. Misalkan, di dalam cluster, kami menulis data ke satu master, pada saat koneksi terputus, kami menulis semuanya ke master kedua, dan setelah kami melakukan replikasi asinkron, ternyata kunci primer yang sama dalam ruang dan replikasi hancur.

Kami memecahkan masalah ini hanya: kami mengambil model di mana kunci utama harus berisi nama simpul Tarantool yang kami tulis. Karena ini, konflik tidak lagi muncul, tetapi situasi menjadi mungkin ketika data pengguna digandakan. Ini adalah kasus yang sangat langka, jadi kami kembali mengabaikannya. Jika duplikasi sering terjadi, maka Tarantool memiliki banyak indeks berbeda, sehingga Anda selalu dapat melakukan deduplikasi.

Masalah lain menyangkut pelestarian putusan dan muncul ketika data yang direkam pada satu master belum muncul pada yang lain, dan permintaan sudah sampai pada master pertama. Sejujurnya, kami belum menyelesaikan masalah ini dan hanya menunda vonis. Jika ini tidak dapat diterima, maka kami akan mengatur semacam dorongan tentang kesiapan data. Itulah cara kami menangani replikasi master / master dan masalahnya.

Ada blok masalah yang terkait langsung dengan Tarantool , driver dan modul kedaluwarsanya. Beberapa waktu setelah peluncuran, serangan mulai datang kepada kami setiap hari, masing-masing, jumlah pesan yang kami simpan dalam basis data untuk sinkronisasi dan penyimpanan konteks telah menjadi sangat besar. Dan selama pengupasan, begitu banyak data mulai dihapus sehingga pengumpul sampah berhenti mengatasinya. Kami memecahkan masalah ini dengan menulis dalam C modul kedaluwarsa kita sendiri yang disebut IExpire.

Namun, dengan expirationd ada satu kesulitan lagi yang belum kita atasi dan itu terletak pada kenyataan bahwa expirationd hanya bekerja pada satu master. Dan jika node kedaluwarsa jatuh, cluster akan kehilangan fungsionalitas kritis. Misalkan kita membersihkan semua data yang lebih lama dari satu jam - jelas bahwa jika sebuah simpul terletak, katakanlah, lima jam, maka jumlah data akan x5 seperti biasanya. Dan jika pada saat itu serangan besar datang, yaitu, dua kasus buruk bertepatan, maka cluster akan jatuh. Kami belum tahu bagaimana menghadapi ini.

Akhirnya, masih ada kesulitan dengan driver Tarantool untuk C. Ketika kami mogok layanan (misalnya, karena kondisi balapan), butuh waktu lama untuk menemukan alasan dan men-debug. Karena itu, kami baru saja menulis driver Tarantool kami. Kami memerlukan waktu lima hari untuk mengimplementasikan protokol bersama dengan pengujian, debugging, dan peluncuran produksi, tetapi kami sudah memiliki kode kami sendiri untuk bekerja dengan jaringan.

Masalah di luar


Ingat bahwa kita sudah memiliki replikasi Tarantool yang siap, kita sudah tahu bagaimana cara menyinkronkan putusan, tetapi belum ada infrastruktur untuk mengirimkan pesan tentang serangan atau masalah antar cluster.
Kami memiliki banyak pemikiran berbeda tentang infrastruktur, termasuk pemikiran untuk menulis layanan TCP kami sendiri. Tapi masih ada modul Antrian Tarantool dari tim Tarantool. Selain itu, kami sudah memiliki Tarantool dengan replikasi lintas-cluster, "lubang" diputar, yaitu, tidak perlu pergi ke admin dan meminta untuk membuka port atau mengarahkan lalu lintas. Sekali lagi, integrasi ke dalam penyaringan perangkat lunak sudah siap.

Ada kesulitan dengan simpul host. Misalkan ada n node independen di dalam sebuah cluster dan Anda harus memilih yang akan berinteraksi dengan antrian tulis. Karena kalau tidak 16 pesan akan dikirim atau 16 kali pesan yang sama akan dikurangi dari antrian. Kami memecahkan masalah ini secara sederhana: kami mendaftarkan simpul yang bertanggung jawab di ruang Tarantool, dan jika simpul tersebut terbakar, maka kami cukup mengubah ruang tersebut jika kami tidak lupa. Tetapi jika kita lupa, maka ini adalah masalah yang juga ingin kita selesaikan di masa depan.

Di bawah ini adalah diagram cluster yang sudah terinci dengan antarmuka interaksi.



Apa yang ingin saya tingkatkan dan tambahkan


Pertama, kami ingin memposting di IExpire open source. Tampaknya bagi kami bahwa ini adalah modul yang berguna, karena memungkinkan Anda untuk melakukan semuanya sama seperti kadaluwarsa, tetapi dengan overhead hampir nol. Di sana Anda harus menambahkan indeks penyortiran untuk menghapus hanya tupel tertua. Sejauh ini, kami belum melakukan ini, karena operasi utama di Tarantool bagi kami adalah "menulis", dan indeks tambahan akan menyebabkan beban tambahan karena dukungannya. Kami juga ingin menulis ulang sebagian besar metode di CAPI untuk menghindari melipat database.

Pertanyaannya tetap dengan pilihan master logis, tetapi tampaknya masalah ini benar-benar mustahil untuk dipecahkan. Yaitu, jika node dengan expirationd jatuh, tetap hanya untuk memilih secara manual node lain dan menjalankan expirationd di atasnya. Ini tidak mungkin terjadi secara otomatis, karena replikasi asinkron. Meskipun kami mungkin akan berkonsultasi tentang ini dengan tim Tarantool.

Jika terjadi pertumbuhan cluster secara eksponensial, kami juga harus meminta bantuan tim Tarantool. Faktanya adalah bahwa replikasi semua-untuk-semua digunakan untuk Antrian Tarantool dan penghematan putusan interluster. Ini bekerja dengan baik, sementara ada tiga kelompok, misalnya, tetapi ketika ada 100 dari mereka, jumlah koneksi yang perlu dipantau akan sangat besar dan sesuatu akan terus-menerus terputus. Kedua, bukan fakta bahwa Tarantool dapat menahan beban seperti itu.

Kesimpulan


Kesimpulan pertama menyangkut UDP multicast dan Tarantool.Multicast tidak perlu takut akan hal itu, penggunaannya di dalam cluster baik, benar dan cepat. Ada banyak kasus ketika ada sinkronisasi konstan negara, dan setelah 50 milidetik tidak peduli apa yang terjadi sebelumnya. Dan dalam hal ini, kemungkinan besar, hilangnya satu negara tidak akan menjadi masalah. Jadi menggunakan UDP multicast dibenarkan, karena Anda tidak membatasi kinerja dan mendapatkan tingkat paket yang optimal.

Poin kedua adalah Tarantool. Jika Anda memiliki layanan saat bepergian, php dan sebagainya, maka kemungkinan besar Tarantool berlaku sebagaimana mestinya. Tetapi jika Anda memiliki banyak beban, Anda akan memerlukan file. Tapi jujur ​​saja, dalam hal ini, file diperlukan sama sekali untuk semuanya: baik untuk Oracle dan untuk PostgeSQL.

Tentu saja, ada pendapat bahwa Anda tidak perlu menemukan kembali roda, dan jika Anda memiliki tim kecil, maka Anda harus mengambil solusi yang sudah jadi: Redis untuk sinkronisasi, standar go, python, dan sebagainya. Ini tidak benar. Jika Anda yakin bahwa Anda memerlukan solusi baru, jika Anda bekerja dengan open source, menemukan bahwa tidak ada yang cocok untuk Anda, atau Anda tahu sebelumnya bahwa tidak ada gunanya mencoba, maka melihat keputusan Anda bermanfaat. Percakapan lain yang penting adalah berhenti tepat waktu. Artinya, Anda tidak perlu menulis Tarantool Anda, Anda tidak perlu mengimplementasikan perpesanan Anda, dan jika Anda hanya membutuhkan broker, bawa Redis dan Anda akan senang.

Source: https://habr.com/ru/post/id459264/


All Articles