refactor(actor): implement Raft consensus algorithm for cluster leader election

- Add voting mechanism with term tracking and vote persistence
- Implement election triggering logic with majority vote counting
- Add primary/replica role transition handling with state management
- Integrate health check failure detection for automatic elections
- Refactor actor messaging system for distributed coordination
- Update repository registration to query cluster for existing primary
- Add broadcast mechanism for role change notifications
- Implement proper term comparison and duplicate request filtering
- Upgrade dependency versions including tokio-util for async utilities
- Optimize code formatting and line wrapping for improved readability
- Remove redundant blank lines and improve code structure consistency
- Enhance error logging and trace information for debugging purposes
This commit is contained in:
zhenyi
2026-06-10 12:35:10 +08:00
parent ab32e8826e
commit 9a0c26e5f6
40 changed files with 1184 additions and 449 deletions
+52 -27
View File
@@ -1,11 +1,9 @@
#[cfg(test)]
mod cluster_test {
use gitks::pb::{
repository_service_client::RepositoryServiceClient,
branch_service_client::BranchServiceClient,
RepositoryHeader, InitRepositoryRequest, CreateBranchRequest,
GetRepositoryRequest,
ObjectSelector, ObjectName, object_selector,
CreateBranchRequest, GetRepositoryRequest, InitRepositoryRequest, ObjectName,
ObjectSelector, RepositoryHeader, branch_service_client::BranchServiceClient,
object_selector, repository_service_client::RepositoryServiceClient,
};
const N1: &str = "http://localhost:50051";
@@ -13,49 +11,76 @@ mod cluster_test {
const N3: &str = "http://localhost:50053";
fn hdr(path: &str) -> RepositoryHeader {
RepositoryHeader { storage_name: String::new(), relative_path: path.into(), storage_path: String::new() }
RepositoryHeader {
storage_name: String::new(),
relative_path: path.into(),
storage_path: String::new(),
}
}
#[tokio::test]
async fn test_cluster_routing() {
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let repo = format!("cluster-test-{ts}");
// ── Init via node1 ──
let mut n1 = RepositoryServiceClient::connect(N1).await.unwrap();
let r = n1.init_repository(tonic::Request::new(InitRepositoryRequest {
repository: Some(hdr(&repo)), bare: true, object_format: 0, initial_branch: "main".into(),
})).await.unwrap().into_inner();
let r = n1
.init_repository(tonic::Request::new(InitRepositoryRequest {
repository: Some(hdr(&repo)),
bare: true,
object_format: 0,
initial_branch: "main".into(),
}))
.await
.unwrap()
.into_inner();
println!("✅ n1 init: bare={}", r.bare);
// ── Read via node2 (should forward to PRIMARY n1) ──
let mut n2 = RepositoryServiceClient::connect(N2).await.unwrap();
let r2 = n2.get_repository(tonic::Request::new(GetRepositoryRequest {
repository: Some(hdr(&repo)),
})).await.unwrap().into_inner();
let r2 = n2
.get_repository(tonic::Request::new(GetRepositoryRequest {
repository: Some(hdr(&repo)),
}))
.await
.unwrap()
.into_inner();
println!("✅ n2 get routed→primary: bare={}", r2.bare);
// ── Read via node3 ──
let mut n3 = RepositoryServiceClient::connect(N3).await.unwrap();
let r3 = n3.get_repository(tonic::Request::new(GetRepositoryRequest {
repository: Some(hdr(&repo)),
})).await.unwrap().into_inner();
let r3 = n3
.get_repository(tonic::Request::new(GetRepositoryRequest {
repository: Some(hdr(&repo)),
}))
.await
.unwrap()
.into_inner();
println!("✅ n3 get routed→primary: bare={}", r3.bare);
// ── Write (create branch) via node2 → primary ──
let mut n2b = BranchServiceClient::connect(N2).await.unwrap();
let b = n2b.create_branch(tonic::Request::new(CreateBranchRequest {
repository: Some(hdr(&repo)),
name: "feature/x".into(),
start_point: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
force: false,
})).await;
let b = n2b
.create_branch(tonic::Request::new(CreateBranchRequest {
repository: Some(hdr(&repo)),
name: "feature/x".into(),
start_point: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
force: false,
}))
.await;
match b {
Ok(branch) => println!("✅ n2 create-branch routed→primary: name={}", branch.into_inner().name),
Ok(branch) => println!(
"✅ n2 create-branch routed→primary: name={}",
branch.into_inner().name
),
Err(e) => println!("⚠️ create-branch: {e} (expected — empty repo has no commits)"),
}