惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 【当耐特】
A
About on SuperTechFans
Last Week in AI
Last Week in AI
雷峰网
雷峰网
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
博客园 - 叶小钗
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - Franky
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
Engineering at Meta
Engineering at Meta
B
Blog RSS Feed
The Cloudflare Blog
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
S
SegmentFault 最新的问题
Recent Announcements
Recent Announcements

Hacker News: Show HN

PurrrrrFocus: Pomodoro Timer App - App Store Workflow Engine — Multi-Step Orchestration for Bun RapidPhoto: Pro Photo Editor App - App Store GitHub - DheerG/swarms: Achieve extraordinary results with claude code across a variety of tasks SPICE simulation → oscilloscope → verification with Claude Code — Lucas Gerads Show HN: VCoding – A 5 MB native Windows IDE with no dynamic dependencies Show HN: LLMs don't hallucinate because they're bad at math, it's the format GitHub - Agent-FM/agentfm-core: AgentFM is a peer-to-peer network that turns everyday computers into a decentralized AI supercomputer. AgentFM lets you run massive AI workloads directly across a global mesh of idle CPUs and GPUs. Show HN: Tracking Top US Science Olympiad Alumni over Last 25 Years GitHub - Potarix/agent-hub: One place to talk to all your agents Show HN: Runtime security for AI agents(injection,tool abuse, data exfiltration) GitHub - dubeyKartikay/lazyspotify: Terminal Spotify client for macOS and Linux GitHub - the-banana-tool/king-louie: Easy to use GUI Personal AI Assistant. Win/Linux/Mac. Show HN I made my vacation rental bookable by AI agents–no Airbnb, 0% commission GitHub - basteez/jsf-autoreload: maven plugin to enable hot reload on jsf projects uvm32/hosts/host-gdbstub at main · ringtailsoftware/uvm32 GitHub - labsai/EDDI: Config-driven engine that turns JSON into production-grade AI agents. Multi-agent orchestration, 12+ LLM providers, MCP/A2A protocols, RAG, persistent memory, and enterprise compliance (EU AI Act, GDPR, HIPAA). Built on Quarkus. GitHub - glitchnsec/fortyone-oss: AI Executive Assistant Platform Quickstart | Alien GitHub - muxshed/shed: One stream in, or many. Every destination, simultaneously. No cloud middleman, no per-channel fees, no limits. GitHub - ocrbase-hq/ocrbase: 📄 PDF/IMG ->.MD/JSON Document OCR API for PaddleOCR and GLMOCR. Self-hostable. GitHub - impactjo/home-memory: MCP server that lets your AI assistant remember everything about your home. GitHub - Sets88/dbcls: DbCls is a powerful terminal database client that supports various databases GitHub - neptun2000/heor-agent-mcp GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh RollQuation: Math Puzzles - Apps on Google Play GitHub - dropbox/witchcraft Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis GitHub - opentalon/opentalon: OpenTalon is an open-source platform built from the ground up in Go as a robust alternative to OpenClaw LinkedIn™ 职位抓取工具 - Chrome 应用商店
GitHub - carterburn/ferris-ferry: Raft Implementation in ...
carterburn · 2026-05-26 · via Hacker News: Show HN

A Raft consensus library in Rust, built from the ground up following the Raft paper. Designed around separation of concerns: the consensus algorithm is isolated from I/O, networking, and application logic.

Architecture

Application (raft-kv (example binary provided), or any application needing distributed consensus)
        |
   RaftNode::propose() / AppliedEntry receiver
        |
   raft-event-loop (tokio::select! over ticks, proposals, messages)
        |
   RaftCore (sans-I/O, deterministic state machine)
       / \
Transport   Storage

Sans-I/O core: RaftCore is a pure synchronous state machine. It accepts inputs (ticks, messages, proposals) and returns Vec<Action> describing what I/O to perform. It never touches the network or disk directly. This makes it deterministic and testable without mocking.

Event loop driver: RaftNode spawns a single Tokio task that owns RaftCore, a Transport, and a Storage implementation. It runs a tokio::select! loop over a tick interval, inbound messages, and client proposals, translating Actions into real I/O.

Pluggable transport and storage: The Transport and Storage traits define how messages are sent/received and how state is persisted. Swap implementations without touching the consensus logic.

Crate Layout

Crate Type Description
raftcore lib Sans-I/O Raft algorithm: leader election, log replication, snapshotting
raft-event-loop lib Async driver, Transport/Storage trait definitions, RaftNode handle
raft-file-storage lib File-based Storage with atomic writes and crash-safe log framing
raft-tcp-transport lib TCP-based Transport with per-peer sender tasks and a connection listener
raft-test-utils lib In-memory Storage and Transport for integration testing
raft-kv bin Example distributed key-value store built on the library

