Aller au contenu principal
IngénierieMar 28, 2026

Deep EVM #21 : Architecture événementielle en Rust — Pattern Bus pour systèmes temps réel

OS
Open Soft Team

Engineering Team

Pourquoi l’architecture événementielle ?

Dans les systèmes haute performance comme les bots MEV, les indexeurs blockchain et les moteurs de trading en temps réel, les composants doivent réagir aux événements avec une latence minimale. Le modèle requête-réponse traditionnel ne peut pas suivre. Vous avez besoin d’une architecture événementielle où les composants communiquent par passage de messages asynchrone.

Les channels tokio

Tokio fournit trois types de channels :

mpsc (Multi-Producer, Single-Consumer)

use tokio::sync::mpsc;

let (tx, mut rx) = mpsc::channel::<Event>(1024);

// Producteur
tokio::spawn(async move {
    tx.send(Event::NewBlock(block)).await.unwrap();
});

// Consommateur
tokio::spawn(async move {
    while let Some(event) = rx.recv().await {
        process(event).await;
    }
});

Idéal pour : pipelines unidirectionnels, files de tâches, agrégation de données.

broadcast (Multi-Producer, Multi-Consumer)

use tokio::sync::broadcast;

let (tx, _) = broadcast::channel::<Event>(1024);

// Chaque consommateur obtient sa propre copie
let mut rx1 = tx.subscribe();
let mut rx2 = tx.subscribe();

tokio::spawn(async move {
    while let Ok(event) = rx1.recv().await {
        handle_logging(event).await;
    }
});

tokio::spawn(async move {
    while let Ok(event) = rx2.recv().await {
        handle_strategy(event).await;
    }
});

Idéal pour : diffusion d’événements à plusieurs consommateurs (logging, métriques, stratégies).

watch (Single-Producer, Multi-Consumer, dernier-état)

use tokio::sync::watch;

let (tx, rx) = watch::channel(State::default());

// Le producteur met à jour l'état
tx.send(new_state).unwrap();

// Les consommateurs lisent le dernier état
let current = rx.borrow().clone();

Idéal pour : état partagé qui change fréquemment (prix courant, dernier bloc).

Le pattern Bus

Un bus d’événements centralise le routage des messages :

struct EventBus {
    block_tx: broadcast::Sender<Block>,
    mempool_tx: broadcast::Sender<Transaction>,
    opportunity_tx: mpsc::Sender<Opportunity>,
}

impl EventBus {
    fn new() -> Self {
        let (block_tx, _) = broadcast::channel(256);
        let (mempool_tx, _) = broadcast::channel(4096);
        let (opportunity_tx, _) = mpsc::channel(1024);
        Self { block_tx, mempool_tx, opportunity_tx }
    }

    fn subscribe_blocks(&self) -> broadcast::Receiver<Block> {
        self.block_tx.subscribe()
    }
}

Contrepression (Backpressure)

Quand un consommateur est plus lent que le producteur, les messages s’accumulent. Stratégies :

  1. Channel bornémpsc::channel(1024) bloque le producteur quand le buffer est plein
  2. try_send — Écarter les messages si le channel est plein (acceptable pour les métriques)
  3. Sémaphore — Limiter le nombre de tâches concurrentes
let semaphore = Arc::new(Semaphore::new(100));

for event in events {
    let permit = semaphore.clone().acquire_owned().await.unwrap();
    tokio::spawn(async move {
        process(event).await;
        drop(permit);
    });
}

Conclusion

L’architecture événementielle avec tokio channels est la base des systèmes temps réel en Rust. Le pattern bus centralise le routage, les différents types de channels répondent à différents besoins, et la contrepression garantit la stabilité sous charge.