Skip to content

Commit 2367e14

Browse files
guozhihaoguozhihao
authored andcommitted
feat(raft): integrate raft consensus into the redis write path
Route cluster-mode write commands through Raft consensus by making the storage batch layer mode-aware, instead of per-command serialization. - storage: implement BinlogBatch capturing CF put/delete as BinlogEntry; Redis gains an injectable append_log_fn (OnceLock) so create_batch returns BinlogBatch in cluster mode and RocksBatch otherwise; the apply path (on_binlog_write) always uses create_rocks_batch to avoid recursive propose; Storage::set_append_log_fn propagates to all instances. - raft: add a lightweight LeaderGate trait implemented by RaftApp. - net: thread an optional LeaderGate to the executor and reject writes on non-leaders with MOVED / not-leader before execution. - server: in cluster mode, create the raft node, bridge storage->raft via an mpsc channel and a drain task calling client_write, inject append_log_fn (block_in_place around blocking_recv to avoid an async-context panic), and pass the LeaderGate to the network server. - cmd: remove the now-dead per-command to_binlog/needs_raft.
1 parent 28b1801 commit 2367e14

18 files changed

Lines changed: 396 additions & 106 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/src/lib.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,6 @@ pub trait Cmd: Send + Sync {
233233
None
234234
}
235235

236-
fn to_binlog(&self, _client: &Client) -> Option<conf::raft_type::Binlog> {
237-
None
238-
}
239-
240-
fn needs_raft(&self) -> bool {
241-
self.has_flag(CmdFlags::RAFT)
242-
}
243236
}
244237

245238
#[macro_export]

src/cmd/src/set.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ use storage::storage::Storage;
2323

2424
use crate::{Cmd, CmdFlags, CmdMeta};
2525
use crate::{impl_cmd_clone_box, impl_cmd_meta};
26-
use conf::raft_type::{Binlog, BinlogEntry, ColumnFamilyIndex as RaftCfIndex, OperateType};
27-
use storage::slot_indexer::key_to_slot_id;
2826

2927
#[derive(Clone, Default)]
3028
pub struct SetCmd {
@@ -74,24 +72,4 @@ impl Cmd for SetCmd {
7472
}
7573
}
7674
}
77-
78-
fn to_binlog(&self, client: &Client) -> Option<Binlog> {
79-
let key = client.key();
80-
let value = &client.argv()[2];
81-
82-
let slot_id = key_to_slot_id(&key) as u32;
83-
84-
let entry = BinlogEntry {
85-
cf_idx: RaftCfIndex::MetaCF as u32,
86-
op_type: OperateType::Put,
87-
key: key.clone(),
88-
value: Some(value.clone()),
89-
};
90-
91-
Some(Binlog {
92-
db_id: 0,
93-
slot_idx: slot_id,
94-
entries: vec![entry],
95-
})
96-
}
9775
}

src/net/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ executor = { path = "../executor" }
2121
runtime = { path = "../common/runtime" }
2222
thiserror = "2.0"
2323
parking_lot = "0.12"
24-
# raft = { path = "../raft" } # removed: src/raft no longer exists
24+
raft = { path = "../raft" }
2525

2626
[dev-dependencies]
2727
env_logger = "0.11"

src/net/src/executor_ext.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use executor::CmdExecutor;
2929
use log::{debug, error, warn};
3030
use resp::RespData;
3131
use runtime::DualRuntimeError;
32+
use cmd::CmdFlags;
3233

3334
use crate::network_execution::NetworkCmdExecution;
3435

@@ -58,6 +59,22 @@ impl CmdExecutorNetworkExt for CmdExecutor {
5859
return Ok(());
5960
}
6061

