Sistemas Distribuídos com Rust
Concorrência, paralelismo e tolerância a falhas
Introdução
Por que Rust?
- Zero-cost abstractions
- Sem data races
- Ownership model
- Ecossistema maduro
🛡️ Segurança Garantida
Rust elimina classes inteiras de bugs em sistemas distribuídos:
Problema | Solução Rust |
---|---|
Data races | Checagem em tempo de compilação |
Memory leaks | Sistema de ownership |
Aplicações Práticas
O modelo de programação do Rust é particularmente adequado para aplicações que exigem alta performance e segurança, como servidores de backend, sistemas embarcados e soluções tolerantes a falhas em redes distribuídas.
🌍 Casos Reais na Indústria
Linkerd 2.x (Service Mesh)
Por que Rust?
- Performance determinística: Sem GC para latência previsível em proxies
- Segurança: Proteção contra vulnerabilidades de memória em sistemas críticos
- Concorrência segura: Manipulação eficiente de +10k conexões concorrentes
Arquitetura Chave:
┌───────────────────────────────┐ │ Aplicação │ │ (Pod Kubernetes) │ └──────────────┬───────────────┘ │ ┌──────────────▼───────────────┐ │ Linkerd-Proxy (Rust) │ │ ┌─────────────────────────┐ │ │ │ Protocolo HTTP/2 │ │ │ │ + mTLS automático │ │ │ └─────────────────────────┘ │ └──────────────┬───────────────┘ │ ┌──────────────▼───────────────┐ │ Service Mesh Control Plane │ └──────────────────────────────┘
Trecho Relevante (Linkerd-proxy):
// Filtro de autorização simplificado
impl<S> tower::Service<http::Request<hyper::Body>> for AuthFilter<S>
where
S: Service<http::Request<hyper::Body>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: http::Request<hyper::Body>) -> Self::Future {
if !authorize(req.headers()) {
return async { Err("Unauthorized".into()) }.boxed();
}
self.inner.call(req)
}
}
Impacto Mensurável:
TiKV (Key-Value Store Distribuído)
Arquitetura com Rust:
- Raft Consensus: Implementação customizada em Rust
- Storage Engine: RocksDB + abstrações seguras
- Processamento Paralelo: Thread pools isoladas com Rayon
Fluxo de Escrita:
- Cliente envia operação para líder Raft
- Líder replica para >50% dos nós
- Commit aplicado ao storage engine
- Resposta ao cliente
// Trecho simplificado do Raft Store
impl RaftStore {
pub async fn propose(&self, cmd: Command) -> Result<RaftResponse> {
let ctx = self.raft_ctx.lock().await;
let data = cmd.to_bytes();
// Submete à replicação Raft
let res = self.raft_group.propose(ctx, data).await?;
// Aplica quando commitado
if let Some(response) = self.apply_responses.get(&res.index) {
return Ok(response.clone());
}
Err(Error::Timeout)
}
}
🔍 Fundamentos Teóricos: Rust vs Outras Linguagens
Comparação de Linguagens para Sistemas Distribuídos
Rust oferece um equilíbrio único entre performance e segurança em sistemas distribuídos:
Vantagens do Rust
- ✅ Segurança de memória sem garbage collector
- ✅ Concorrência sem data races em tempo de compilação
- ✅ Performance comparável a C/C++
- ✅ Compilação para WASM (edge computing)
Cenários Típicos
- 🦀 Rust: Middlewares críticos, proxies, sistemas de coordenação
- 🐹 Go: Microsserviços, APIs, CLIs distribuídos
- ☕ Java: Sistemas empresariais com JVM
- 🐍 Python: Prototipagem, scripts de automação
Arquitetura Típica com Rust
┌─────────────────────────────────────────────────┐ │ Camada de Aplicação (Python/Java/Go) │ └───────────────┬─────────────────┬───────────────┘ │ HTTP/gRPC │ ┌───────────────▼─────┐ ┌─────────▼───────────────┐ │ Serviços Rust │ │ Message Broker │ │ (High-Perf) │ │ (Kafka/RabbitMQ) │ └───────────┬─────────┘ └─────────┬───────────────┘ │ │ ┌───────────▼─────────────────────▼───────────────┐ │ Componentes Críticos em Rust │ │ • Service Mesh (Linkerd) │ │ • Sistemas de Consenso (Raft) │ │ • Data Processing Pipelines │ └─────────────────────────────────────────────────┘
Rust brilha em componentes onde performance e confiabilidade são críticas.
Tabela Comparativa de Garantias
Característica | Rust | Go | Java | C++ |
---|---|---|---|---|
Segurança de Memória | ✅ Compile-time | ✅ GC | ✅ GC | ❌ Manual |
Thread Safety | ✅ Garantido | ✅ Canal-based | ✅ Monitor | ❌ Opcional |
Performance | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ |
Startup Time | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️⭐️⭐️ |
Ecossistema DS | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️ |
Legenda: GC = Garbage Collector, DS = Distributed Systems
🧠 Conceitos Fundamentais
Concorrência e Segurança de Memória
Rust oferece garantias de segurança em tempo de compilação, evitando condições de corrida e vazamentos de memória através de seu sistema de ownership e borrowing. Isso é especialmente valioso em sistemas distribuídos onde a concorrência é essencial.
// Uso seguro de múltiplas threads com compartilhamento imutável
use std::thread;
fn main() {
let dados = vec![1, 2, 3, 4];
let handles: Vec<_> = (0..4).map(|i| {
// Usamos move para transferir ownership da cópia
thread::spawn(move || {
println!("Thread {} processando valor: {}", i, dados[i]);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
// Dados originais ainda estão disponíveis aqui
println!("Dados originais: {:?}", dados);
}
Note como Rust permite compartilhamento seguro de dados entre threads quando eles são imutáveis. Para dados mutáveis, Rust exige mecanismos como Arc<Mutex<T>>
para garantir segurança.
Modelo de Ownership
O sistema de ownership do Rust previne acessos inválidos e uso após liberação de memória através de três regras fundamentais:
- Cada valor em Rust tem um owner (dono)
- Só pode haver um owner por vez
- Quando o owner sai de escopo, o valor é liberado
// Demonstração de borrowing e ownership
fn main() {
let mut mensagem = String::from("Processamento");
// Empresta uma referência imutável
calcular_tamanho(&mensagem);
// Empresta uma referência mutável
modificar(&mut mensagem);
println!("Mensagem final: {}", mensagem);
}
fn calcular_tamanho(s: &String) -> usize {
s.len() // s é uma referência imutável
}
fn modificar(s: &mut String) {
s.push_str(" distribuído"); // s é uma referência mutável
}
Este exemplo mostra como Rust gerencia empréstimos (borrowing) de forma segura, permitindo ou uma única referência mutável ou múltiplas referências imutáveis.
Tipos de Comunicação
Rust oferece vários padrões para comunicação entre threads e processos, essenciais para sistemas distribuídos:
1. Channels (Múltiplos Produtores, Único Consumidor)
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
// Produtor 1
let tx1 = tx.clone();
thread::spawn(move || {
let mensagens = vec!["Task1", "Task2", "Task3"];
for msg in mensagens {
tx1.send(msg).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
// Produtor 2
thread::spawn(move || {
let mensagens = vec!["Ack1", "Ack2"];
for msg in mensagens {
tx.send(msg).unwrap();
thread::sleep(Duration::from_secs(2));
}
});
// Consumidor
for recebida in rx {
println!("Processando: {}", recebida);
}
}
2. Shared State com Arc<Mutex<T>>
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let contador = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..4 {
let contador = Arc::clone(&contador);
let handle = thread::spawn(move || {
let mut num = contador.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Total: {}", *contador.lock().unwrap());
}
Estes padrões são fundamentais para construir sistemas distribuídos seguros e eficientes em Rust.
Pré Requisitos Rust
⚙️ Configuração do Ambiente Rust
Instalação do Rust
Para começar a trabalhar com Rust, você precisa instalar o compilador e o gerenciador de pacotes:
Linux/macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
Windows
Baixe e execute o rustup-init.exe
Verifique a instalação:
rustc --version
cargo --version
Ferramentas Úteis
Para desenvolvimento eficiente:
- Rust Analyzer: Extensão para VS Code/IDEs (autocompletar, verificação em tempo real)
- Cargo: Gerenciador de pacotes e sistema de build (já vem com a instalação)
Testando Online (REPL)
Se não puder instalar localmente, use:
- Rust Playground - Ambiente online para testes rápidos
- Replit Rust - IDE online completa
Para os exemplos assíncronos (Tokio/Reqwest), você precisará de instalação local pois o playground não suporta todas as bibliotecas.
Configurando Dependências
Para os exemplos desta aula, adicione ao seu Cargo.toml
:
[dependencies]
tokio = { version = "1.0", features = ["full"] }
reqwest = "0.11"
crossbeam = "0.8"
serde = { version = "1.0", features = ["derive"] }
Depois execute:
cargo build
🦀 Rust na Prática
Trabalhando com Threads
Rust permite a criação de múltiplas threads com segurança em tempo de compilação, evitando condições de corrida.
use std::thread;
fn main() {
let t1 = thread::spawn(|| {
println!(\"Thread 1 ativa\");
});
let t2 = thread::spawn(|| {
println!(\"Thread 2 ativa\");
});
t1.join().unwrap();
t2.join().unwrap();
}
Programação Assíncrona com Tokio
Com o runtime tokio
, é possível criar aplicações assíncronas eficientes para servidores e clientes distribuídos.
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let tarefa1 = tokio::spawn(async {
sleep(Duration::from_secs(1)).await;
println!(\"Tarefa 1 concluída\");
});
let tarefa2 = tokio::spawn(async {
println!(\"Tarefa 2 concluída\");
});
tarefa1.await.unwrap();
tarefa2.await.unwrap();
}
HTTP com a Crate Reqwest
Podemos consumir APIs com segurança e performance usando reqwest
.
use reqwest;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let resposta = reqwest::get(\"https://httpbin.org/ip\").await?
.text().await?;
println!(\"Resposta: {}\", resposta);
Ok(())
}
Mensageria com Crate Crossbeam
O canal crossbeam
permite comunicação segura entre threads, útil para arquiteturas com múltiplos workers.
use crossbeam::channel;
use std::thread;
fn main() {
let (tx, rx) = channel::unbounded();
thread::spawn(move || {
tx.send(\"Mensagem distribuída\").unwrap();
});
println!(\"Recebido: {}\", rx.recv().unwrap());
}
⚡ Padrões Avançados em Sistemas Distribuídos
Padrões Essenciais com Rust
Estes são os padrões fundamentais para construir sistemas resilientes:
🔗 Circuit Breaker
Previne cascata de falhas abrindo o circuito quando erros atingem um limiar.
use tower::timeout::Timeout;
use tower::retry::Retry;
use tower::buffer::Buffer;
use tower::limit::RateLimit;
// Stack de resiliência típica
let service = Timeout::new(
Retry::new(
CircuitBreaker::new(
RateLimit::new(
Buffer::new(MyService, 10),
100 // req/s
)
),
ExponentialBackoff::default()
),
Duration::from_secs(3)
);
Crates recomendadas:
tower
, rust-circuit-breaker
🔄 Retry com Backoff Exponencial
Reconecta automaticamente com intervalos crescentes.
use backoff::ExponentialBackoff;
let operation = || async {
reqwest::get("https://api.example.com")
.await?
.error_for_status()
};
backoff::future::retry(ExponentialBackoff::default(), operation).await;
Crates:
backoff
, tokio-retry
🚧 Bulkhead (Compartimentação)
Isola recursos para evitar falhas em cascata.
use tokio::sync::Semaphore;
let semaphore = Arc::new(Semaphore::new(10)); // 10 conexões máximas
async fn process() {
let _permit = semaphore.acquire().await; // Bloqueia se exceder
// Operação limitada
}
Mecanismo nativo:
tokio::sync::Semaphore
🏗️ Casos Reais em Produção
Arquitetura Completa de Serviço Distribuído
Exemplo de serviço Rust pronto para produção integrando múltiplos componentes:
use tonic::{transport::Server, Request, Response, Status};
use etcd_client::{Client, LeaseGrantOptions};
use tokio::signal;
use std::time::Duration;
mod pb {
tonic::include_proto!("meuservico");
}
#[derive(Default)]
struct MeuServico;
#[tonic::async_trait]
impl pb::servico_server::Servico for MeuServico {
async fn health_check(
&self,
_: Request<pb::HealthCheckRequest>
) -> Result<Response<pb::HealthCheckResponse>, Status> {
Ok(Response::new(pb::HealthCheckResponse { healthy: true }))
}
}
#[tokio::main]
async fn main() -> Result<, Box<dyn std::error::Error>> {
// 1. Inicia serviço gRPC
let addr = "[::1]:50051".parse()?;
let servico = MeuServico::default();
// 2. Conecta ao etcd para service discovery
let mut etcd = Client::connect(["http://etcd:2379"], None).await?;
let lease = etcd.lease_grant(30, None).await?; // TTL de 30s
// 3. Registra instância
etcd.put(
format!("services/meuservico/{}", uuid::Uuid::new_v4()),
addr.to_string(),
Some(LeaseGrantOptions::new().with_lease(lease.id()))
).await?;
// 4. Health check periódico
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
etcd.lease_keep_alive(lease.id()).await.unwrap();
}
});
// 5. Shutdown graceful
Server::builder()
.add_service(pb::servico_server::ServicoServer::new(servico))
.serve_with_shutdown(addr, async {
signal::ctrl_c().await.unwrap();
println!("Desligando graciosamente...");
})
.await?;
Ok(())
}
Dependências necessárias (Cargo.toml):
[dependencies]
tokio = { version = "1.0", features = ["full"] }
tonic = "0.8"
etcd-client = "0.9"
prost = "0.11"
uuid = { version = "1.0", features = ["v4"] }
Protocol Buffer (meuservico.proto):
syntax = "proto3";
package meuservico;
service Servico {
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
}
message HealthCheckRequest {}
message HealthCheckResponse { bool healthy = 1; }
Checklist para Production-Ready
// Exemplo com OpenTelemetry
tracing_subscriber::fmt()
.with_max_level(Level::INFO)
.init();
let meter = global::meter("meuservico");
let request_counter = meter.u64_counter("requests").init();
Crates recomendadas: opentelemetry
, tracing
, metrics
// Configuração via etcd
let watch = etcd.watch("config/meuservico").await?;
tokio::spawn(async move {
while let Some(resp) = watch.message().await.unwrap() {
update_config(resp.events);
}
});
Alternativas: consul
, config-rs
, envconfig
tokio::select! {
_ = servidor => println!("Servidor finalizado"),
_ = signal::ctrl_c() => {
println!("Recebido SIGINT");
servidor.shutdown().await;
}
}
Padrões: Finalizar conexões, esperar tarefas, liberar recursos
🐞 Debugging em Sistemas Distribuídos com Rust
Cenários Problemáticos Comuns
1. Deadlock Clássico
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
let a = Arc::new(Mutex::new(1));
let b = Arc::new(Mutex::new(2));
let t1 = {
let a = Arc::clone(&a);
let b = Arc::clone(&b);
thread::spawn(move || {
let _ga = a.lock().unwrap(); // Lock A
thread::sleep(Duration::from_secs(1));
let _gb = b.lock().unwrap(); // Espera B (DEADLOCK)
println!("Thread 1 completou");
})
};
let t2 = thread::spawn(move || {
let _gb = b.lock().unwrap(); // Lock B
let _ga = a.lock().unwrap(); // Espera A
println!("Thread 2 completou");
});
t1.join().unwrap();
t2.join().unwrap();
}
🔍 Como Identificar:
- Programa "trava" sem erro aparente
- Uso de CPU cai para ~0%
- Logs param de ser emitidos
🛠️ Solução:
// Estratégia 1: Ordem consistente de locks
let _ga = a.lock().unwrap();
let _gb = b.lock().unwrap();
// Estratégia 2: Timeout
b.try_lock_for(Duration::from_millis(100))?
2. Condição de Corrida em Canais
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(1).unwrap();
tx.send(2).unwrap(); // ⚠️ Pode bloquear se consumidor parar
});
println!("{}", rx.recv().unwrap());
// Esqueceu de receber a segunda mensagem
}
Problema: Thread produtor pode ficar bloqueada indefinidamente se o consumidor não ler todas as mensagens.
Solução: Usar try_send()
ou buffer limitado:
let (tx, rx) = mpsc::sync_channel(10); // Buffer de 10 itens
Análise de Performance com Flamegraphs
Passo a Passo:
- Adicione as dependências:
[dependencies] pprof = { version = "0.11", features = ["flamegraph"] } tokio = { version = "1.0", features = ["rt-multi-thread"] }
- Instrumente seu código:
use pprof::ProfilerGuard; let guard = ProfilerGuard::new(100).unwrap(); // 100Hz // ... código sendo analisado ... if let Ok(report) = guard.report().build() { let file = std::fs::File::create("flamegraph.svg").unwrap(); report.flamegraph(file).unwrap(); }
- Execute e visualize:
cargo run --release xdg-open flamegraph.svg
Exemplo de Saída:

Pontos-chave para análise:
- 🔴 Blocos largos = hotspots de CPU
- 🟡 Chamadas de sistema = possíveis IO bounds
- 🟢 Muitas pequenas chamadas = possível thrashing
Exercício Prático: Debugging
Encontre o Problema:
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let data = Arc::new(Mutex::new(Vec::new()));
for i in 0..10 {
let data = Arc::clone(&data);
tokio::spawn(async move {
let mut guard = data.lock().await;
guard.push(i);
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
guard.pop(); // ⚠️ Onde está o erro?
});
}
}
🧩 Integração com o Ecossistema Rust
Ferramentas Essenciais para Produção
tokio-console
Monitoramento em tempo real de tasks, recursos e channels async
Configuração:
[dependencies]
tokio = { version = "1.0", features = ["full", "tracing"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
// main.rs
use tracing_subscriber::prelude::*;
#[tokio::main]
async fn main() {
// Configure o subscriber
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
"my_crate=info,tokio=debug",
))
.with(tracing_subscriber::fmt::layer())
.init();
// Sua aplicação aqui...
}
Uso:
# Instale o console
cargo install tokio-console
# Execute seu projeto com instrumentação
RUSTFLAGS="--cfg tokio_unstable" cargo run
# Em outro terminal:
tokio-console
pprof
Gera flamegraphs e análise de performance
Configuração:
[dependencies]
pprof = { version = "0.11", features = ["flamegraph"] }
Exemplo de Uso:
use pprof::ProfilerGuard;
fn main() {
let guard = ProfilerGuard::new(100).unwrap(); // 100Hz
// Código a ser analisado
heavy_computation();
if let Ok(report) = guard.report().build() {
let file = std::fs::File::create("flamegraph.svg").unwrap();
report.flamegraph(file).unwrap();
}
}
cargo-deny
Auditoria de dependências e verificação de segurança
Instalação:
cargo install cargo-deny
Configuração (deny.toml):
[advisories]
db-path = "~/.cargo/advisory-db"
vulnerability = "deny"
unmaintained = "warn"
[bans]
multiple-versions = "deny"
Uso:
# Verificar todas as regras
cargo deny check all
# Verificar atualizações
cargo deny check update
Template para Sistemas Distribuídos
Estrutura Recomendada
cargo new --lib meu-sistema-distribuido
cd meu-sistema-distribuido
.
├── Cargo.toml
├── src/
│ ├── lib.rs # Core lógico
│ ├── client.rs # Cliente gRPC/REST
│ ├── server.rs # Serviço principal
│ └── raft/ # Implementação consenso
├── protos/ # Definições Protocol Buffer
├── scripts/ # Scripts de deploy
└── tests/ # Testes de integração
Template Inicial (Cargo.toml)
[package]
name = "meu-sistema-distribuido"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.0", features = ["full"] }
tonic = "0.8" # gRPC
serde = { version = "1.0", features = ["derive"] }
tracing = "0.1" # Logging estruturado
[dev-dependencies]
tokio-test = "0.4"
testcontainers = "0.15" # Testes com containers
🚀 Comando para Iniciar Rápido
cargo new --template https://github.com/rust-distributed/rust-dist-template \
--bin meu-sistema-distribuido
Nota: Requer Git instalado. Template fictício para ilustração.
📚 Material Complementar
Mapa Mental de Conceitos
Legenda de Crates Rust:
- tonic: Implementação gRPC
- rdkafka: Cliente Apache Kafka
- async-raft: Implementação Raft
- tower: Middleware para serviços
Glossário de Termos Técnicos
Consenso
Definição: Mecanismo para múltiplos nós concordarem em um valor
Em Rust: async_raft::Raft
Sharding
Definição: Partição horizontal de dados
Em Rust: sled::Db::sharded()
Event Sourcing
Definição: Armazenamento de estados como sequência de eventos
Em Rust: cqrs-es::Aggregate
Backpressure
Definição: Controle de fluxo em sistemas assíncronos
Em Rust: tokio::sync::Semaphore
Idempotência
Definição: Operação com mesmo efeito quando aplicada múltiplas vezes
Em Rust: tower::idempotent::Idempotent
Two-Phase Commit (2PC)
Definição: Protocolo atômico de commit distribuído
Em Rust: tpc::Coordinator
Recursos Recomendados
📖 Livros
- "Rust Async Programming" (Oficial)
- "Distributed Systems with Rust"
- "Designing Data-Intensive Applications" (Cap. 8-9)
🛠️ Projetos de Referência
- TiKV (KV store distribuído)
- Linkerd Proxy
⚖️ Algoritmo Raft
O que é o Raft?
O Raft é um algoritmo de consenso distribuído projetado para ser fácil de entender, dividindo o problema em três subproblemas:
- Liderança: Um único líder é eleito para gerenciar a replicação de logs
- Replicação de Log: O líder garante que as entradas do log sejam replicadas na maioria dos nós
- Segurança: Garante que nenhum nó retorne um valor antigo após uma atualização
Comparado ao Paxos, Raft é mais compreensível e vem sendo adotado em sistemas como etcd
, Consul
e TiKV
.
Estados do Raft
- Líder: Responsável por todas as requisições de clientes e replicação para seguidores
- Seguidor: Nó passivo que apenas responde a requisições do líder
- Candidato: Estado temporário durante eleições
- Timeout de Eleição: Período aleatório (150-300ms) para evitar empates
[Seguidor] ↑ ↓ Timeout | Recebe voto majoritário ↓ ↑ [Candidato] → Votos suficientes → [Líder] ↓ Perde comunicação ←
Implementação Básica em Rust
Estrutura expandida de um nó Raft com máquina de estados:
use std::collections::HashMap;
use tokio::sync::mpsc;
#[derive(Debug, Clone)]
pub enum EstadoRaft {
Seguidor,
Candidato,
Lider,
}
pub struct NoRaft {
pub id: u64,
pub estado: EstadoRaft,
pub termo_atual: u64,
pub votos_recebidos: u64,
pub votou_em: Option,
pub nos_conhecidos: HashMap>,
}
impl NoRaft {
pub fn novo(id: u64) -> Self {
Self {
id,
estado: EstadoRaft::Seguidor,
termo_atual: 0,
votos_recebidos: 0,
votou_em: None,
nos_conhecidos: HashMap::new(),
}
}
pub async fn iniciar_eleicao(&mut self) {
self.estado = EstadoRaft::Candidato;
self.termo_atual += 1;
self.votos_recebidos = 1; // Vota em si mesmo
self.votou_em = Some(self.id);
// Envia RequestVote para outros nós
for (id, tx) in &self.nos_conhecidos {
if *id != self.id {
let mensagem = RaftMensagem::SolicitacaoVoto {
candidato_id: self.id,
termo: self.termo_atual,
};
tx.send(mensagem).await.unwrap();
}
}
}
}
#[derive(Debug)]
pub enum RaftMensagem {
SolicitacaoVoto { candidato_id: u64, termo: u64 },
VotoConcedido { votante_id: u64, termo: u64 },
AppendEntries { lider_id: u64, termo: u64 },
}
Esta implementação mostra a estrutura básica para eleições, incluindo:
- Transição de estados (Seguidor → Candidato → Líder)
- Lógica de votação
- Comunicação entre nós via channels
Exemplo Completo: Lógica de Heartbeat
Implementação do mecanismo de heartbeat que mantém a liderança:
use tokio::time::{interval, Duration};
impl NoRaft {
pub async fn executar_heartbeat(&self) {
let mut interval = interval(Duration::from_millis(100));
loop {
interval.tick().await;
if let EstadoRaft::Lider = self.estado {
for (id, tx) in &self.nos_conhecidos {
if *id != self.id {
let mensagem = RaftMensagem::AppendEntries {
lider_id: self.id,
termo: self.termo_atual,
};
tx.send(mensagem).await.unwrap();
}
}
}
}
}
}
O heartbeat é enviado periodicamente para:
- Manter a autoridade do líder
- Evitar novas eleições
- Replicar entradas do log (não mostrado neste exemplo simplificado)
🧰 Bibliotecas Úteis
Implementações completas e ferramentas para Raft em Rust:
raft-rs
- Implementação de referência da CoreOStokio
- Para concorrência e timers assíncronosserde
- Serialização de mensagens entre nósasync-raft
- Implementação async/await moderna
Exemplo de Cargo.toml para um projeto Raft:
[dependencies]
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
async-raft = "0.7"
thiserror = "1.0"
Padrões Comuns em Implementações Reais
Em sistemas de produção, você encontrará:
- Snapshotting: Compactação periódica do log
- Membership Changes: Adição/remoção dinâmica de nós
- Log Compaction: Para evitar crescimento infinito do log
- Pre-Vote: Extensão para evitar eleições desnecessárias
🔧 Exercícios Práticos
Exercício 1: Concorrência com Threads
Crie um programa que:
- Inicie 4 threads worker
- Cada thread processa itens de uma fila compartilhada
- Use
Arc<Mutex<Vec<i32>>>
para compartilhamento seguro
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Fila compartilhada protegida por Mutex
let fila = Arc::new(Mutex::new(vec![1, 2, 3, 4, 5, 6, 7, 8]));
let mut handles = vec![];
for id in 1..=4 {
let fila = Arc::clone(&fila);
let handle = thread::spawn(move || {
loop {
// Bloqueia o mutex para acessar a fila
let mut guard = fila.lock().unwrap();
if let Some(item) = guard.pop() {
println!("Thread {} processando item {}", id, item);
// Libera o lock antes do processamento demorado
drop(guard);
thread::sleep(std::time::Duration::from_millis(100));
} else {
break; // Fila vazia
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
Explicação:
Arc
permite compartilhamento seguro entre threadsMutex
garante acesso exclusivo à filadrop(guard)
libera o lock explicitamente- Cada thread processa itens até esvaziar a fila
Exercício 2: Comunicação com Channels Avançado
Crie um sistema com:
- 1 thread produtora que gera números aleatórios
- 3 threads worker que processam os números
- 1 thread consumidora que mostra resultados finais
use std::sync::mpsc;
use std::thread;
use rand::Rng;
fn main() {
let (tx_prod, rx_work) = mpsc::channel();
let (tx_res, rx_res) = mpsc::channel();
// Produtor
thread::spawn(move || {
let mut rng = rand::thread_rng();
for _ in 0..10 {
let num = rng.gen_range(1..100);
tx_prod.send(num).unwrap();
}
});
// Workers
for id in 1..=3 {
let rx_work = rx_work.clone();
let tx_res = tx_res.clone();
thread::spawn(move || {
for num in rx_work {
let resultado = num * id;
tx_res.send((id, num, resultado)).unwrap();
}
});
}
// Consumidor
for _ in 0..10 {
let (id, num, res) = rx_res.recv().unwrap();
println!("Worker {}: {} → {}", id, num, res);
}
}
Dependência necessária:
[dependencies]
rand = "0.8"
Fluxo do programa:
- Produtor gera números e envia para workers
- Cada worker multiplica pelo seu ID
- Consumidor coleta e exibe resultados
Exercício 3: Simulação de Eleição Raft
Implemente:
- 3 nós Raft (Seguidor, Candidato, Líder)
- Lógica de eleição com timeout
- Transição de estados baseada em votos
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone)]
enum Estado {
Seguidor,
Candidato,
Lider,
}
struct No {
id: u64,
estado: Estado,
termo: u64,
votos: u64,
}
impl No {
fn new(id: u64) -> Self {
Self {
id,
estado: Estado::Seguidor,
termo: 0,
votos: 0,
}
}
fn iniciar_eleicao(&mut self) {
self.estado = Estado::Candidato;
self.termo += 1;
self.votos = 1; // Vota em si mesmo
println!("Nó {} iniciou eleição (Termo {})", self.id, self.termo);
}
fn receber_voto(&mut self) {
self.votos += 1;
if self.votos >= 2 { // Simples maioria para 3 nós
self.estado = Estado::Lider;
println!("Nó {} tornou-se líder!", self.id);
}
}
}
fn main() {
let nos = Arc::new(Mutex::new(vec![
No::new(1),
No::new(2),
No::new(3),
]));
// Simulação de timeout para nó 1
let nos_clone = Arc::clone(&nos);
thread::spawn(move || {
thread::sleep(Duration::from_secs(2));
let mut guard = nos_clone.lock().unwrap();
guard[0].iniciar_eleicao();
});
// Nó 2 concede voto
let nos_clone = Arc::clone(&nos);
thread::spawn(move || {
thread::sleep(Duration::from_secs(3));
let mut guard = nos_clone.lock().unwrap();
guard[0].receber_voto();
});
// Nó 3 concede voto
let nos_clone = Arc::clone(&nos);
thread::spawn(move || {
thread::sleep(Duration::from_secs(4));
let mut guard = nos_clone.lock().unwrap();
guard[0].receber_voto();
});
thread::sleep(Duration::from_secs(5));
}
Conceitos implementados:
- Transição de estados do Raft
- Lógica de eleição simplificada
- Timeout simulado com thread::sleep
- Compartilhamento seguro com Arc
Exercício 4: Heartbeat com Tokio
Crie um serviço de heartbeat que:
- Envie pulsos periódicos a cada 500ms
- Use Tokio para tarefas assíncronas
- Pare após 5 heartbeats
use tokio::time::{interval, Duration};
#[tokio::main]
async fn main() {
let mut interval = interval(Duration::from_millis(500));
let mut count = 0;
while count < 5 {
interval.tick().await;
println!("Heartbeat {}", count + 1);
count += 1;
}
println!("Finalizado após 5 heartbeats");
}
Dependência necessária:
[dependencies]
tokio = { version = "1.0", features = ["full"] }
Pontos-chave:
interval.tick().await
pausa sem bloquear a thread- Tokio permite concorrência eficiente
- Modelo similar ao usado em sistemas reais de Raft
Exercício 5: Cliente HTTP Distribuído
Crie um cliente que:
- Faça requisições paralelas para múltiplos URLs
- Use Reqwest e Tokio
- Agregue os resultados
use reqwest;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box> {
let urls = vec![
"https://httpbin.org/get?query=1",
"https://httpbin.org/get?query=2",
"https://httpbin.org/get?query=3",
];
let mut tasks = vec![];
for url in urls {
let task = tokio::spawn(async move {
let resp = reqwest::get(url).await?
.text().await?;
Ok::(resp)
});
tasks.push(task);
}
for task in tasks {
match task.await? {
Ok(resp) => println!("Resposta: {}", resp),
Err(e) => eprintln!("Erro: {}", e),
}
}
Ok(())
}
Dependências:
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
Funcionamento:
- Cria uma tarefa assíncrona para cada URL
- Executa requisições em paralelo
- Coleta e exibe resultados na ordem de conclusão