Sumber kumpulan menggunakan Rangka Kerja Apache's Commons Pool

Pengumpulan sumber (juga disebut penyatuan objek) di antara beberapa klien adalah teknik yang digunakan untuk mempromosikan penggunaan semula objek dan untuk mengurangkan overhead untuk membuat sumber daya baru, menghasilkan kinerja dan hasil yang lebih baik. Bayangkan aplikasi pelayan Java tugas berat yang menghantar beratus-ratus pertanyaan SQL dengan membuka dan menutup sambungan untuk setiap permintaan SQL. Atau pelayan Web yang melayani ratusan permintaan HTTP, menangani setiap permintaan dengan memunculkan utas yang terpisah. Atau bayangkan membuat instance penghurai XML untuk setiap permintaan untuk menguraikan dokumen tanpa menggunakan kembali contohnya. Ini adalah beberapa senario yang memerlukan pengoptimuman sumber yang digunakan.

Penggunaan sumber boleh menjadi kritikal kadang-kadang untuk aplikasi tugas berat. Beberapa laman web terkenal telah ditutup kerana ketidakmampuan mereka menangani beban berat. Sebilangan besar masalah yang berkaitan dengan beban berat dapat ditangani, pada tahap makro, dengan menggunakan kemampuan pengelompokan dan pengimbangan beban. Kekhawatiran tetap ada di tingkat aplikasi sehubungan dengan penciptaan objek yang berlebihan dan ketersediaan sumber pelayan yang terhad seperti memori, CPU, utas, dan sambungan pangkalan data, yang dapat mewakili kemacetan yang berpotensi dan, ketika tidak digunakan secara optimal, menurunkan seluruh pelayan.

Dalam beberapa situasi, kebijakan penggunaan pangkalan data dapat memberlakukan batasan pada jumlah sambungan serentak. Juga, aplikasi luaran dapat menentukan atau menyekat jumlah sambungan terbuka serentak. Contoh biasa ialah pendaftaran domain (seperti Verisign) yang menghadkan bilangan sambungan soket aktif yang tersedia untuk pendaftar (seperti BulkRegister). Pengumpulan sumber telah terbukti menjadi salah satu pilihan terbaik dalam menangani jenis masalah ini dan, sampai batas tertentu, juga membantu dalam mempertahankan tahap layanan yang diperlukan untuk aplikasi perusahaan.

Sebilangan besar vendor pelayan aplikasi J2EE menyediakan penyatuan sumber sebagai bahagian tidak terpisahkan dari wadah Web dan EJB (Enterprise JavaBean) mereka. Untuk sambungan pangkalan data, vendor pelayan biasanya menyediakan pelaksanaan DataSourceantara muka, yang berfungsi bersamaan dengan ConnectionPoolDataSourcepelaksanaan vendor pemacu JDBC (Java Database Connectivity) . The ConnectionPoolDataSourcepelaksanaan berfungsi sebagai sambungan kilang pengurus sumber untuk dikumpulkan java.sql.Connectionobjek. Begitu juga, contoh kacang sesi tanpa status, kacang berdasarkan pesanan, dan kacang entiti digabungkan dalam bekas EJB untuk mendapatkan hasil dan prestasi yang lebih tinggi. Instance parser XML juga merupakan kandidat untuk penyatuan, kerana penciptaan instance parser memakan banyak sumber sistem.

Pelaksanaan penyatuan sumber terbuka yang berjaya adalah DBCP framework Commons Pool, komponen penyatuan sambungan pangkalan data dari Apace Software Foundation yang banyak digunakan dalam aplikasi perusahaan kelas produksi. Dalam artikel ini, saya membincangkan secara ringkas kerangka dalaman Commons Pool dan menggunakannya untuk melaksanakan kumpulan utas.

Mari lihat dahulu kerangka yang disediakan.

Kerangka Kolam Renang Commons

Rangka kerja Commons Pool menawarkan pelaksanaan dasar dan mantap untuk mengumpulkan objek sewenang-wenangnya. Beberapa implementasi disediakan, tetapi untuk tujuan artikel ini, kami menggunakan implementasi yang paling umum, yaitu GenericObjectPool. Ini menggunakan sebuah CursorableLinkedList, yang merupakan implementasi daftar ganda-terkait (bagian dari Koleksi Jakarta Commons), sebagai infrastruktur dasar untuk menahan objek yang dikumpulkan.

Di atas, kerangka menyediakan satu set antara muka yang menyediakan kaedah kitaran hidup dan kaedah penolong untuk mengurus, memantau, dan memperluas kumpulan.

Antaramuka org.apache.commons.PoolableObjectFactorymenentukan kaedah kitaran hidup berikut, yang terbukti penting untuk menerapkan komponen penyatuan:

 // Creates an instance that can be returned by the pool public Object makeObject() {} // Destroys an instance no longer needed by the pool public void destroyObject(Object obj) {} // Validate the object before using it public boolean validateObject(Object obj) {} // Initialize an instance to be returned by the pool public void activateObject(Object obj) {} // Uninitialize an instance to be returned to the pool public void passivateObject(Object obj) {}

