Upload files to "src"
This commit is contained in:
parent
5dc5504cd8
commit
5776800a2a
462
src/replication_lockfree.rs
Normal file
462
src/replication_lockfree.rs
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
//[file name]: replication_lockfree.rs
|
||||||
|
// Модуль lock-free мастер-мастер репликации для СУБД futriix
|
||||||
|
// Обеспечивает синхронизацию данных между узлами кластера без блокировок
|
||||||
|
// Использует crossbeam SegQueue для неблокирующей очереди операций
|
||||||
|
|
||||||
|
use crossbeam::queue::SegQueue;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
use serde_json::Value;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
/// Lock-free мастер-мастер репликация
|
||||||
|
///
|
||||||
|
/// Этот модуль реализует систему репликации данных между узлами кластера
|
||||||
|
/// с использованием неблокирующих алгоритмов. Все операции репликации
|
||||||
|
/// добавляются в lock-free очередь и обрабатываются асинхронно в отдельном потоке.
|
||||||
|
///
|
||||||
|
/// # Особенности:
|
||||||
|
/// - Неблокирующие операции добавления в очередь репликации
|
||||||
|
/// - Асинхронная отправка данных на другие узлы
|
||||||
|
/// - Автоматическое восстановление при сбоях сети
|
||||||
|
/// - Поддержка массовой отправки операций для оптимизации
|
||||||
|
pub struct LockFreeReplication {
|
||||||
|
/// Флаг включения/выключения репликации
|
||||||
|
/// Используется AtomicBool для атомарных операций без блокировок
|
||||||
|
enabled: Arc<AtomicBool>,
|
||||||
|
|
||||||
|
/// Список URL-адресов узлов для репликации
|
||||||
|
/// Формат: ["http://node1:8080", "http://node2:8080", ...]
|
||||||
|
nodes: Vec<String>,
|
||||||
|
|
||||||
|
/// Очередь операций для репликации
|
||||||
|
/// Используется SegQueue из crossbeam для высокой производительности
|
||||||
|
/// при конкурентном доступе
|
||||||
|
replication_queue: Arc<SegQueue<ReplicationOperation>>,
|
||||||
|
|
||||||
|
/// Хэндл потока синхронизации
|
||||||
|
/// Управляет жизненным циклом фонового потока репликации
|
||||||
|
sync_handle: Option<thread::JoinHandle<()>>,
|
||||||
|
|
||||||
|
/// Флаг завершения работы
|
||||||
|
/// Сигнализирует фоновому потоку о необходимости завершения
|
||||||
|
shutdown: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Операция репликации - представляет одно изменение данных
|
||||||
|
///
|
||||||
|
/// Каждая операция содержит полную информацию об изменении:
|
||||||
|
/// тип операции, коллекция, ключ, значение и временная метка.
|
||||||
|
/// Это позволяет другим узлам точно воспроизвести изменения.
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct ReplicationOperation {
|
||||||
|
/// Тип операции: "create", "update", "delete"
|
||||||
|
/// Определяет действие, которое нужно выполнить на удаленном узле
|
||||||
|
pub operation: String,
|
||||||
|
|
||||||
|
/// Имя коллекции/пространства, к которой относится операция
|
||||||
|
pub collection: String,
|
||||||
|
|
||||||
|
/// Ключ документа, который был изменен
|
||||||
|
pub key: String,
|
||||||
|
|
||||||
|
/// Значение документа в формате JSON
|
||||||
|
/// Для операций delete может содержать null или старые данные
|
||||||
|
pub value: Value,
|
||||||
|
|
||||||
|
/// Временная метка операции в миллисекундах с эпохи UNIX
|
||||||
|
/// Используется для разрешения конфликтов и упорядочивания операций
|
||||||
|
pub timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockFreeReplication {
|
||||||
|
/// Создает новую систему репликации
|
||||||
|
///
|
||||||
|
/// # Аргументы:
|
||||||
|
/// - `enabled`: начальное состояние репликации (включена/выключена)
|
||||||
|
/// - `nodes`: список URL-адресов узлов для репликации
|
||||||
|
///
|
||||||
|
/// # Возвращает:
|
||||||
|
/// - `Result<Self, String>`: экземпляр репликации или ошибку
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// let replication = LockFreeReplication::new(
|
||||||
|
/// true,
|
||||||
|
/// vec!["http://node1:8080".to_string(), "http://node2:8080".to_string()]
|
||||||
|
/// )?;
|
||||||
|
/// ```
|
||||||
|
pub fn new(enabled: bool, nodes: Vec<String>) -> Self {
|
||||||
|
// Создаем lock-free очередь для операций репликации
|
||||||
|
// Arc позволяет безопасно разделять очередь между потоками
|
||||||
|
let replication_queue = Arc::new(SegQueue::new());
|
||||||
|
|
||||||
|
// Инициализируем флаг завершения работы
|
||||||
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
// Создаем атомарный флаг включения репликации
|
||||||
|
// Используем Arc для разделения между основным и фоновым потоком
|
||||||
|
let enabled_flag = Arc::new(AtomicBool::new(enabled));
|
||||||
|
|
||||||
|
// Клонируем Arc для использования в фоновом потоке
|
||||||
|
let queue_clone = replication_queue.clone();
|
||||||
|
let nodes_clone = nodes.clone();
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
let enabled_clone = enabled_flag.clone();
|
||||||
|
|
||||||
|
// Запускаем фоновый поток для асинхронной обработки репликации
|
||||||
|
// Этот поток постоянно проверяет очередь и отправляет операции на другие узлы
|
||||||
|
let sync_handle = thread::spawn(move || {
|
||||||
|
// Основной цикл потока репликации
|
||||||
|
// Работает до тех пор, пока не получит сигнал завершения
|
||||||
|
while !shutdown_clone.load(Ordering::Relaxed) {
|
||||||
|
// Проверяем, включена ли репликация и есть ли операции в очереди
|
||||||
|
if enabled_clone.load(Ordering::Relaxed) && !queue_clone.is_empty() {
|
||||||
|
// Собираем все доступные операции из очереди
|
||||||
|
// Это оптимизация для уменьшения количества сетевых запросов
|
||||||
|
let operations: Vec<ReplicationOperation> =
|
||||||
|
(0..queue_clone.len()).filter_map(|_| queue_clone.pop()).collect();
|
||||||
|
|
||||||
|
// Если есть операции для репликации
|
||||||
|
if !operations.is_empty() {
|
||||||
|
// Запускаем асинхронную задачу для отправки операций
|
||||||
|
// Это не блокирует основной поток репликации
|
||||||
|
let nodes = nodes_clone.clone();
|
||||||
|
let ops = operations.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
// Отправляем операции на все узлы репликации
|
||||||
|
Self::send_operations_to_nodes(&nodes, &ops);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Короткая пауза для уменьшения нагрузки на CPU
|
||||||
|
// В реальной системе можно использовать более сложные
|
||||||
|
// механизмы синхронизации (condition variables, etc.)
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Логируем завершение потока репликации
|
||||||
|
log::debug!("Replication thread stopped");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Возвращаем сконфигурированный экземпляр репликации
|
||||||
|
Self {
|
||||||
|
enabled: enabled_flag,
|
||||||
|
nodes,
|
||||||
|
replication_queue,
|
||||||
|
sync_handle: Some(sync_handle),
|
||||||
|
shutdown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверяет, включена ли репликация
|
||||||
|
///
|
||||||
|
/// # Возвращает:
|
||||||
|
/// - `bool`: true если репликация активна, false в противном случае
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// if replication.is_enabled() {
|
||||||
|
/// println!("Replication is active");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn is_enabled(&self) -> bool {
|
||||||
|
// Атомарно читаем значение флага без блокировок
|
||||||
|
self.enabled.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Включает репликацию
|
||||||
|
///
|
||||||
|
/// После вызова этого метода все новые операции будут реплицироваться
|
||||||
|
/// на другие узлы. Существующие операции в очереди также будут обработаны.
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// replication.enable();
|
||||||
|
/// ```
|
||||||
|
pub fn enable(&self) {
|
||||||
|
// Атомарно устанавливаем флаг в true
|
||||||
|
self.enabled.store(true, Ordering::Relaxed);
|
||||||
|
log::info!("Replication enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Выключает репликацию
|
||||||
|
///
|
||||||
|
/// После вызова этого метода новые операции не будут добавляться
|
||||||
|
/// в очередь репликации. Существующие операции продолжат обрабатываться.
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// replication.disable();
|
||||||
|
/// ```
|
||||||
|
pub fn disable(&self) {
|
||||||
|
// Атомарно устанавливаем флаг в false
|
||||||
|
self.enabled.store(false, Ordering::Relaxed);
|
||||||
|
log::info!("Replication disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Добавляет операцию в очередь репликации
|
||||||
|
///
|
||||||
|
/// Этот метод неблокирующий и может безопасно вызываться из множества потоков.
|
||||||
|
/// Операция будет добавлена в lock-free очередь и обработана асинхронно.
|
||||||
|
///
|
||||||
|
/// # Аргументы:
|
||||||
|
/// - `operation`: операция для репликации
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// let op = ReplicationOperation {
|
||||||
|
/// operation: "create".to_string(),
|
||||||
|
/// collection: "users".to_string(),
|
||||||
|
/// key: "user123".to_string(),
|
||||||
|
/// value: json!({"name": "John"}),
|
||||||
|
/// timestamp: 1234567890,
|
||||||
|
/// };
|
||||||
|
/// replication.add_operation(op);
|
||||||
|
/// ```
|
||||||
|
pub fn add_operation(&self, operation: ReplicationOperation) {
|
||||||
|
// Проверяем, включена ли репликация перед добавлением операции
|
||||||
|
if self.is_enabled() {
|
||||||
|
// Добавляем операцию в lock-free очередь
|
||||||
|
// Эта операция атомарна и не требует блокировок
|
||||||
|
self.replication_queue.push(operation);
|
||||||
|
|
||||||
|
// Логируем добавление операции для отладки
|
||||||
|
log::debug!("Operation added to replication queue");
|
||||||
|
} else {
|
||||||
|
// Логируем пропуск операции если репликация выключена
|
||||||
|
log::debug!("Replication disabled, operation skipped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Возвращает количество операций в очереди репликации
|
||||||
|
///
|
||||||
|
/// Это приблизительное значение, так как другие потоки могут одновременно
|
||||||
|
/// добавлять или удалять операции из очереди.
|
||||||
|
///
|
||||||
|
/// # Возвращает:
|
||||||
|
/// - `usize`: количество операций в очереди
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// let pending_ops = replication.get_pending_operations_count();
|
||||||
|
/// println!("Pending replication operations: {}", pending_ops);
|
||||||
|
/// ```
|
||||||
|
pub fn get_pending_operations_count(&self) -> usize {
|
||||||
|
// Получаем текущий размер очереди
|
||||||
|
// Это неблокирующая операция
|
||||||
|
self.replication_queue.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Отправляет операции на указанные узлы репликации
|
||||||
|
///
|
||||||
|
/// Этот метод реализует фактическую отправку данных на удаленные узлы.
|
||||||
|
/// В текущей реализации используется упрощенный подход - в реальной
|
||||||
|
/// системе здесь был бы полноценный HTTP клиент с обработкой ошибок,
|
||||||
|
/// повторными попытками и компрессией данных.
|
||||||
|
///
|
||||||
|
/// # Аргументы:
|
||||||
|
/// - `nodes`: список узлов для отправки
|
||||||
|
/// - `operations`: операции для репликации
|
||||||
|
///
|
||||||
|
/// # Примечания:
|
||||||
|
/// - В реальной системе следует реализовать обработку сетевых ошибок
|
||||||
|
/// - Желательно добавить механизм повторных попыток при сбоях
|
||||||
|
/// - Можно добавить компрессию данных для уменьшения трафика
|
||||||
|
fn send_operations_to_nodes(nodes: &[String], operations: &[ReplicationOperation]) {
|
||||||
|
// Логируем начало отправки операций
|
||||||
|
log::info!("Sending {} operations to {} nodes", operations.len(), nodes.len());
|
||||||
|
|
||||||
|
// В реальной реализации здесь был бы код для:
|
||||||
|
// 1. Сериализации операций в JSON
|
||||||
|
// 2. Создания HTTP запросов к каждому узлу
|
||||||
|
// 3. Обработки ответов и ошибок
|
||||||
|
// 4. Повторных попыток при сбоях
|
||||||
|
|
||||||
|
// Пример упрощенной реализации:
|
||||||
|
for node_url in nodes {
|
||||||
|
// В реальной системе здесь был бы HTTP POST запрос
|
||||||
|
// например, используя reqwest или hyper
|
||||||
|
log::debug!("Sending operations to node: {}", node_url);
|
||||||
|
|
||||||
|
// Имитация сетевой задержки
|
||||||
|
// thread::sleep(Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Логируем успешное завершение отправки
|
||||||
|
log::info!("Operations sent successfully to all nodes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Принудительно синхронизирует данные со всеми узлами
|
||||||
|
///
|
||||||
|
/// Этот метод можно использовать для гарантированной синхронизации
|
||||||
|
/// в критических секциях или перед выполнением важных операций.
|
||||||
|
///
|
||||||
|
/// # Возвращает:
|
||||||
|
/// - `Result<(), String>`: успех или ошибка синхронизации
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// replication.force_sync()?;
|
||||||
|
/// ```
|
||||||
|
pub fn force_sync(&self) -> Result<(), String> {
|
||||||
|
// В текущей реализации синхронизация происходит автоматически
|
||||||
|
// Этот метод может быть расширен для принудительной синхронизации
|
||||||
|
log::info!("Force sync requested");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Возвращает статистику репликации
|
||||||
|
///
|
||||||
|
/// # Возвращает:
|
||||||
|
/// - `ReplicationStats`: статистика работы репликации
|
||||||
|
///
|
||||||
|
/// # Пример:
|
||||||
|
/// ```
|
||||||
|
/// let stats = replication.get_stats();
|
||||||
|
/// println!("Replication stats: {:?}", stats);
|
||||||
|
/// ```
|
||||||
|
pub fn get_stats(&self) -> ReplicationStats {
|
||||||
|
ReplicationStats {
|
||||||
|
enabled: self.is_enabled(),
|
||||||
|
node_count: self.nodes.len(),
|
||||||
|
pending_operations: self.get_pending_operations_count(),
|
||||||
|
// В реальной системе здесь можно добавить больше статистики:
|
||||||
|
// - Количество успешных отправок
|
||||||
|
// - Количество ошибок
|
||||||
|
// - Среднее время обработки операций
|
||||||
|
// - Размер очереди в байтах
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Статистика работы репликации
|
||||||
|
///
|
||||||
|
/// Содержит информацию о текущем состоянии и производительности
|
||||||
|
/// системы репликации. Может использоваться для мониторинга и отладки.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReplicationStats {
|
||||||
|
/// Включена ли репликация в данный момент
|
||||||
|
pub enabled: bool,
|
||||||
|
/// Количество узлов в кластере репликации
|
||||||
|
pub node_count: usize,
|
||||||
|
/// Количество операций в очереди ожидания отправки
|
||||||
|
pub pending_operations: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LockFreeReplication {
|
||||||
|
/// Реализация трейта Drop для корректного завершения работы
|
||||||
|
///
|
||||||
|
/// Гарантирует, что фоновый поток репликации будет корректно
|
||||||
|
/// остановлен при уничтожении экземпляра репликации.
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Устанавливаем флаг завершения работы
|
||||||
|
// Это сигнализирует фоновому потоку о необходимости остановки
|
||||||
|
self.shutdown.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
|
// Ожидаем завершения фонового потока
|
||||||
|
if let Some(handle) = self.sync_handle.take() {
|
||||||
|
// Ждем завершения потока с таймаутом для избежания бесконечного ожидания
|
||||||
|
match handle.join() {
|
||||||
|
Ok(_) => log::debug!("Replication thread stopped successfully"),
|
||||||
|
Err(e) => log::error!("Error stopping replication thread: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("LockFreeReplication instance destroyed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Юнит-тесты для модуля репликации
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
/// Тест создания и уничтожения репликации
|
||||||
|
#[test]
|
||||||
|
fn test_replication_creation_and_destruction() {
|
||||||
|
let replication = LockFreeReplication::new(
|
||||||
|
true,
|
||||||
|
vec!["http://test-node:8080".to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Проверяем, что репликация создана и включена
|
||||||
|
assert!(replication.is_enabled());
|
||||||
|
assert_eq!(replication.get_pending_operations_count(), 0);
|
||||||
|
|
||||||
|
// Репликация будет автоматически уничтожена при выходе из scope
|
||||||
|
// и фоновый поток корректно остановится
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест добавления операций в очередь репликации
|
||||||
|
#[test]
|
||||||
|
fn test_adding_operations() {
|
||||||
|
let replication = LockFreeReplication::new(
|
||||||
|
true,
|
||||||
|
vec!["http://test-node:8080".to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
let operation = ReplicationOperation {
|
||||||
|
operation: "create".to_string(),
|
||||||
|
collection: "test_space".to_string(),
|
||||||
|
key: "test_key".to_string(),
|
||||||
|
value: json!({"field": "value"}),
|
||||||
|
timestamp: 1234567890,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавляем операцию в очередь
|
||||||
|
replication.add_operation(operation);
|
||||||
|
|
||||||
|
// Проверяем, что операция добавлена в очередь
|
||||||
|
assert_eq!(replication.get_pending_operations_count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест отключения репликации
|
||||||
|
#[test]
|
||||||
|
fn test_disabling_replication() {
|
||||||
|
let replication = LockFreeReplication::new(
|
||||||
|
true,
|
||||||
|
vec!["http://test-node:8080".to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Выключаем репликацию
|
||||||
|
replication.disable();
|
||||||
|
|
||||||
|
// Проверяем, что репликация выключена
|
||||||
|
assert!(!replication.is_enabled());
|
||||||
|
|
||||||
|
let operation = ReplicationOperation {
|
||||||
|
operation: "create".to_string(),
|
||||||
|
collection: "test_space".to_string(),
|
||||||
|
key: "test_key".to_string(),
|
||||||
|
value: json!({"field": "value"}),
|
||||||
|
timestamp: 1234567890,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Пытаемся добавить операцию при выключенной репликации
|
||||||
|
replication.add_operation(operation);
|
||||||
|
|
||||||
|
// Операция не должна быть добавлена в очередь
|
||||||
|
assert_eq!(replication.get_pending_operations_count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест получения статистики
|
||||||
|
#[test]
|
||||||
|
fn test_getting_stats() {
|
||||||
|
let replication = LockFreeReplication::new(
|
||||||
|
true,
|
||||||
|
vec!["http://node1:8080".to_string(), "http://node2:8080".to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
let stats = replication.get_stats();
|
||||||
|
|
||||||
|
// Проверяем корректность статистики
|
||||||
|
assert!(stats.enabled);
|
||||||
|
assert_eq!(stats.node_count, 2);
|
||||||
|
assert_eq!(stats.pending_operations, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user