Kesesuaian dengan LMAX Disruptor - Pengenalan

1. Gambaran keseluruhan

Artikel ini memperkenalkan LMAX Disruptor dan membincangkan bagaimana ia membantu mencapai kesesuaian perisian dengan latensi rendah. Kami juga akan melihat penggunaan asas perpustakaan Disruptor.

2. Apa itu Pengganggu?

Disruptor adalah pustaka Java sumber terbuka yang ditulis oleh LMAX. Ini adalah kerangka pengaturcaraan serentak untuk memproses sejumlah besar transaksi, dengan latensi rendah (dan tanpa kerumitan kod serentak). Pengoptimuman prestasi dicapai dengan reka bentuk perisian yang memanfaatkan kecekapan perkakasan yang mendasari.

2.1. Simpati Mekanikal

Mari kita mulakan dengan konsep inti simpati mekanikal - ini adalah mengenai memahami bagaimana perkakasan yang mendasari beroperasi dan pengaturcaraan dengan cara yang paling sesuai dengan perkakasan tersebut.

Sebagai contoh, mari kita lihat bagaimana CPU dan organisasi memori dapat mempengaruhi prestasi perisian. CPU mempunyai beberapa lapisan cache di antaranya dan memori utama. Semasa CPU melakukan operasi, pertama kali mencari data di L1, kemudian L2, kemudian L3, dan akhirnya, memori utama. Semakin jauh perjalanannya, operasi akan semakin lama.

Sekiranya operasi yang sama dilakukan pada sekeping data berkali-kali (misalnya, penghitung gelung), masuk akal untuk memuatkan data tersebut ke tempat yang sangat dekat dengan CPU.

Beberapa angka petunjuk untuk kos kekurangan cache:

Latensi dari CPU ke Kitaran CPU Masa
Ingatan utama Pelbagai ~ 60-80 ns
Cache L3 ~ 40-45 kitaran ~ 15 ns
Cache L2 ~ 10 rakaat ~ 3 ns
Cache L1 ~ 3-4 kitaran ~ 1 ns
Daftar 1 kitaran Sangat cepat

2.2. Mengapa Tidak Beratur

Pelaksanaan antrian cenderung mempunyai perbalahan menulis pada kepala, ekor, dan pemboleh ubah ukuran. Antrian biasanya hampir penuh atau hampir kosong kerana perbezaan kadar antara pengguna dan pengeluar. Mereka jarang beroperasi di jalan tengah yang seimbang di mana kadar pengeluaran dan penggunaannya sama rata.

Untuk menangani perbalahan menulis, barisan sering menggunakan kunci, yang boleh menyebabkan peralihan konteks ke kernel. Apabila ini berlaku, pemproses yang terlibat cenderung kehilangan data dalam cache.

Untuk mendapatkan tingkah laku caching yang terbaik, reka bentuk hanya boleh mempunyai satu inti penulisan ke mana-mana lokasi memori (banyak pembaca baik-baik saja, kerana pemproses sering menggunakan pautan berkelajuan tinggi khas di antara cache mereka). Antrian gagal prinsip satu penulis.

Sekiranya dua utas berasingan menulis kepada dua nilai yang berbeza, setiap inti membatalkan garis cache yang lain (data dipindahkan antara memori utama dan cache dalam blok ukuran tetap, yang disebut garis cache). Itu adalah pertikaian menulis antara dua utas walaupun mereka menulis kepada dua pemboleh ubah yang berbeza. Ini disebut pembagian palsu, kerana setiap kali kepala diakses, ekor juga diakses, dan sebaliknya.

2.3. Bagaimana Pengganggu Berfungsi

Disruptor mempunyai struktur data bulat berdasarkan array (ring buffer). Ini adalah susunan yang mempunyai penunjuk ke slot yang tersedia seterusnya. Ia dipenuhi dengan objek pemindahan yang telah diperuntukkan. Pengeluar dan pengguna melakukan penulisan dan pembacaan data ke deringan tanpa mengunci atau berselisih pendapat.

Dalam Disruptor, semua acara diterbitkan kepada semua pengguna (multicast), untuk penggunaan selari melalui barisan hiliran yang berasingan. Oleh kerana pemprosesan selari oleh pengguna, perlu untuk menyelaraskan kebergantungan antara pengguna (grafik ketergantungan).

