pub(crate) mod clock;
mod error;
mod extension;
mod node;
mod nodes_selector;
mod rpc;
mod statistics;
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Display;
use std::net::SocketAddr;
use std::sync::atomic::Ordering;
use std::time::Duration;
use chitchat::transport::Transport;
use chitchat::FailureDetectorConfig;
pub use clock::Clock;
use datacake_rpc::{RpcService, Server};
pub use error::NodeError;
pub use extension::ClusterExtension;
use futures::StreamExt;
pub use node::{ChitchatNode, ClusterMember};
pub use nodes_selector::{
Consistency,
ConsistencyError,
DCAwareSelector,
NodeSelector,
NodeSelectorHandle,
Nodes,
};
pub use rpc::network::RpcNetwork;
pub use statistics::ClusterStatistics;
use tokio::sync::watch;
use tokio_stream::wrappers::WatchStream;
use tracing::info;
use crate::node::NodeMembership;
use crate::rpc::chitchat_transport::ChitchatTransport;
use crate::rpc::services::chitchat_impl::ChitchatService;
pub static DEFAULT_CLUSTER_ID: &str = "datacake-cluster-unknown";
pub static DEFAULT_DATA_CENTER: &str = "datacake-dc-unknown";
pub type NodeId = u8;
pub struct DatacakeNodeBuilder<S = DCAwareSelector> {
node_id: NodeId,
connection_cfg: ConnectionConfig,
cluster_id: String,
data_center: Cow<'static, str>,
node_selector: S,
}
impl<S> DatacakeNodeBuilder<S>
where
S: NodeSelector + Send + 'static,
{
pub fn new(
node_id: NodeId,
connection_cfg: ConnectionConfig,
) -> DatacakeNodeBuilder<DCAwareSelector> {
DatacakeNodeBuilder {
node_id,
connection_cfg,
cluster_id: DEFAULT_CLUSTER_ID.to_string(),
data_center: Cow::Borrowed(DEFAULT_DATA_CENTER),
node_selector: DCAwareSelector::default(),
}
}
pub fn with_node_selector<S2>(self, selector: S2) -> DatacakeNodeBuilder<S2> {
DatacakeNodeBuilder {
node_id: self.node_id,
connection_cfg: self.connection_cfg,
cluster_id: self.cluster_id,
data_center: self.data_center,
node_selector: selector,
}
}
pub fn with_cluster_id(mut self, cluster_id: impl Display) -> Self {
self.cluster_id = cluster_id.to_string();
self
}
pub fn with_data_center(mut self, dc: impl Display) -> Self {
self.data_center = Cow::Owned(dc.to_string());
self
}
pub async fn connect(self) -> Result<DatacakeNode, NodeError> {
let clock = Clock::new(self.node_id);
let statistics = ClusterStatistics::default();
let network = RpcNetwork::default();
let rpc_server = Server::listen(self.connection_cfg.listen_addr).await?;
let selector = nodes_selector::start_node_selector(
self.connection_cfg.public_addr,
self.data_center.clone(),
self.node_selector,
)
.await;
let cluster_info = ClusterInfo {
listen_addr: self.connection_cfg.listen_addr,
public_addr: self.connection_cfg.public_addr,
seed_nodes: self.connection_cfg.seed_nodes,
data_center: self.data_center.as_ref(),
};
let (node, transport) = connect_node(
self.node_id,
self.cluster_id.clone(),
clock.clone(),
network.clone(),
cluster_info,
&rpc_server,
statistics.clone(),
)
.await?;
let (tx, membership_changes) = watch::channel(MembershipChange::default());
tokio::spawn(watch_membership_changes(
self.node_id,
network.clone(),
selector.clone(),
statistics.clone(),
node.member_change_watcher(),
tx,
));
info!(
node_id = %self.node_id,
cluster_id = %self.cluster_id,
listen_addr = %self.connection_cfg.listen_addr,
"Datacake cluster connected."
);
Ok(DatacakeNode {
rpc_server,
node,
network,
clock,
selector,
membership_changes,
_transport: transport,
})
}
}
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub listen_addr: SocketAddr,
pub public_addr: SocketAddr,
pub seed_nodes: Vec<String>,
}
impl ConnectionConfig {
pub fn new(
listen_addr: SocketAddr,
public_addr: SocketAddr,
seeds: impl IntoIterator<Item = impl AsRef<str>>,
) -> Self {
Self {
listen_addr,
public_addr,
seed_nodes: seeds
.into_iter()
.map(|seed| seed.as_ref().to_string())
.collect(),
}
}
}
pub struct DatacakeNode {
node: ChitchatNode,
rpc_server: Server,
clock: Clock,
network: RpcNetwork,
selector: NodeSelectorHandle,
membership_changes: watch::Receiver<MembershipChange>,
_transport: Box<dyn Transport>,
}
impl DatacakeNode {
pub async fn shutdown(self) {
self.node.shutdown().await;
}
pub fn add_rpc_service<Svc>(&self, service: Svc)
where
Svc: RpcService + Send + Sync + 'static,
{
self.rpc_server.add_service(service);
}
pub async fn add_extension<Ext>(&self, ext: Ext) -> Result<Ext::Output, Ext::Error>
where
Ext: ClusterExtension,
{
ext.init_extension(self).await
}
#[inline]
pub fn statistics(&self) -> ClusterStatistics {
self.node.statistics()
}
#[inline]
pub fn clock(&self) -> &Clock {
&self.clock
}
#[inline]
pub fn network(&self) -> &RpcNetwork {
&self.network
}
#[inline]
pub fn me(&self) -> &ClusterMember {
self.node.me.as_ref()
}
#[inline]
pub fn membership_changes(&self) -> WatchStream<MembershipChange> {
WatchStream::new(self.membership_changes.clone())
}
#[inline]
pub async fn select_nodes(
&self,
consistency: Consistency,
) -> Result<Nodes, ConsistencyError> {
self.selector.get_nodes(consistency).await
}
#[inline]
pub async fn wait_for_nodes(
&self,
node_ids: impl AsRef<[NodeId]>,
timeout: Duration,
) -> Result<(), anyhow::Error> {
let nodes = node_ids.as_ref();
self.node
.wait_for_members(
|members| {
for id in nodes {
if !members.contains_key(id) {
return false;
}
}
true
},
timeout,
)
.await
}
#[inline]
pub fn handle(&self) -> DatacakeHandle {
DatacakeHandle {
me: self.node.me.clone(),
clock: self.clock.clone(),
network: self.network.clone(),
selector: self.selector.clone(),
statistics: self.statistics(),
membership_changes: self.membership_changes.clone(),
}
}
}
#[derive(Clone)]
pub struct DatacakeHandle {
me: Cow<'static, ClusterMember>,
clock: Clock,
network: RpcNetwork,
selector: NodeSelectorHandle,
statistics: ClusterStatistics,
membership_changes: watch::Receiver<MembershipChange>,
}
impl DatacakeHandle {
#[inline]
pub fn statistics(&self) -> ClusterStatistics {
self.statistics.clone()
}
#[inline]
pub fn clock(&self) -> &Clock {
&self.clock
}
#[inline]
pub fn network(&self) -> &RpcNetwork {
&self.network
}
#[inline]
pub fn membership_changes(&self) -> WatchStream<MembershipChange> {
WatchStream::new(self.membership_changes.clone())
}
#[inline]
pub fn me(&self) -> &ClusterMember {
self.me.as_ref()
}
#[inline]
pub async fn select_nodes(
&self,
consistency: Consistency,
) -> Result<Nodes, ConsistencyError> {
self.selector.get_nodes(consistency).await
}
}
#[derive(Clone, Default)]
pub struct MembershipChange {
pub joined: Vec<ClusterMember>,
pub left: Vec<ClusterMember>,
}
struct ClusterInfo<'a> {
listen_addr: SocketAddr,
public_addr: SocketAddr,
seed_nodes: Vec<String>,
data_center: &'a str,
}
async fn connect_node(
node_id: NodeId,
cluster_id: String,
clock: Clock,
network: RpcNetwork,
cluster_info: ClusterInfo<'_>,
server: &Server,
statistics: ClusterStatistics,
) -> Result<(ChitchatNode, Box<dyn Transport>), NodeError> {
let (chitchat_tx, chitchat_rx) = flume::bounded(1000);
let service = ChitchatService::new(clock.clone(), chitchat_tx);
server.add_service(service);
let transport =
ChitchatTransport::new(cluster_info.listen_addr, clock, network, chitchat_rx);
let me = ClusterMember::new(
node_id,
cluster_info.public_addr,
cluster_info.data_center.to_string(),
);
let node = ChitchatNode::connect(
me,
cluster_info.listen_addr,
cluster_id,
cluster_info.seed_nodes,
FailureDetectorConfig::default(),
&transport,
statistics,
)
.await?;
Ok((node, Box::new(transport)))
}
async fn watch_membership_changes(
self_node_id: NodeId,
network: RpcNetwork,
node_selector: NodeSelectorHandle,
statistics: ClusterStatistics,
mut changes: WatchStream<NodeMembership>,
membership_changes_tx: watch::Sender<MembershipChange>,
) {
let mut last_network_set = BTreeSet::new();
while let Some(members) = changes.next().await {
info!(
self_node_id = %self_node_id,
num_members = members.len(),
"Cluster membership has changed."
);
let mut membership_changes = MembershipChange::default();
let new_network_set = members
.iter()
.filter(|(node_id, _)| *node_id != &self_node_id)
.map(|(_, member)| (member.node_id, member.public_addr))
.collect::<BTreeSet<_>>();
{
let mut data_centers = BTreeMap::<Cow<'static, str>, Nodes>::new();
for member in members.values() {
let dc = Cow::Owned(member.data_center.clone());
data_centers.entry(dc).or_default().push(member.public_addr);
}
statistics
.num_data_centers
.store(data_centers.len() as u64, Ordering::Relaxed);
node_selector.set_nodes(data_centers).await;
}
for (node_id, addr) in last_network_set.difference(&new_network_set) {
info!(
self_node_id = %self_node_id,
target_node_id = %node_id,
target_addr = %addr,
"Node is no longer part of cluster."
);
network.disconnect(*addr);
if let Some(member) = members.get(node_id) {
membership_changes.left.push(member.clone());
}
}
for (node_id, addr) in new_network_set.difference(&last_network_set) {
info!(
self_node_id = %self_node_id,
target_node_id = %node_id,
target_addr = %addr,
"Node has connected to the cluster."
);
if let Some(member) = members.get(node_id) {
membership_changes.joined.push(member.clone());
}
}
let _ = membership_changes_tx.send(membership_changes);
last_network_set = new_network_set;
}
}