Key Design Decisions

  • Actions, not callbacks: RaftCore returns actions (PersistMetadata, SendMessage, ApplyToStateMachine, etc.) rather than performing I/O internally. The event loop processes them in order, awaiting persistence before sending messages.
  • Single tick metronome: One fixed-interval timer drives all timeout logic. Election and heartbeat timeouts are internal to RaftCore via tick counting.
  • Proposal correlation: The event loop tracks pending proposals in a HashMap<u64, oneshot::Sender> keyed by log index, matching committed entries back to the client that proposed them.
  • Snapshot compaction: The event loop initiates snapshots (not the application). It requests serialized state from the application via a oneshot channel, then calls prepare_snapshot() / complete_snapshot() on RaftCore synchronously -- guaranteeing index/data alignment since no entries can be applied in between.
  • No-op on election: The leader appends an empty log entry on election win (Section 8) to ensure previous-term entries can be committed. The event loop filters these out and does not deliver them to the application.

Implementing Your Own Transport

Implement the Transport trait from raft-event-loop:

use raft_event_loop::types::Transport;
use raftcore::types::{Message, NodeId};

pub struct MyTransport { /* ... */ }

impl Transport for MyTransport {
    type Address = String; // whatever your addressing scheme is

    async fn send(&self, node: NodeId, message: Message) {
        // Fire-and-forget: serialize `message` and send it to `node`.
        // If delivery fails, silently drop it -- Raft handles retries.
    }

    async fn recv(&mut self) -> Message {
        // Return the next inbound message.
        // IMPORTANT: This must be cancel-safe (backed by a channel, not a raw socket read)
        // because it runs inside tokio::select!
    }
}

Key points:

  • One Transport instance manages all peer connections (not one per peer).
  • send() takes &self -- use interior mutability or channels if needed.
  • recv() must be cancel-safe. Back it with an mpsc channel.

Implementing Your Own Storage

Implement the Storage trait from raft-event-loop:

use raft_event_loop::types::{Storage, PersistedMetadata, PersistedLogAddendum, Snapshot};
use raftcore::types::LogEntry;

pub struct MyStorage { /* ... */ }

impl Storage for MyStorage {
    async fn store_metadata(&self, metadata: PersistedMetadata) -> std::io::Result<()> {
        // Persist term and voted_for. Must be durable when this returns.
    }

    async fn restore_metadata(&self) -> std::io::Result<Option<PersistedMetadata>> {
        // Return None on first boot, Some on recovery.
    }

    async fn store_log_entries(&self, addendum: PersistedLogAddendum) -> std::io::Result<()> {
        // Append entries starting at addendum.start_index.
        // If start_index overlaps existing entries, overwrite from that point (conflict resolution).
        // Must be durable when this returns.
    }

    async fn restore_log_entries(&self) -> std::io::Result<Option<Vec<LogEntry>>> {
        // Return None if no log file exists. Return Some(vec![]) if the file exists but is empty.
    }

    async fn store_snapshot(&self, snapshot: Snapshot) -> std::io::Result<()> {
        // Atomically persist the snapshot (write-to-temp, rename).
    }

    async fn restore_snapshot(&self) -> std::io::Result<Option<Snapshot>> {
        // Return None if no snapshot exists.
    }

    async fn retrieve_snapshot_bytes(&self) -> std::io::Result<Vec<u8>> {
        // Return the raw snapshot data for sending to a follower via InstallSnapshot.
        // Error if no snapshot exists (this should never be called without one).
    }

    async fn truncate_log(&self) -> std::io::Result<()> {
        // Clear the log file (called after snapshot compaction or InstallSnapshot).
    }
}

Key points:

  • All methods take &self -- use interior mutability if needed.
  • store_metadata and store_log_entries must be durable (fsync) before returning. The event loop relies on this for crash safety.
  • restore_* methods return Option: None means fresh start, Err means I/O failure (unrecoverable).
  • For crash safety on metadata and snapshots, use the write-to-temp-then-rename pattern.
  • For the log file, use length-prefixed frames so partial writes from crashes can be detected and discarded on recovery.

Running raft-kv

Build the project:

Start a 3-node cluster (in separate terminals):

# Node 1
RUST_LOG=info cargo run -p raft-kv -- \
  --nodes 127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003 \
  --id 1 --addr 0.0.0.0:3001 --directory /tmp/raft-kv/node1

# Node 2
RUST_LOG=info cargo run -p raft-kv -- \
  --nodes 127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003 \
  --id 2 --addr 0.0.0.0:3002 --directory /tmp/raft-kv/node2

# Node 3
RUST_LOG=info cargo run -p raft-kv -- \
  --nodes 127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003 \
  --id 3 --addr 0.0.0.0:3003 --directory /tmp/raft-kv/node3

Interact with the cluster (requests must go to the current leader; try each node):

# Set a key
curl -X POST http://localhost:3001/key \
  -H 'Content-Type: application/json' \
  -d '{"key": "hello", "value": "world"}'

# Get a key
curl http://localhost:3001/key/hello

# Delete a key
curl -X DELETE http://localhost:3001/key/hello

Try killing a node and watching the cluster elect a new leader. Set keys, restart the killed node, and verify it catches up via log replication or snapshot transfer.