Menyiapkan Proyek Rust untuk Database Terdistribusi
Dari sebelumnya membahas teori, mau melanjutkan ke praktik, kita siapkan dulu lingkungan pengembangan proyeknya.
Bagian ini menyiapkan lingkungan pengembangan dan memperkenalkan crate-crate fundamental yang akan digunakan di seluruh proyek. Kombinasi tokio1
, tonic
2, dan serde
3 merupakan tumpukan standar de facto untuk membangun layanan jaringan berkinerja tinggi di Rust. Menguasai alat-alat ini memberikan keterampilan yang dapat ditransfer jauh melampaui pengembangan database.
Struktur Proyek
Menggunakan Cargo workspace adalah praktik yang baik untuk mengelola proyek multi-komponen. Kita akan membuat struktur berikut:
kv-store/
├── Cargo.toml # Workspace manifest
├── proto/ # Definisi Protocol Buffers
│ └── raft.proto
├── client/ # Crate untuk klien
│ └── Cargo.toml
│ └── src/
└── server/ # Crate untuk server
└── Cargo.toml
└── src/
└── build.rs
Runtime Asinkron: tokio
Aplikasi jaringan seperti server database terikat oleh I/O (I/O-bound). Mereka menghabiskan sebagian besar waktunya menunggu data tiba dari jaringan atau dibaca dari disk. Pemrograman asinkron memungkinkan satu thread untuk mengelola ribuan koneksi secara bersamaan. Ketika satu operasi menunggu I/O, runtime dapat menjadwalkan tugas lain untuk dijalankan, sehingga memaksimalkan penggunaan CPU.
tokio
adalah runtime asinkron yang paling banyak digunakan di ekosistem Rust. Ia menyediakan komponen-komponen penting:
Scheduler Tugas: Mengelola eksekusi ribuan tugas asinkron, bahkan pada sejumlah kecil thread sistem operasi.
I/O Asinkron: Menyediakan versi non-blocking dari API jaringan (TCP, UDP) dan I/O file.
Timer: Memungkinkan tugas untuk tidur atau menunggu hingga titik waktu tertentu.
Untuk memulai dengan tokio
, Anda akan menggunakan makro atribut #[tokio::main]
pada fungsi main
Anda. Ini akan menyiapkan runtime dan menjalankan fungsi main
asinkron Anda di dalamnya. Tugas-tugas konkuren dibuat menggunakan tokio::spawn
, yang mengambil sebuah Future
(biasanya blok async
) dan menjalankannya secara independen dari tugas saat ini.
Komunikasi Antar-Node: tonic
dan gRPC
Untuk komunikasi internal antar node Raft, kita memerlukan protokol Remote Procedure Call (RPC) yang efisien dan terdefinisi dengan baik. gRPC4 adalah pilihan yang sangat baik karena menggunakan Protocol Buffers5 untuk definisi skema dan HTTP/2 untuk transportasi, yang memberikan performa tinggi.
tonic
adalah implementasi gRPC untuk Rust, yang dibangun di atas tokio
dan hyper
. Alur kerja untuk menggunakan tonic
adalah sebagai berikut :
Definisikan Layanan: Tulis definisi layanan RPC Anda dalam file
.proto
. Untuk Raft, ini akan mencakup layananRaft
dengan RPCRequestVote
danAppendEntries
.Protocol Buffers
syntax = “proto3”;
package raft;
service Raft {
rpc RequestVote (RequestVoteRequest) returns (RequestVoteReply);
rpc AppendEntries (AppendEntriesRequest) returns (AppendEntriesReply);
}
//... definisi pesan
Hasilkan Kode: Gunakan
tonic-build
dalam skripbuild.rs
untuk mengkompilasi file.proto
menjadi kode Rust. Ini akan menghasilkan trait untuk layanan server danstruct
untuk klien.Implementasikan Server: Buat
struct
dan implementasikan trait layanan yang dihasilkan untuknya. Metode-metode dalam implementasi Anda akan menjadiasync fn
.Jalankan Server: Dalam fungsi
main
Anda, buat instansi dari implementasi layanan Anda dan gunakantonic::transport::Server
untuk mulai mendengarkan permintaan masuk.
Serialisasi Pesan: serde
Serialisasi adalah proses mengubah struktur data dalam memori (seperti struct
Rust) menjadi format yang dapat disimpan atau ditransmisikan (seperti JSON, atau format biner), dan kemudian merekonstruksinya kembali (deserialisasi). serde
adalah kerangka kerja standar untuk serialisasi dan deserialisasi di Rust.
Penggunaan paling umum dari serde
adalah melalui makro derive
:
Rust
use serde::{Serialize, Deserialize};
#
struct RaftMessage {
term: u64,
payload: Vec<u8>,
}
Dengan menambahkan #
, Anda secara otomatis mengimplementasikan trait Serialize
dan Deserialize
untuk struct
Anda. Ini memungkinkan Anda untuk dengan mudah mengubah instansi RaftMessage
menjadi dan dari berbagai format. Misalnya, untuk mengirimnya melalui jaringan sebagai JSON:
Rust
let message = RaftMessage { term: 1, payload: vec! };
// Serialisasi ke string JSON
let json_string = serde_json::to_string(&message).unwrap();
// Deserialisasi dari string JSON
let deserialized_message: RaftMessage = serde_json::from_str(&json_string).unwrap();
serde
sangat kuat dan dapat dikonfigurasi, dengan atribut untuk menangani kasus-kasus seperti mengganti nama bidang, memberikan nilai default, dan banyak lagi.
https://github.com/tokio-rs/tokio
https://github.com/hyperium/tonic
https://github.com/serde-rs/serde
https://grpc.io/docs/what-is-grpc/
https://protobuf.dev/overview/