Satu atau dua minggu yang lalu, saya melihat
pesan di forum
Pengguna RabbitMQ tentang cara mengatur pengiriman pesan dari SQL Server ke RabbitMQ. Karena kami bekerja sama dengan
Derivco , saya meninggalkan beberapa saran di sana, dan juga mengatakan bahwa saya menulis blog tentang cara melakukan ini. Sebagian dari pesan saya tidak sepenuhnya benar - setidaknya sampai saat itu (maaf, Bro, dia sangat sibuk).
Hebatnya, ini adalah
SQL Server Anda . Menggunakannya sangat mudah untuk memasukkan informasi ke dalam basis data. Mengambil data dari database menggunakan kueri juga mudah. Tetapi mendapatkan data yang baru saja diperbarui atau ditempelkan sudah sedikit lebih sulit. Pikirkan tentang peristiwa waktu nyata; pembelian dilakukan - seseorang harus diberi tahu tentang hal ini segera setelah hal ini terjadi. Mungkin seseorang akan mengatakan bahwa data tersebut tidak boleh muncul dari database, tetapi dari tempat lain. Tentu saja, ini masalahnya, tetapi seringkali kita tidak punya pilihan.
Kami memiliki tugas: mengirim peristiwa dari database di luar untuk diproses lebih lanjut, dan pertanyaannya adalah - bagaimana melakukan ini?
SQL Server dan komunikasi eksternal
Selama keberadaan SQL Server, ada beberapa upaya untuk mengatur komunikasi di luar database;
SQL Server Notification Services (NS), yang muncul di SQL Server 2000, dan kemudian, di SQL Server 2005,
SQL Server Service Broker (SSB) muncul. Saya menggambarkan mereka dalam buku saya
Pandangan Pertama di SQL Server 2005 untuk Pengembang , bersama dengan Bob Boshemen dan Dan Sullivan. NS muncul di SQL Server 2000, seperti yang saya katakan, dan dirancang ulang dalam versi beta SQL Server 2005. Namun, NS
sepenuhnya dikecualikan dari versi siap-jual (RTM) SQL Server 2005.
Catatan: Jika Anda membaca buku, Anda akan menemukan ada sejumlah fitur yang tidak ada dalam versi RTM.
SSB selamat, dan Microsoft memperkenalkan
Service Broker External Activator (EA) dalam SQL Server 2008 Feature Pack-nya. Itu memungkinkan melalui SSB untuk berinteraksi di luar database lokal. Secara teori, ini terdengar bagus, tetapi dalam praktiknya - ini rumit dan membingungkan. Kami melakukan beberapa tes dan dengan cepat menyadari bahwa itu tidak melakukan apa yang kami butuhkan. Selain itu, SSB tidak memberi kami kinerja yang diperlukan, jadi kami harus menciptakan sesuatu yang lain.
SQLCLR
Apa yang kami dapatkan sebagai hasilnya didasarkan pada teknologi SQLCLR. SQLCLR adalah platform .NET yang dibangun ke dalam inti SQL Server dan dapat digunakan untuk mengeksekusi kode .NET di dalam kernel. Karena kami menjalankan kode .NET, kami dapat melakukan hampir semua hal seperti pada aplikasi .NET biasa.
Catatan: Saya menulis "hampir" di atas, karena sebenarnya ada beberapa batasan. Dalam konteks ini, pembatasan ini hampir tidak berpengaruh pada apa yang akan kita lakukan.
Prinsip operasi SQLCLR adalah sebagai berikut: kode dikompilasi ke perpustakaan dll, dan kemudian perpustakaan ini terdaftar menggunakan alat SQL Server:
Bangun Majelis
CREATE ASSEMBLY [RabbitMQ.SqlServer] AUTHORIZATION rmq FROM 'F:\some_path\RabbitMQSqlClr4.dll' WITH PERMISSION_SET = UNSAFE; GO
Cuplikan Kode 1: Membuat Majelis di sepanjang Jalan Absolut
Kode melakukan tindakan berikut:
CREATE ASSEMBLY
- Membuat perakitan dengan nama yang diberikan (tidak peduli apa yang seharusnya).AUTHORIZATION
- Menunjukkan pemilik rakitan. Dalam hal ini, rmq adalah peran SQL Server yang telah ditentukan.FROM
- Menentukan lokasi perakitan asli. Di FROM
, Anda juga bisa menentukan lintasan dalam format biner atau UNC. File instalasi untuk proyek ini menggunakan representasi biner.WITH PERMISSION_SET
- Menetapkan izin. UNSAFE
adalah yang paling tidak ketat dan diperlukan dalam kasus ini.
Catatan: terlepas dari peran atau login yang digunakan dalam klausa AUTHORIZATION
, kelas appdomain harus dibuat dengan nama yang sama seperti ketika memuat perakitan ke dalam domain. Disarankan untuk memisahkan rakitan dengan berbagai nama kelas appdomain sehingga ketika satu rakitan gagal, sisanya tidak jatuh. Namun, jika majelis bergantung satu sama lain, mereka tidak dapat dibagi ke dalam kelas yang berbeda.
Ketika perakitan dibuat, kami membuat pembungkus metode .NET di dalamnya:
CREATE PROCEDURE rmq.pr_clr_PostRabbitMsg @EndpointID int, @Message nvarchar(max) AS EXTERNAL NAME [RabbitMQ.SqlServer].[RabbitMQSqlClr.RabbitMQSqlServer].[pr_clr_PostRabbitMsg]; GO
Kode 2: Pembungkus Metode .NET
Kode melakukan tindakan berikut:
- Membuat prosedur tersimpan T-SQL bernama
rmq.pr_clr_PostRabbitMsg
yang mengambil dua parameter; @EndpointID
dan @Message
. - Alih-alih badan prosedur, sumber eksternal digunakan, yang terdiri dari:
- Majelis yang bernama
RabbitMQ.SqlServer
, mis. Agregat yang kami buat di atas dalam cuplikan kode 1 . - Tipe lengkap (namespace dan kelas):
RabbitMQSqlClr.RabbitMQSqlServer
- Metode dari namespace dan kelas di atas adalah:
pr_clr_PostRabbitMsg
.
Ketika
rmq.pr_clr_PostRabbitMsg
, metode
pr_clr_PostRabbitMsg
akan dipanggil.
Catatan: saat membuat prosedur, nama rakitan tidak peka huruf besar-kecil, tidak seperti nama lengkap jenis dan metode. Tidak perlu bahwa nama prosedur yang dibuat cocok dengan nama metode. Namun, tipe data akhir untuk parameter harus cocok.
Seperti yang saya katakan sebelumnya, kami di Derivco perlu mengirim data di luar SQL Server, jadi kami menggunakan SQLCLR dan
RabbitMQ (RMQ).
Rabbitmq
RMQ adalah broker pesan sumber terbuka yang mengimplementasikan Advanced Message Queuing Protocol (AMQP) dan ditulis dalam bahasa Erlang.
Karena RMQ adalah pialang pesan, pustaka klien AMQP diharuskan untuk terhubung dengannya. Aplikasi merujuk ke pustaka klien dan, dengan bantuan mereka, membuka koneksi dan mengirim pesan - seperti, misalnya, ada panggilan melalui ADO.NET ke SQL Server. Tetapi tidak seperti ADO.NET, di mana, kemungkinan besar, koneksi terbuka setiap kali Anda mengakses database, di sini koneksi tetap terbuka untuk seluruh periode aplikasi.
Dengan demikian, untuk dapat berinteraksi dari database dengan RabbitMQ, kita membutuhkan aplikasi dan pustaka klien .NET untuk RabbitMQ.
Catatan: di bagian selanjutnya artikel ini, fragmen kode RabbitMQ akan ditemukan, tetapi tanpa penjelasan rinci tentang apa yang mereka lakukan. Jika Anda baru bekerja dengan RabbitMQ, maka saya sarankan untuk melihat berbagai tutorial RabbitMQ untuk memahami tujuan kode. Tutorial Hello World C # adalah awal yang baik. Salah satu perbedaan antara buku teks dan contoh kode adalah bahwa penukar tidak dinyatakan dalam contoh. Mereka seharusnya sudah ditentukan sebelumnya.
RabbitMQ.SqlServer
RabbitMQ.SqlServer adalah perakitan yang menggunakan perpustakaan .NET klien untuk RabbitMQ dan menyediakan kemampuan untuk mengirim pesan dari database ke satu atau lebih titik akhir RabbitMQ (VHosts dan penukar). Kode dapat diunduh / bercabang dari repositori saya
RabbitMQ-SqlServer di GitHub. Ini berisi sumber rakitan dan file instalasi (mis. Anda tidak harus mengompilasinya sendiri).
Catatan: ini hanyalah contoh untuk menunjukkan bagaimana SQL Server dapat berinteraksi dengan RabbitMQ. Ini BUKAN produk jadi atau bahkan bagian dari itu. Jika kode ini menghancurkan otak Anda - jangan salahkan saya, karena ini hanyalah sebuah contoh.
Fungsionalitas
Ketika rakitan dimuat, atau ketika inisialisasi secara eksplisit dipanggil, atau ketika itu disebut secara tidak langsung, pada saat prosedur pembungkus dipanggil, rakitan memuat string koneksi ke dalam database lokal di mana ia dipasang, serta titik akhir RabbitMQ yang terhubung:
Koneksi
internal bool InternalConnect() { try { connFactory = new ConnectionFactory(); connFactory.Uri = connString; connFactory.AutomaticRecoveryEnabled = true; connFactory.TopologyRecoveryEnabled = true; RabbitConn = connFactory.CreateConnection(); for (int x = 0; x < channels; x++) { var ch = RabbitConn.CreateModel(); rabbitChannels.Push(ch); } return true; } catch(Exception ex) { return false; } }
Cuplikan Kode 3: Sambungkan ke Titik Akhir
Pada saat yang sama, bagian dari koneksi ke titik akhir juga membuat IModels pada koneksi, dan mereka digunakan saat mengirim (menambahkan ke antrian) pesan:
Pengiriman pesan
internal bool Post(string exchange, byte[] msg, string topic) { IModel value = null; int channelTryCount = 0; try { while ((!rabbitChannels.TryPop(out value)) && channelTryCount < 100) { channelTryCount += 1; Thread.Sleep(50); } if (channelTryCount == 100) { var errMsg = $"Channel pool blocked when trying to post message to Exchange: {exchange}."; throw new ApplicationException(errMsg); } value.BasicPublish(exchange, topic, false, null, msg); rabbitChannels.Push(value); return true; } catch (Exception ex) { if (value != null) { _rabbitChannels.Push(value); } throw; } }
Metode
Post
dipanggil dari metode
pr_clr_PostRabbitMsg(int endPointId, string msgToPost)
, yang disajikan sebagai prosedur menggunakan klausa
CREATE PROCEDURE
dalam fragmen kode 2:
Metode pemanggilan pos
public static void pr_clr_PostRabbitMsg(int endPointId, string msgToPost) { try { if(endPointId == 0) { throw new ApplicationException("EndpointId cannot be 0"); } if (!isInitialised) { pr_clr_InitialiseRabbitMq(); } var msg = Encoding.UTF8.GetBytes(msgToPost); if (endPointId == -1) { foreach (var rep in remoteEndpoints) { var exch = rep.Value.Exchange; var topic = rep.Value.RoutingKey; foreach (var pub in rabbitPublishers.Values) { pub.Post(exch, msg, topic); } } } else { RabbitPublisher pub; if (rabbitPublishers.TryGetValue(endPointId, out pub)) { pub.Post(remoteEndpoints[endPointId].Exchange, msg, remoteEndpoints[endPointId].RoutingKey); } else { throw new ApplicationException($"EndpointId: {endPointId}, does not exist"); } } } catch { throw; } }
Cuplikan Kode 5: Mewakili Metode sebagai Prosedur
Ketika metode dieksekusi, diasumsikan bahwa penelepon mengirimkan pengidentifikasi titik akhir di mana pesan harus dikirim, dan, pada kenyataannya, pesan itu sendiri. Jika nilai -1 dilewatkan sebagai pengidentifikasi titik akhir, maka kami mengulangi semua titik dan mengirim pesan ke masing-masing. Pesan itu datang dalam bentuk string dari mana kita mendapatkan byte menggunakan
Encoding.UTF8.GetBytes
. Dalam lingkungan produksi, panggilan
Encoding.UTF8.GetBytes
harus diganti dengan serialisasi.
Instalasi
Untuk menginstal dan menjalankan contoh, Anda memerlukan semua file di folder
src\SQL
. Untuk menginstal, ikuti langkah-langkah ini:
- Jalankan skrip
01.create_database_and_role.sql
. Dia akan menciptakan:
RabbitMQTest
data uji RabbitMQTest
tempat perakitan akan dibuat.- peran
rmq
untuk ditugaskan sebagai pemilik majelis - skema, yang juga akan disebut
rmq
. Dalam diagram ini, berbagai objek basis data dibuat.
- Jalankan file
02.create_database_objects.sql
. Dia akan menciptakan:
- tabel
rmq.tb_RabbitSetting
, yang akan menyimpan string koneksi ke database lokal. - Tabel
rmq.tb_RabbitEndpoint
, di mana satu atau lebih titik akhir RabbitMQ
akan disimpan.
- Dalam file
03.create_localhost_connstring.sql
ubah nilai variabel @connString
ke string koneksi yang benar untuk database RabbitMQTest
dibuat pada langkah 1 dan jalankan skrip.
Sebelum melanjutkan, Anda harus memiliki instance berjalan broker RabbitMQ dan VHost (secara default, VHost direpresentasikan sebagai /). Sebagai aturan, kami memiliki beberapa VHost, hanya untuk isolasi. Tuan rumah ini juga membutuhkan penukar, dalam contoh kita menggunakan
amq.topic
. Ketika broker RabbitMQ Anda siap, edit
rmq.pr_UpsertRabbitEndpoint
prosedur
rmq.pr_UpsertRabbitEndpoint
, yang terletak di file
04.upsert_rabbit_endpoint.sql
:
Endpoint RabbitMQ
EXEC rmq.pr_UpsertRabbitEndpoint @Alias = 'rabbitEp1', @ServerName = 'RabbitServer', @Port = 5672, @VHost = 'testHost', @LoginName = 'rabbitAdmin', @LoginPassword = 'some_secret_password', @Exchange = 'amq.topic', @RoutingKey = '#', @ConnectionChannels = 5, @IsEnabled = 1
Kode 6: Menciptakan Endpoint di RabbitMQ
Pada titik ini, sekarang saatnya untuk menggelar majelis. Ada perbedaan dalam opsi penyebaran untuk versi SQL Server sebelum SQL Server 2014 (2005, 2008, 2008R2, 2012), dan untuk 2014 dan yang lebih baru. Perbedaannya terletak pada versi CLR yang didukung. Sebelum SQL Server 2014, platform .NET berjalan di CLR versi 2, dan di SQL Server 2014 dan di atasnya, versi 4 digunakan.
SQL Server 2005 - 2012
Mari kita mulai dengan versi SQL Server yang berjalan pada CLR 2, karena mereka memiliki karakteristik sendiri. Kita perlu menggunakan rakitan yang dibuat, dan pada saat yang sama menyebarkan pustaka RabbitMQ klien .NET (
RabbitMQ.Client
). Dari pertemuan kami, kami akan merujuk ke perpustakaan klien RabbitMQ. Karena Karena kami berencana untuk menggunakan CLR 2, perakitan kami dan
RabbitMQ.Client
harus dikompilasi berdasarkan .NET 3.5. Ada masalah.
Semua versi terbaru dari perpustakaan
RabbitMQ.Client
dikompilasi untuk lingkungan CLR 4, sehingga mereka tidak dapat digunakan dalam perakitan kami. Versi terbaru dari perpustakaan klien untuk CLR 2 dikompilasi di .NET 3.4.3. Tetapi bahkan jika kami mencoba menggunakan majelis ini, kami mendapatkan pesan kesalahan:
Gambar 1: Sistem Hilang. Perakitan Model LayananVersi
RabbitMQ.Client
merujuk ke perakitan yang bukan bagian dari SQL Server CLR. Ini adalah perakitan WCF, dan ini adalah salah satu keterbatasan dalam SQLCLR yang saya sebutkan di atas: perakitan khusus ini untuk jenis tugas yang tidak diperbolehkan dilakukan dalam SQL Server. Versi terbaru dari
RabbitMQ.Client
tidak memiliki dependensi ini, sehingga mereka dapat digunakan tanpa masalah, kecuali untuk persyaratan mengganggu dari CLR 4. Apa yang harus saya lakukan?
Seperti yang Anda ketahui, RabbitMQ adalah open source, tetapi kami adalah pengembang, bukan? ;) Jadi mari kita kompilasi ulang! Dalam versi sebelum rilis terbaru (mis. Versi <3.5.0) dari
RabbitMQ.Client
saya menghapus tautan ke
System.ServiceModel
dan dikompilasi ulang. Saya harus mengubah beberapa baris kode menggunakan fungsionalitas
System.ServiceModel
, tetapi ini adalah perubahan kecil.
Dalam contoh ini, saya tidak menggunakan versi klien 3.4.3, tapi saya mengambil
rilis stabil 3.6.6 dan dikompilasi ulang menggunakan .NET 3.5 (CLR 2). Itu hampir berhasil :), kecuali bahwa rilis kemudian dari
RabbitMQ.Client
menggunakan
Task
'dan yang awalnya bukan bagian dari. NET 3.5.
Untungnya, ada versi
System.Threading.dll
untuk .NET 3.5 yang mencakup
Task
. Saya mengunduhnya, mengatur tautan dan semuanya berjalan! Di sini trik utamanya adalah
System.Threading.dll
harus diinstal dengan perakitan.
Catatan: sumber RabbitMQ.Client
, dari mana saya menyusun versi .NET 3.5, ada di repositori saya di GitHub RabbitMQ Client 3.6.6 .NET 3.5 . Biner dll bersama dengan System.Threading.dll
untuk. NET 3.5 juga terletak di lib\NET3.5
repositori (RabbitMQ-SqlServer) .
Untuk menginstal rakitan yang diperlukan (
System.Threading
,
RabbitMQ.Client
dan
RabbitMQ.SqlServer
) jalankan skrip instalasi dari direktori
src\sql
dengan urutan sebagai berikut:
05.51.System.Threading.sql2k5-12.sql
- System.Threading05.52.RabbitMQ.Client.sql2k5-12.sql
- RabbitMQ.Client05.53.RabbitMQ.SqlServer.sql2k5-12.sql
- RabbitMQ.SqlServer
SQL Server 2014+
Di SQL Server 2014 dan yang lebih baru, kumpulan mengkompilasi di bawah. NET 4.XX (contoh saya ada di 4.5.2), dan Anda dapat mereferensikan salah satu versi terbaru dari
RabbitMQ.Client
, yang dapat diperoleh dengan menggunakan
NuGet . Dalam contoh saya, saya menggunakan 4.1.1.
RabbitMQ.Client
, yang juga ada di
lib\NET4
repositori (RabbitMQ-SqlServer) .
Untuk menginstal, jalankan skrip dari direktori
src\sql
dengan urutan sebagai berikut:
05.141.RabbitMQ.Client.sql2k14+.sql
.sql - RabbitMQ.Client05.142.RabbitMQ.SqlServer.sql2k14+.sql
.sql - RabbitMQ.SqlServer
Pembungkus Metode SQL
Untuk membuat prosedur yang akan digunakan dari perakitan kami (3,5 atau 4), jalankan skrip
06.create_sqlclr_procedures.sql
. Dia akan membuat prosedur T-SQL untuk tiga metode .NET:
rmq.pr_clr_InitialiseRabbitMq
memanggil pr_clr_InitialiseRabbitMq
. Digunakan untuk memuat dan menginisialisasi perakitan RabbitMQ.SqlServer.rmq.pr_clr_ReloadRabbitEndpoints
memanggil pr_clr_ReloadRabbitEndpoints
. Memuat berbagai titik akhir RabbitMQ.rmq.pr_clr_PostRabbitMsg
memanggil pr_clr_PostRabbitMsg
. Digunakan untuk mengirim pesan ke RabbitMQ.
Script juga membuat prosedur T-SQL sederhana -
rmq.pr_PostRabbitMsg
, yang berlaku untuk
rmq.pr_clr_PostRabbitMsg
. Ini adalah prosedur pembungkus yang tahu apa yang harus dilakukan dengan data, menangani pengecualian, dll. Dalam lingkungan produksi, kami memiliki beberapa prosedur serupa yang memproses berbagai jenis pesan. Baca lebih lanjut tentang ini di bawah ini.
Gunakan
Dari semua hal di atas, jelas bahwa untuk mengirim pesan ke RabbitMQ kita sebut
rmq.pr_PostRabbitMsg
atau
rmq.pr_clr_PostRabbitMsg
, dengan mengirimkan parameter pengidentifikasi titik akhir dan pesan itu sendiri sebagai string. Semua ini, tentu saja, keren, tetapi saya ingin melihat bagaimana ini akan bekerja dalam kenyataan.
Apa yang kami lakukan di lingkungan produksi adalah bahwa dalam prosedur tersimpan yang memproses data yang harus dikirim ke RabbitMQ, kami mengumpulkan data yang akan dikirim dan di blok koneksi kami memanggil prosedur seperti
rmq.pr_PostRabbitMsg
. Berikut ini adalah contoh prosedur yang sangat disederhanakan:
Prosedur pemrosesan data
ALTER PROCEDURE dbo.pr_SomeProcessingStuff @id int AS BEGIN SET NOCOUNT ON; BEGIN TRY
Dalam
fragmen kode 7, kita melihat bagaimana data yang diperlukan ditangkap dan diproses dalam prosedur dan dikirim setelah pemrosesan. Untuk menggunakan prosedur ini, jalankan skrip
07.create_processing_procedure.sql
dari direktori
src\SQL
.
Mari kita jalankan semuanya
Pada titik ini, Anda harus siap untuk mengirim beberapa pesan. Sebelum pengujian, pastikan Anda memiliki antrian di RabbitMQ yang terpasang pada penukar titik akhir di
rmq.tb_RabbitEndpoint
.
Jadi, untuk memulai Anda perlu melakukan hal berikut:
Buka file
99.test_send_message.sql
.
Lari
EXEC rmq.pr_clr_InitialiseRabbitMq;
untuk menginisialisasi perakitan dan memuat titik akhir RabbitMQ. Ini bukan langkah yang diperlukan, tetapi Anda disarankan untuk melakukan preload perakitan setelah membuat atau memodifikasinya.
Lari
EXEC dbo.pr_SomeProcessingStuff @id = 101
(Anda dapat menggunakan pengenal lain yang Anda suka).
Jika semuanya berfungsi tanpa kesalahan, maka sebuah pesan akan muncul di antrian RabbitMQ! Jadi, Anda menggunakan SQLCLR untuk mengirim pesan ke RabbitMQ.
Selamat!