r-nacos Architecture Explained: How ConfigActor and NamingActor Interact
The r-nacos architecture implements a dual-plane design where independent Actix actors—ConfigActor managing configuration via Raft consensus and NamingActor handling service discovery—share the same binary and coordinate during cluster snapshots while maintaining separate state machines.
The r-nacos project is a lightweight, Rust-based reimplementation of the Nacos platform, combining service discovery and configuration management into a single binary. Understanding the r-nacos architecture requires examining how its two primary actors—ConfigActor and NamingActor—operate as isolated state machines while sharing critical infrastructure for consensus and persistence.
High-Level Architecture Overview
The r-nacos architecture follows a single-binary, multi-node Raft cluster model. The system runs two parallel data planes within the same process, each backed by independent consensus mechanisms:
| Component | Data Store | Consensus Protocol | Primary Actor |
|---|---|---|---|
| Config Center | Configuration key-value pairs (data-id, group, tenant) | Raft (async-raft) with replicated log and snapshot | ConfigActor in src/config/core.rs |
| Naming Center | Service definitions and instance metadata (IP, port, health status) | Raft (separate log) with distro-style sync for gRPC/HTTP | NamingActor in src/naming/core.rs |
Both actors are initialized by the bean-factory framework at startup, as defined in src/starter.rs. The architecture diagram in book/src/architecture.md illustrates how these planes expose unified HTTP/gRPC interfaces while maintaining isolated persistence layers.
ConfigActor: The Configuration State Machine
Located in src/config/core.rs at line 397, ConfigActor encapsulates the configuration management plane as an Actix actor with its own Raft-backed state machine.
Core Responsibilities
ConfigActor maintains an in-memory cache of configuration data:
#[bean(inject)]
pub struct ConfigActor {
pub(crate) cache: HashMap<ConfigKey, ConfigValue>,
pub(crate) listener: ConfigListener,
raft: Option<Weak<NacosRaft>>,
// ...
}
The actor handles client subscription requests for push-based configuration updates and processes write operations through Raft commands (ConfigRaftCmd). When the leader receives a configuration change, it appends a log entry via set_config (around line 630), replicates it to followers, and updates the local cache upon commit.
Raft Integration and Snapshots
ConfigActor implements snapshot logic for Raft persistence through build_snapshot and load_snapshot_record. It also provides a transfer_backup method for exporting the entire configuration database.
NamingActor: The Service Discovery State Machine
Defined in src/naming/core.rs at line 82, NamingActor manages the service discovery plane with distinct consensus mechanisms for different data types.
Core Responsibilities
NamingActor maintains a map of services to their instances:
#[bean(inject)]
pub struct NamingActor {
pub(crate) service_map: HashMap<ServiceKey, Service>,
// ...
pub(crate) raft_router: Option<Arc<RaftRequestRoute>>,
}
The actor handles instance registration, heartbeat management, and client queries via NamingCmd messages. It runs periodic health checks through instance_time_out_heartbeat and performs empty-service cleanup.
Raft Routing and Distro Sync
Unlike ConfigActor, which uses direct Raft integration, NamingActor routes persistent instance changes through raft_router via process_naming_raft_request. For high-throughput ephemeral registrations, it employs a distro-style synchronization mechanism through build_distro_instances to replicate data across the cluster using gRPC/HTTP protocols.
How ConfigActor and NamingActor Interact
While ConfigActor and NamingActor operate as independent state machines, they cooperate at critical junctures in the r-nacos architecture.
Bootstrap and Dependency Injection
Both actors are instantiated together during application startup in src/starter.rs (lines 65-73):
let (index_manager, config_addr) = create_actor_at_thread2(index_manager, ConfigActor::new());
let naming_addr = NamingActor::create_at_new_system();
// ...
The bean-factory framework injects shared resources, such as the Raft router and consensus infrastructure, into both actors during this phase. This ensures that while the actors maintain separate concerns, they share the underlying consensus infrastructure.
Coordinated Snapshot Operations
The most significant interaction occurs during cluster snapshots. Both actors contribute their state to a shared SnapshotWriterActor defined in src/raft/filestore/raftsnapshot.rs.
When a snapshot is triggered, ConfigActor::build_snapshot (lines 555-564) writes configuration records:
// Inside ConfigActor::build_snapshot
writer.do_send(SnapshotWriterRequest::Record(config_record));
Simultaneously, NamingActor::build_snapshot (lines 1216-1244) writes permanent instance data:
// Inside NamingActor::build_snapshot
writer.do_send(SnapshotWriterRequest::Record(naming_record));
Because the same SnapshotWriterActor serializes both data streams, the resulting snapshot file contains a consistent view of both configuration and naming state. During node recovery or new node bootstrap, load_snapshot_record methods in both actors restore their respective states from this unified snapshot, ensuring the cluster resumes with synchronized configuration and service discovery data.
Summary
- r-nacos architecture implements dual independent state machines—
ConfigActorfor configuration andNamingActorfor service discovery—within a single Rust binary. - Both actors use Raft consensus:
ConfigActorembeds direct Raft integration viaasync-raft, whileNamingActorroutes persistent changes through araft_routerand uses distro sync for ephemeral instances. - Actors interact during bootstrap:
src/starter.rsinitializes both actors through the bean-factory framework, injecting shared consensus infrastructure. - Snapshot coordination ensures consistency: Both actors write to the same
SnapshotWriterActorduringbuild_snapshotoperations, producing unified cluster state files that restore both planes simultaneously. - Code locations:
ConfigActorresides insrc/config/core.rs(line 397),NamingActorinsrc/naming/core.rs(line 82), with snapshot coordination insrc/raft/filestore/raftsnapshot.rs.
Frequently Asked Questions
What is the difference between ConfigActor and NamingActor in r-nacos?
ConfigActor manages configuration key-value pairs using direct Raft consensus, maintaining an in-memory HashMap<ConfigKey, ConfigValue> cache and handling push-based client notifications. NamingActor manages service discovery instances and service definitions, using a separate Raft router for persistent data and a distro-style synchronization mechanism for high-throughput ephemeral instance registrations.
How do ConfigActor and NamingActor share the same Raft cluster?
Both actors participate in the same Raft cluster infrastructure but use separate state machines. ConfigActor embeds a Weak<NacosRaft> reference directly for consensus operations, while NamingActor uses an Arc<RaftRequestRoute> to route persistent changes. The src/starter.rs bootstrap process injects these shared consensus components into both actors during initialization, ensuring they communicate with the same underlying Raft nodes.
Why does r-nacos use a unified snapshot writer for both actors?
r-nacos uses a shared SnapshotWriterActor to ensure crash consistency across both configuration and naming data planes. When either ConfigActor::build_snapshot or NamingActor::build_snapshot is triggered, both serialize their state to the same writer, producing a single snapshot file. This guarantees that during node recovery or cluster expansion, both planes restore from the same point-in-time, preventing configuration and service discovery state desynchronization.
Can ConfigActor and NamingActor run independently or be separated into different processes?
Currently, r-nacos architecture packages both actors into a single binary designed for co-location. While they maintain independent state machines and could theoretically be separated, the current implementation in src/starter.rs initializes them together and relies on shared memory references for the snapshot writer and Raft infrastructure. Running them as separate processes would require significant refactoring of the consensus and persistence layers to use network protocols instead of Actix actor references.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →