Deep EVM #21: Ereignisgesteuerte Architektur in Rust — Bus-Muster fuer Echtzeitsysteme
Engineering Team
Warum ereignisgesteuerte Architektur?
In Hochleistungssystemen wie MEV-Bots, Blockchain-Indexern und Echtzeit-Trading-Engines muessen Komponenten mit minimaler Latenz auf Ereignisse reagieren. Das traditionelle Request-Response-Modell kann nicht mithalten. Sie brauchen eine ereignisgesteuerte Architektur, bei der Komponenten ueber asynchrones Message Passing kommunizieren.
tokio-Channel-Typen
mpsc (Multi-Producer, Single-Consumer)
use tokio::sync::mpsc;
let (tx, mut rx) = mpsc::channel::<BlockEvent>(1000);
// Producer 1
let tx1 = tx.clone();
tokio::spawn(async move {
tx1.send(BlockEvent::NewBlock(block)).await.unwrap();
});
// Producer 2
let tx2 = tx.clone();
tokio::spawn(async move {
tx2.send(BlockEvent::Reorg(depth)).await.unwrap();
});
// Single Consumer
tokio::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
BlockEvent::NewBlock(b) => process_block(b).await,
BlockEvent::Reorg(d) => handle_reorg(d).await,
}
}
});
broadcast (Multi-Producer, Multi-Consumer)
use tokio::sync::broadcast;
let (tx, _) = broadcast::channel::<PriceUpdate>(1000);
// Mehrere Consumer
let mut rx1 = tx.subscribe();
let mut rx2 = tx.subscribe();
tokio::spawn(async move {
while let Ok(update) = rx1.recv().await {
strategy_a.on_price(update).await;
}
});
tokio::spawn(async move {
while let Ok(update) = rx2.recv().await {
strategy_b.on_price(update).await;
}
});
watch (Single-Value, Multiple-Readers)
use tokio::sync::watch;
let (tx, rx) = watch::channel(GasPrice::default());
// Writer: Aktualisiert den neuesten Wert
tokio::spawn(async move {
loop {
let price = fetch_gas_price().await;
tx.send(price).unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
}
});
// Reader: Erhaelt immer den neuesten Wert
let current_price = *rx.borrow();
Das Bus-Muster
Ein zentraler Event-Bus, der Nachrichten an alle registrierten Handler weiterleitet:
struct EventBus {
block_tx: broadcast::Sender<BlockEvent>,
price_tx: broadcast::Sender<PriceUpdate>,
trade_tx: mpsc::Sender<TradeSignal>,
}
impl EventBus {
fn new() -> Self {
let (block_tx, _) = broadcast::channel(1000);
let (price_tx, _) = broadcast::channel(1000);
let (trade_tx, _) = mpsc::channel(1000);
Self { block_tx, price_tx, trade_tx }
}
fn subscribe_blocks(&self) -> broadcast::Receiver<BlockEvent> {
self.block_tx.subscribe()
}
}
Backpressure-Strategien
Wenn ein Consumer langsamer ist als ein Producer:
- Bounded Channels — Producer blockiert, wenn der Puffer voll ist
- Dropping — Aelteste Nachrichten verwerfen (broadcast::channel macht das automatisch)
- Sampling — Nur jede N-te Nachricht verarbeiten
- Batching — Nachrichten buendeln und als Batch verarbeiten
Fazit
Ereignisgesteuerte Architektur mit tokio-Channels ermoeglicht hochperformante, entkoppelte Systeme in Rust. Die Wahl des richtigen Channel-Typs — mpsc, broadcast oder watch — haengt von Ihrem Kommunikationsmuster ab. Das Bus-Muster bietet eine saubere Abstraktion fuer komplexe Systeme mit vielen Komponenten.