Seperti yang anda dapati dengan tandatangan kaedah, antara muka ini terutama berkaitan dengan yang berikut:

  • makeObject(): Melaksanakan penciptaan objek
  • destroyObject(): Melaksanakan pemusnahan objek
  • validateObject(): Mengesahkan objek sebelum digunakan
  • activateObject(): Melaksanakan kod inisialisasi objek
  • passivateObject(): Melaksanakan kod uninitialization objek

Antara muka teras org.apache.commons.ObjectPoollain— —mendefinisikan kaedah berikut untuk mengurus dan memantau kumpulan:

 // Obtain an instance from my pool Object borrowObject() throws Exception; // Return an instance to my pool void returnObject(Object obj) throws Exception; // Invalidates an object from the pool void invalidateObject(Object obj) throws Exception; // Used for pre-loading a pool with idle objects void addObject() throws Exception; // Return the number of idle instances int getNumIdle() throws UnsupportedOperationException; // Return the number of active instances int getNumActive() throws UnsupportedOperationException; // Clears the idle objects void clear() throws Exception, UnsupportedOperationException; // Close the pool void close() throws Exception; //Set the ObjectFactory to be used for creating instances void setFactory(PoolableObjectFactory factory) throws IllegalStateException, UnsupportedOperationException;

Pelaksanaan ObjectPoolantaramuka mengambil PoolableObjectFactorysebagai argumen dalam konstruktornya, sehingga mendelegasikan pembuatan objek ke subkelasnya. Saya tidak banyak bercakap mengenai corak reka bentuk di sini kerana itu bukan fokus kami. Bagi pembaca yang berminat untuk melihat gambar rajah kelas UML, sila lihat Sumber.

Seperti yang disebutkan di atas, kelas org.apache.commons.GenericObjectPoolhanya satu pelaksanaan org.apache.commons.ObjectPoolantara muka. Kerangka kerja ini juga menyediakan implementasi untuk kumpulan objek yang dikunci, menggunakan antarmuka org.apache.commons.KeyedObjectPoolFactorydan org.apache.commons.KeyedObjectPool, di mana seseorang dapat mengaitkan kumpulan dengan kunci (seperti dalam HashMap) dan dengan itu menguruskan beberapa kumpulan.

Kunci strategi penggabungan yang berjaya bergantung pada bagaimana kita mengkonfigurasi kumpulan. Kolam yang dikonfigurasi dengan buruk boleh menjadi babi sumber, jika parameter konfigurasi tidak diselaraskan dengan baik. Mari lihat beberapa parameter penting dan tujuannya.

Perincian konfigurasi

Kolam boleh dikonfigurasi menggunakan GenericObjectPool.Configkelas, yang merupakan kelas dalaman yang statik. Sebagai alternatif, kita hanya boleh menggunakan GenericObjectPoolkaedah setter untuk menetapkan nilai.

Senarai berikut memperincikan beberapa parameter konfigurasi yang tersedia untuk GenericObjectPoolpelaksanaan:

  • maxIdle: Jumlah maksimum tidur di kolam renang, tanpa objek tambahan dilepaskan.
  • minIdle: Jumlah minimum tidur di kolam renang, tanpa dibuat objek tambahan.
  • maxActive: Jumlah maksimum kejadian aktif di kumpulan.
  • timeBetweenEvictionRunsMillis: Bilangan milisaat untuk tidur antara larian utas pengusir objek terbiar. Apabila negatif, tidak ada benang pengusir objek terbiar yang akan dijalankan. Gunakan parameter ini hanya apabila anda mahu thread pengusir berjalan.
  • minEvictableIdleTimeMillis: Jangka masa minimum objek, jika aktif, boleh duduk dalam kumpulan sebelum layak diusir oleh pengusir objek terbiar. Sekiranya nilai negatif diberikan, tidak ada objek yang diusir kerana waktu senggang sahaja.
  • testOnBorrow: Apabila "benar", objek disahkan. Sekiranya objek gagal disahkan, ia akan diturunkan dari kumpulan, dan kumpulan akan berusaha meminjam yang lain.

Nilai optimum harus diberikan untuk parameter di atas untuk mencapai prestasi dan hasil maksimum. Oleh kerana corak penggunaannya bervariasi dari aplikasi ke aplikasi, sesuaikan kumpulan dengan berbagai kombinasi parameter untuk mencapai solusi yang optimal.

Untuk memahami lebih lanjut mengenai kolam renang dan dalamannya, mari kita laksanakan kumpulan utas.

Keperluan kolam benang yang dicadangkan