Pengeluar dan pengguna mempunyai pembilang urutan untuk menunjukkan slot mana dalam penyangga yang sedang diusahakannya. Setiap pengeluar / pengguna boleh menulis pembilang urutan sendiri tetapi boleh membaca pembilang urutan yang lain. Pengeluar dan pengguna membaca kaunter untuk memastikan slot yang ingin ditulisnya tersedia tanpa kunci.

3. Menggunakan Perpustakaan Disruptor

3.1. Ketergantungan Maven

Mari mulakan dengan menambahkan kebergantungan perpustakaan Disruptor di pom.xml :

 com.lmax disruptor 3.3.6 

Versi ketergantungan terkini boleh diperiksa di sini.

3.2. Mendefinisikan Peristiwa

Mari tentukan peristiwa yang membawa data:

public static class ValueEvent { private int value; public final static EventFactory EVENT_FACTORY = () -> new ValueEvent(); // standard getters and setters } 

The EventFactory membolehkan yang Disruptor preallocate peristiwa-peristiwa.

3.3. Pengguna

Pengguna membaca data dari penyangga cincin. Mari tentukan pengguna yang akan mengendalikan acara:

public class SingleEventPrintConsumer { ... public EventHandler[] getEventHandler() { EventHandler eventHandler = (event, sequence, endOfBatch) -> print(event.getValue(), sequence); return new EventHandler[] { eventHandler }; } private void print(int id, long sequenceId) { logger.info("Id is " + id + " sequence id that was used is " + sequenceId); } }

Dalam contoh kami, pengguna hanya mencetak ke log.

3.4. Membina Pengganggu

Bina Pengganggu:

ThreadFactory threadFactory = DaemonThreadFactory.INSTANCE; WaitStrategy waitStrategy = new BusySpinWaitStrategy(); Disruptor disruptor = new Disruptor( ValueEvent.EVENT_FACTORY, 16, threadFactory, ProducerType.SINGLE, waitStrategy); 

Dalam konstruktor Disruptor, berikut ditentukan:

  • Event Factory - Bertanggungjawab untuk menghasilkan objek yang akan disimpan di ring buffer semasa inisialisasi
  • Ukuran Ring Buffer - Kami telah menentukan 16 sebagai ukuran penyangga cincin. Ini harus menjadi kekuatan 2 yang lain akan membuang pengecualian semasa inisialisasi. Ini penting kerana mudah melakukan sebahagian besar operasi menggunakan pengendali binari logik contohnya mod operasi
  • Thread Factory - Kilang untuk membuat utas untuk pemproses acara
  • Jenis Pengeluar - Menentukan sama ada kita akan mempunyai pengeluar tunggal atau berganda
  • Strategi Menunggu - Menentukan bagaimana kami ingin menangani pelanggan lambat yang tidak mengikuti langkah pengeluar

Sambungkan pengendali pengguna:

disruptor.handleEventsWith(getEventHandler()); 

Adalah mungkin untuk membekalkan pelbagai pengguna dengan Disruptor untuk menangani data yang dihasilkan oleh pengeluar. Dalam contoh di atas, kami hanya mempunyai satu pengguna aka pengendali acara.

3.5. Memulakan Pengganggu

Untuk memulakan Disruptor:

RingBuffer ringBuffer = disruptor.start();

3.6. Menghasilkan dan Menerbitkan Acara

Pengeluar meletakkan data dalam penyangga cincin secara berurutan. Pengeluar harus mengetahui slot yang tersedia seterusnya agar mereka tidak menimpa data yang belum habis digunakan.

Gunakan RingBuffer dari Disruptor untuk menerbitkan:

for (int eventCount = 0; eventCount < 32; eventCount++) { long sequenceId = ringBuffer.next(); ValueEvent valueEvent = ringBuffer.get(sequenceId); valueEvent.setValue(eventCount); ringBuffer.publish(sequenceId); } 

Di sini, pengeluar menghasilkan dan menerbitkan item mengikut urutan. Penting untuk diperhatikan di sini bahawa Disruptor berfungsi serupa dengan protokol komit 2 fasa. Ia membaca urutan baruId dan menerbitkan. Lain kali ia akan mendapat urutanId + 1 sebagai urutanId seterusnya .

4. Kesimpulan

Dalam tutorial ini, kita telah melihat apa itu Disruptor dan bagaimana ia mencapai serentak dengan latensi rendah. Kami telah melihat konsep simpati mekanikal dan bagaimana ia dapat dimanfaatkan untuk mencapai kependaman rendah. Kami kemudian telah melihat contoh menggunakan perpustakaan Disruptor.

Contoh kod boleh didapati di projek GitHub - ini adalah projek berasaskan Maven, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.