62+
// Cluster-mode leader gate: reject writes on non-leaders before any
63+
// command-specific setup runs.
64+
if let Some(gate) = exec.leader_gate.as_ref() {
65+
if exec.cmd.has_flag(CmdFlags::WRITE) && !gate.is_leader() {
66+
// Simplified redirect: Kiwi returns "MOVED <addr>" (no hash slot,
67+
// unlike Redis Cluster's "MOVED <slot> <ip:port>"). Clients are
68+
// expected to reconnect to the returned leader address directly.
69+
let reply = match gate.leader_resp_addr() {
70+
Some(addr) => format!("MOVED {addr}"),
71+
None => "ERR not leader".to_string(),
72+
};
73+
exec.client.set_reply(RespData::Error(reply.into()));
74+
return Ok(());
75+
}
76+
}
77+
6178
// Execute do_initial if needed
6279
if !exec.cmd.do_initial(&exec.client) {
6380
debug!("Command initial check failed for: {}", cmd_name);

src/net/src/handle.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,9 @@ pub async fn process_connection_with_storage_client(
129129
storage_client: Arc<StorageClient>,
130130
cmd_table: Arc<CmdTable>,
131131
executor: Arc<CmdExecutor>,
132+
leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
132133
) -> std::io::Result<()> {
133134
// Delegate to the network-aware connection handler
134-
crate::network_handle::process_network_connection(client, storage_client, cmd_table, executor)
135+
crate::network_handle::process_network_connection(client, storage_client, cmd_table, executor, leader_gate)
135136
.await
136137
}

src/net/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ impl ServerFactory {
5757
addr: Option<String>,
5858
runtime_manager: &RuntimeManager,
5959
requirepass: Option<String>,
60+
leader_gate: Option<Arc<dyn raft::leader_gate::LeaderGate>>,
6061
) -> Option<Box<dyn ServerTrait>> {
6162
match protocol.to_lowercase().as_str() {
62-
"tcp" => match Self::create_network_server(addr, runtime_manager, requirepass) {
63+
"tcp" => match Self::create_network_server(addr, runtime_manager, requirepass, leader_gate) {
6364
Ok(server) => Some(Box::new(server) as Box<dyn ServerTrait>),
6465
Err(e) => {
6566
log::error!("Failed to create NetworkServer: {}", e);
@@ -113,6 +114,7 @@ impl ServerFactory {
113114
addr: Option<String>,
114115
runtime_manager: &RuntimeManager,
115116
requirepass: Option<String>,
117+
leader_gate: Option<Arc<dyn raft::leader_gate::LeaderGate>>,
116118
) -> Result<NetworkServer, Box<dyn std::error::Error>> {
117119
// Get the storage client from RuntimeManager
118120
let runtime_storage_client = runtime_manager.storage_client().map_err(|e| {
@@ -132,6 +134,6 @@ impl ServerFactory {
132134
})));
133135
let executor = Arc::new(CmdExecutorBuilder::new().build());
134136

135-
NetworkServer::new(addr, storage_client, cmd_table, executor, requirepass)
137+
NetworkServer::new(addr, storage_client, cmd_table, executor, requirepass, leader_gate)
136138
}
137139
}

src/net/src/network_execution.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,7 @@ pub struct NetworkCmdExecution {
4040
pub client: Arc<Client>,
4141
/// The storage client for network-to-storage communication
4242
pub storage_client: Arc<StorageClient>,
43-
}
44-
45-
impl NetworkCmdExecution {
46-
/// Create a new NetworkCmdExecution
47-
pub fn new(cmd: Arc<dyn Cmd>, client: Arc<Client>, storage_client: Arc<StorageClient>) -> Self {
48-
Self {
49-
cmd,
50-
client,
51-
storage_client,
52-
}
53-
}
43+
/// Optional leadership gate; `None` in standalone mode. When `Some` and the
44+
/// command is a write on a non-leader, the executor replies `-MOVED` (Task 7).
45+
pub leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
5446
}

src/net/src/network_handle.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub async fn process_network_connection(
4949
storage_client: Arc<StorageClient>,
5050
cmd_table: Arc<CmdTable>,
5151
executor: Arc<CmdExecutor>,
52+
leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
5253
) -> std::io::Result<()> {
5354
let mut buf = vec![0; 4096]; // Increased buffer size for better performance
5455
let mut resp_parser = resp::RespParse::new(resp::RespVersion::RESP2);
@@ -71,6 +72,7 @@ pub async fn process_network_connection(
7172
storage_client.clone(),
7273
cmd_table.clone(),
7374
executor.clone(),
75+
leader_gate.clone(),
7476
).await;
7577
}
7678
return Ok(());
@@ -97,6 +99,7 @@ pub async fn process_network_connection(
9799
storage_client.clone(),
98100
cmd_table.clone(),
99101
executor.clone(),
102+
leader_gate.clone(),
100103
).await;
101104
pending_commands.clear();
102105
}
@@ -124,6 +127,7 @@ pub async fn process_network_connection(
124127
storage_client.clone(),
125128
cmd_table.clone(),
126129
executor.clone(),
130+
leader_gate.clone(),
127131
).await;
128132
pending_commands.clear();
129133
}
@@ -151,6 +155,7 @@ async fn handle_network_command(
151155
storage_client: Arc<StorageClient>,
152156
cmd_table: Arc<CmdTable>,
153157
executor: Arc<CmdExecutor>,
158+
leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
154159
) {
155160
// Convert the command name from &[u8] to a lowercase String for lookup
156161
let cmd_name = String::from_utf8_lossy(&client.cmd_name()).to_lowercase();
@@ -164,6 +169,7 @@ async fn handle_network_command(
164169
cmd: cmd.clone(),
165170
client: client.clone(),
166171
storage_client: storage_client.clone(),
172+
leader_gate: leader_gate.clone(),
167173
};
168174

169175
// Execute the command using the network-aware executor
@@ -340,6 +346,7 @@ async fn process_command_batch(
340346
storage_client: Arc<StorageClient>,
341347
cmd_table: Arc<CmdTable>,
342348
executor: Arc<CmdExecutor>,
349+
leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
343350
) {
344351
debug!("Processing command batch of {} commands", commands.len());
345352

@@ -371,6 +378,7 @@ async fn process_command_batch(
371378
storage_client.clone(),
372379
cmd_table.clone(),
373380
executor.clone(),
381+
leader_gate.clone(),
374382
)
375383
.await;
376384

src/net/src/network_server.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ pub struct NetworkServer {
6969
connection_pool: Arc<ConnectionPool<NetworkResources>>,
7070
/// Authentication password; when set, clients must AUTH before running commands
7171
requirepass: Option<String>,
72+
/// Optional leadership gate for cluster-mode write rejection
73+
leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
7274
}
7375

7476
impl NetworkServer {
@@ -81,6 +83,7 @@ impl NetworkServer {
8183
cmd_table: Arc<CmdTable>,
8284
executor: Arc<CmdExecutor>,
8385
requirepass: Option<String>,
86+
leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
8487
) -> Result<Self, Box<dyn Error>> {
8588
let pool_config = default_network_pool_config();
8689

@@ -91,6 +94,7 @@ impl NetworkServer {
9194
executor: executor.clone(),
9295
connection_pool: Arc::new(ConnectionPool::new(pool_config)),
9396
requirepass,
97+
leader_gate,
9498
})
9599
}
96100

@@ -102,6 +106,7 @@ impl NetworkServer {
102106
executor: Arc<CmdExecutor>,
103107
pool_config: PoolConfig,
104108
requirepass: Option<String>,
109+
leader_gate: Option<std::sync::Arc<dyn raft::leader_gate::LeaderGate>>,
105110
) -> Result<Self, Box<dyn Error>> {
106111
Ok(Self {
107112
addr: addr.unwrap_or("127.0.0.1:7379".to_string()),
@@ -110,6 +115,7 @@ impl NetworkServer {
110115
executor: executor.clone(),
111116
connection_pool: Arc::new(ConnectionPool::new(pool_config)),
112117
requirepass,
118+
leader_gate,
113119
})
114120
}
115121

@@ -175,6 +181,7 @@ impl ServerTrait for NetworkServer {
175181
let cmd_table = self.cmd_table.clone();
176182
let executor = self.executor.clone();
177183
let requirepass = self.requirepass.clone();
184+
let leader_gate = self.leader_gate.clone();
178185

179186
tokio::spawn(async move {
180187
// Get or create resources from the pool
@@ -214,6 +221,7 @@ impl ServerTrait for NetworkServer {
214221
pooled_resources.inner().storage_client.clone(),
215222
pooled_resources.inner().cmd_table.clone(),
216223
pooled_resources.inner().executor.clone(),
224+
leader_gate,
217225
)
218226
.await;
219227

@@ -258,6 +266,7 @@ mod tests {
258266
cmd_table,
259267
executor,
260268
None,
269+
None,
261270
);
262271

263272
assert!(server.is_ok());
@@ -291,6 +300,7 @@ mod tests {
291300
executor,
292301
pool_config,
293302
None,
303+
None,
294304
);
295305

296306
assert!(server.is_ok());
@@ -311,7 +321,7 @@ mod tests {
311321
let cmd_table = Arc::new(create_command_table(Arc::new(|| None)));
312322
let executor = Arc::new(CmdExecutorBuilder::new().build());
313323

314-
let server = NetworkServer::new(None, storage_client, cmd_table, executor, None);
324+
let server = NetworkServer::new(None, storage_client, cmd_table, executor, None, None);
315325

316326
assert!(server.is_ok());
317327
let server = server.unwrap();

0 commit comments

Comments
 (0)