Andaikan kita diberitahu untuk merancang dan menerapkan komponen kumpulan utas untuk penjadwal pekerjaan untuk memicu pekerjaan pada jadwal yang ditentukan dan melaporkan penyelesaiannya dan, mungkin, hasil pelaksanaannya. Dalam senario seperti itu, objektif kumpulan utas kami adalah mengumpulkan sejumlah utas prasyarat dan melaksanakan tugas yang dijadualkan dalam utas bebas. Keperluannya diringkaskan seperti berikut:

  • Benang harus dapat menggunakan kaedah kelas sewenang-wenangnya (pekerjaan yang dijadualkan)
  • Benang harus dapat mengembalikan hasil pelaksanaan
  • Benang harus dapat melaporkan penyelesaian tugas

Keperluan pertama memberi ruang untuk pelaksanaan yang digabungkan secara longgar kerana tidak memaksa kita untuk melaksanakan antara muka seperti Runnable. Ini juga memudahkan integrasi. Kami dapat melaksanakan syarat pertama kami dengan memberikan maklumat berikut:

  • Nama kelas
  • Nama kaedah yang akan digunakan
  • Parameter yang akan diteruskan ke kaedah
  • Jenis parameter parameter yang dilalui

Keperluan kedua membolehkan pelanggan menggunakan utas untuk menerima hasil pelaksanaan. Pelaksanaan yang mudah adalah menyimpan hasil pelaksanaan dan menyediakan kaedah aksesor seperti getResult().

Keperluan ketiga agak berkaitan dengan syarat kedua. Melaporkan penyelesaian tugas juga dapat berarti bahawa klien sedang menunggu untuk mendapatkan hasil pelaksanaan. Untuk menangani kemampuan ini, kami dapat menyediakan beberapa bentuk mekanisme panggilan balik. Mekanisme panggilan balik yang paling mudah dapat dilaksanakan dengan menggunakan java.lang.Objects wait()dan notify()semantik. Sebagai alternatif, kita boleh menggunakan corak Observer , tetapi buat masa ini mari kita memperbaikinya. Anda mungkin tergoda untuk menggunakan kaedah java.lang.Threadkelas join(), tetapi itu tidak akan berjaya kerana benang yang digabungkan tidak pernah menyelesaikan run()kaedahnya dan terus berjalan selagi kolam itu memerlukannya.

Sekarang kita sudah mempersiapkan keperluan kita dan idea kasar mengenai bagaimana melaksanakan kumpulan utas, inilah masanya untuk melakukan pengekodan sebenar.

Pada peringkat ini, rajah kelas UML kami mengenai reka bentuk yang dicadangkan kelihatan seperti gambar di bawah.

Melaksanakan kumpulan benang

Objek utas yang akan kita kumpulkan sebenarnya adalah pembungkus di sekitar objek utas. Mari panggil pembungkus WorkerThreadkelas, yang memanjangkan java.lang.Threadkelas. Sebelum kita dapat memulakan pengekodan WorkerThread, kita mesti melaksanakan syarat kerangka. Seperti yang kita lihat sebelumnya, kita mesti melaksanakan PoolableObjectFactory, yang bertindak sebagai kilang, untuk membuat WorkerThreads yang dapat dikumpulkan . Setelah kilang siap, kami melaksanakan ThreadPooldengan memanjangkan GenericObjectPool. Kemudian, kita selesai WorkerThread.

Melaksanakan antara muka PoolableObjectFactory

Kami bermula dengan PoolableObjectFactoryantara muka dan cuba menerapkan kaedah kitaran hidup yang diperlukan untuk kumpulan utas kami. Kami menulis kelas kilang ThreadObjectFactoryseperti berikut:

public class ThreadObjectFactory implements PoolableObjectFactory{

public Object makeObject() { return new WorkerThread(); } public void destroyObject(Object obj) { if (obj instanceof WorkerThread) { WorkerThread rt = (WorkerThread) obj; rt.setStopped(true);//Make the running thread stop } } public boolean validateObject(Object obj) { if (obj instanceof WorkerThread) { WorkerThread rt = (WorkerThread) obj; if (rt.isRunning()) { if (rt.getThreadGroup() == null) { return false; } return true; } } return true; } public void activateObject(Object obj) { log.debug(" activateObject..."); }

public void passivateObject(Object obj) { log.debug(" passivateObject..." + obj); if (obj instanceof WorkerThread) { WorkerThread wt = (WorkerThread) obj; wt.setResult(null); //Clean up the result of the execution } } }

Mari kita ikuti setiap kaedah secara terperinci:

Kaedah makeObject()mencipta WorkerThreadobjek. Untuk setiap permintaan, kolam diperiksa untuk melihat apakah objek baru akan dibuat atau objek yang ada harus digunakan kembali. Sebagai contoh, jika permintaan tertentu adalah permintaan pertama dan kumpulan kosong, ObjectPoolpelaksanaan memanggil makeObject()dan menambahkan WorkerThreadke kolam.

Kaedah destroyObject()mengeluarkan WorkerThreadobjek dari kolam dengan menetapkan bendera Boolean dan dengan itu menghentikan benang berjalan. Kami akan melihat bahagian ini lagi kemudian, tetapi perhatikan bahawa kita sekarang mengawal bagaimana objek kita dimusnahkan.