use crate::error::{CacheError, CacheResult};
use crate::types::EvictionStrategy;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use sysinfo::System;
use rat_logger;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
pub l1: L1Config,
pub l2: Option<L2Config>,
pub ttl: TtlConfig,
pub performance: PerformanceConfig,
pub logging: Option<LoggingConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct L1Config {
pub max_memory: usize,
pub max_entries: usize,
pub eviction_strategy: EvictionStrategy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct L2Config {
pub enable_l2_cache: bool,
pub data_dir: Option<PathBuf>,
#[serde(default)]
pub clear_on_startup: bool,
#[serde(default)]
pub max_disk_size: u64,
#[serde(default)]
pub write_buffer_size: usize,
#[serde(default)]
pub max_write_buffer_number: i32,
#[serde(default)]
pub block_cache_size: usize,
#[serde(default)]
pub background_threads: i32,
#[serde(default = "default_true")]
pub enable_lz4: bool,
#[serde(default = "default_compression_threshold")]
pub compression_threshold: usize,
#[serde(default = "default_compression_max_threshold")]
pub compression_max_threshold: usize,
#[serde(default)]
pub compression_level: i32,
#[serde(default)]
pub cache_size_mb: usize,
#[serde(default)]
pub max_file_size_mb: usize,
#[serde(default)]
pub smart_flush_enabled: bool,
#[serde(default)]
pub smart_flush_base_interval_ms: usize,
#[serde(default)]
pub smart_flush_min_interval_ms: usize,
#[serde(default)]
pub smart_flush_max_interval_ms: usize,
#[serde(default)]
pub smart_flush_write_rate_threshold: usize,
#[serde(default)]
pub smart_flush_accumulated_bytes_threshold: usize,
#[serde(default)]
pub cache_warmup_strategy: CacheWarmupStrategy,
#[serde(default)]
pub zstd_compression_level: Option<i32>,
#[serde(default)]
pub l2_write_strategy: String,
#[serde(default)]
pub l2_write_threshold: usize,
#[serde(default)]
pub l2_write_ttl_threshold: u64,
}
#[cfg(feature = "melange-storage")]
impl Default for L2Config {
fn default() -> Self {
Self {
enable_l2_cache: false,
data_dir: None,
clear_on_startup: false,
max_disk_size: 1024 * 1024 * 1024, write_buffer_size: 64 * 1024 * 1024, max_write_buffer_number: 3,
block_cache_size: 32 * 1024 * 1024, background_threads: 2,
enable_lz4: true,
compression_threshold: 128,
compression_max_threshold: 1024 * 1024,
compression_level: 6,
cache_size_mb: 512,
max_file_size_mb: 1024,
smart_flush_enabled: true,
smart_flush_base_interval_ms: 100,
smart_flush_min_interval_ms: 20,
smart_flush_max_interval_ms: 500,
smart_flush_write_rate_threshold: 10000,
smart_flush_accumulated_bytes_threshold: 4 * 1024 * 1024, cache_warmup_strategy: CacheWarmupStrategy::Recent,
zstd_compression_level: None,
l2_write_strategy: "write_through".to_string(),
l2_write_threshold: 1024,
l2_write_ttl_threshold: 300,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CacheWarmupStrategy {
None,
Recent,
Frequent,
Full,
}
impl Default for CacheWarmupStrategy {
fn default() -> Self {
CacheWarmupStrategy::None
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TtlConfig {
pub expire_seconds: Option<u64>,
pub cleanup_interval: u64,
pub max_cleanup_entries: usize,
pub lazy_expiration: bool,
pub active_expiration: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceConfig {
pub worker_threads: usize,
pub enable_concurrency: bool,
pub read_write_separation: bool,
pub batch_size: usize,
pub enable_warmup: bool,
pub large_value_threshold: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
#[serde(default = "default_log_level")]
pub level: String,
#[serde(default = "default_true")]
pub enable_colors: bool,
#[serde(default = "default_true")]
pub show_timestamp: bool,
#[serde(default = "default_true")]
pub enable_performance_logs: bool,
#[serde(default = "default_true")]
pub enable_audit_logs: bool,
#[serde(default = "default_true")]
pub enable_cache_logs: bool,
#[serde(default = "default_true")]
pub enable_logging: bool,
#[serde(default = "default_false")]
pub enable_async: bool,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
#[serde(default = "default_batch_interval_ms")]
pub batch_interval_ms: u64,
#[serde(default = "default_buffer_size")]
pub buffer_size: usize,
}
#[derive(Debug)]
pub struct CacheConfigBuilder {
l1_config: Option<L1Config>,
l2_config: Option<L2Config>,
ttl_config: Option<TtlConfig>,
performance_config: Option<PerformanceConfig>,
logging_config: Option<LoggingConfig>,
}
impl CacheConfigBuilder {
pub fn new() -> Self {
Self {
l1_config: None,
l2_config: None,
ttl_config: None,
performance_config: None,
logging_config: None,
}
}
pub fn with_l1_config(mut self, config: L1Config) -> Self {
self.l1_config = Some(config);
self
}
#[cfg(feature = "melange-storage")]
pub fn with_l2_config(mut self, config: L2Config) -> Self {
self.l2_config = Some(config);
self
}
pub fn with_ttl_config(mut self, config: TtlConfig) -> Self {
self.ttl_config = Some(config);
self
}
pub fn with_performance_config(mut self, config: PerformanceConfig) -> Self {
self.performance_config = Some(config);
self
}
pub fn with_logging_config(mut self, config: LoggingConfig) -> Self {
self.logging_config = Some(config);
self
}
pub fn build(self) -> CacheResult<CacheConfig> {
let l1_config = self.l1_config.ok_or_else(|| {
CacheError::config_error("L1 配置未设置")
})?;
let l2_config = if cfg!(feature = "melange-storage") {
if self.l2_config.is_none() {
return Err(CacheError::config_error("L2 配置未设置(启用了melange-storage特性时必须配置)"));
}
self.l2_config
} else {
None
};
let ttl_config = self.ttl_config.ok_or_else(|| {
CacheError::config_error("TTL 配置未设置")
})?;
let performance_config = self.performance_config.ok_or_else(|| {
CacheError::config_error("性能配置未设置")
})?;
let logging_config = self.logging_config;
#[cfg(feature = "melange-storage")]
if let Some(ref l2_config) = l2_config {
Self::validate_config(&l1_config, l2_config, &ttl_config, &performance_config)?;
}
#[cfg(not(feature = "melange-storage"))]
Self::validate_config(&l1_config, &ttl_config, &performance_config)?;
let config = CacheConfig {
l1: l1_config,
l2: l2_config,
ttl: ttl_config,
performance: performance_config,
logging: logging_config,
};
Self::validate_overall_config(&config)?;
Ok(config)
}
#[cfg(feature = "melange-storage")]
fn validate_config(
l1_config: &L1Config,
l2_config: &L2Config,
ttl_config: &TtlConfig,
performance_config: &PerformanceConfig,
) -> CacheResult<()> {
if l1_config.max_memory == 0 {
return Err(CacheError::config_error("L1 最大内存不能为 0"));
}
if l1_config.max_entries == 0 {
return Err(CacheError::config_error("L1 最大条目数不能为 0"));
}
if l2_config.enable_l2_cache {
if l2_config.max_disk_size == 0 {
return Err(CacheError::config_error("L2 最大磁盘大小不能为 0"));
}
if l2_config.write_buffer_size == 0 {
return Err(CacheError::config_error("写缓冲区大小不能为 0"));
}
if l2_config.max_write_buffer_number <= 0 {
return Err(CacheError::config_error("最大写缓冲区数量必须大于 0"));
}
if l2_config.background_threads <= 0 {
return Err(CacheError::config_error("后台线程数必须大于 0"));
}
let valid_strategies = ["always", "never", "size_based", "ttl_based", "adaptive", "write_through"];
if !valid_strategies.contains(&l2_config.l2_write_strategy.as_str()) {
return Err(CacheError::config_error(&format!(
"无效的 L2 写入策略: {},有效值: {:?}",
l2_config.l2_write_strategy, valid_strategies
)));
}
if let Some(ref data_dir) = l2_config.data_dir {
PathUtils::validate_writable_path(data_dir)?;
}
if l2_config.enable_lz4 {
if l2_config.compression_level < 1 || l2_config.compression_level > 12 {
return Err(CacheError::config_error("压缩级别必须在 1-12 之间"));
}
if l2_config.compression_threshold >= l2_config.compression_max_threshold {
return Err(CacheError::config_error("压缩最小阈值必须小于最大阈值"));
}
}
}
if ttl_config.cleanup_interval == 0 {
return Err(CacheError::config_error("清理间隔不能为 0"));
}
if ttl_config.max_cleanup_entries == 0 {
return Err(CacheError::config_error("最大清理条目数不能为 0"));
}
if performance_config.worker_threads == 0 {
return Err(CacheError::config_error("工作线程数不能为 0"));
}
if performance_config.batch_size == 0 {
return Err(CacheError::config_error("批处理大小不能为 0"));
}
Ok(())
}
#[cfg(not(feature = "melange-storage"))]
fn validate_config(
l1_config: &L1Config,
ttl_config: &TtlConfig,
performance_config: &PerformanceConfig,
) -> CacheResult<()> {
if l1_config.max_memory == 0 {
return Err(CacheError::config_error("L1 最大内存不能为 0"));
}
if l1_config.max_entries == 0 {
return Err(CacheError::config_error("L1 最大条目数不能为 0"));
}
if ttl_config.cleanup_interval == 0 {
return Err(CacheError::config_error("清理间隔不能为 0"));
}
if ttl_config.max_cleanup_entries == 0 {
return Err(CacheError::config_error("最大清理条目数不能为 0"));
}
if performance_config.worker_threads == 0 {
return Err(CacheError::config_error("工作线程数不能为 0"));
}
if performance_config.batch_size == 0 {
return Err(CacheError::config_error("批处理大小不能为 0"));
}
Ok(())
}
fn validate_overall_config(config: &CacheConfig) -> CacheResult<()> {
let system_info = SystemInfo::get();
let l1_memory_mb = config.l1.max_memory / (1024 * 1024);
if system_info.available_memory > 0 {
let available_memory_mb = system_info.available_memory / (1024 * 1024);
if config.l1.max_memory > (system_info.available_memory as usize / 2) {
return Err(CacheError::config_error(&format!(
"L1 缓存内存 ({} MB) 超过可用内存的一半 ({} MB),可能导致系统不稳定",
l1_memory_mb, available_memory_mb / 2
)));
}
} else {
rat_logger::debug!("无法获取可用内存信息,跳过内存检查");
}
if config.performance.worker_threads > system_info.cpu_count * 4 {
return Err(CacheError::config_error(&format!(
"工作线程数 ({}) 超过 CPU 核心数的 4 倍 ({}×4={})",
config.performance.worker_threads,
system_info.cpu_count,
system_info.cpu_count * 4
)));
}
#[cfg(feature = "melange-storage")]
if let Some(ref l2_config) = config.l2 {
if l2_config.enable_l2_cache && l2_config.enable_lz4 {
if l2_config.compression_threshold >= l2_config.compression_max_threshold {
return Err(CacheError::config_error(
"L2 缓存压缩最小阈值必须小于最大阈值"
));
}
}
}
Ok(())
}
}
impl Default for CacheConfigBuilder {
fn default() -> Self {
Self::new()
}
}
struct SystemInfo {
total_memory: u64,
available_memory: u64,
cpu_count: usize,
}
impl SystemInfo {
fn get() -> Self {
let mut sys = System::new_all();
sys.refresh_all();
Self {
total_memory: sys.total_memory(),
available_memory: sys.available_memory(),
cpu_count: sys.cpus().len(),
}
}
fn recommended_l1_memory(&self) -> usize {
let quarter_memory = (self.available_memory / 4) as usize;
let max_l1_memory = 2 * 1024 * 1024 * 1024; quarter_memory.min(max_l1_memory)
}
fn recommended_worker_threads(&self) -> usize {
(self.cpu_count * 2).min(32).max(4)
}
}
struct PathUtils;
impl PathUtils {
fn default_cache_dir() -> CacheResult<PathBuf> {
rat_logger::debug!("获取默认缓存目录");
let temp_dir = std::env::temp_dir();
rat_logger::debug!("系统临时目录: {:?}", temp_dir);
let cache_dir = temp_dir.join("rat_memcache");
rat_logger::debug!("缓存目录路径: {:?}", cache_dir);
rat_logger::debug!("尝试创建缓存目录...");
match std::fs::create_dir_all(&cache_dir) {
Ok(_) => rat_logger::debug!("缓存目录创建成功"),
Err(e) => {
rat_logger::debug!("创建缓存目录失败: {}", e);
return Err(CacheError::config_error(&format!("创建缓存目录失败: {}", e)));
}
}
rat_logger::debug!("返回缓存目录: {:?}", cache_dir);
Ok(cache_dir)
}
fn validate_writable_path(path: &PathBuf) -> CacheResult<()> {
rat_logger::debug!("验证路径是否可写: {:?}", path);
rat_logger::debug!("路径是否存在: {}", path.exists());
if !path.exists() {
rat_logger::debug!("目标目录不存在,尝试创建: {:?}", path);
match std::fs::create_dir_all(path) {
Ok(_) => rat_logger::debug!("目标目录创建成功"),
Err(e) => {
rat_logger::debug!("创建目标目录失败: {}", e);
return Err(CacheError::config_error(&format!("创建目录失败: {}", e)));
}
}
}
let test_file = path.join(".write_test");
rat_logger::debug!("尝试创建测试文件: {:?}", test_file);
match std::fs::write(&test_file, b"test") {
Ok(_) => rat_logger::debug!("测试文件创建成功"),
Err(e) => {
rat_logger::debug!("创建测试文件失败: {}", e);
return Err(CacheError::config_error(&format!("路径不可写: {}", e)));
}
}
rat_logger::debug!("尝试删除测试文件...");
match std::fs::remove_file(&test_file) {
Ok(_) => rat_logger::debug!("测试文件删除成功"),
Err(e) => rat_logger::debug!("删除测试文件失败: {}", e)
}
rat_logger::debug!("路径验证成功");
Ok(())
}
}
#[cfg(feature = "melange-storage")]
fn default_melange_compression() -> bool {
true
}
#[cfg(feature = "melange-storage")]
fn default_melange_cache_size() -> usize {
512 }
#[cfg(feature = "melange-storage")]
fn default_melange_max_file_size() -> usize {
1024 }
#[cfg(feature = "melange-storage")]
fn default_melange_stats_enabled() -> bool {
true
}
#[cfg(feature = "melange-storage")]
fn default_melange_smart_flush_enabled() -> bool {
true
}
#[cfg(feature = "melange-storage")]
fn default_melange_smart_flush_base_interval() -> usize {
100 }
#[cfg(feature = "melange-storage")]
fn default_melange_smart_flush_min_interval() -> usize {
20 }
#[cfg(feature = "melange-storage")]
fn default_melange_smart_flush_max_interval() -> usize {
500 }
#[cfg(feature = "melange-storage")]
fn default_melange_smart_flush_write_threshold() -> usize {
10000
}
#[cfg(feature = "melange-storage")]
fn default_melange_smart_flush_bytes_threshold() -> usize {
4 * 1024 * 1024 }
#[cfg(feature = "melange-storage")]
fn default_melange_warmup_strategy() -> CacheWarmupStrategy {
CacheWarmupStrategy::Recent
}
fn default_log_level() -> String {
"info".to_string()
}
fn default_true() -> bool {
true
}
fn default_false() -> bool {
false
}
fn default_batch_size() -> usize {
2048
}
fn default_batch_interval_ms() -> u64 {
25
}
fn default_buffer_size() -> usize {
16 * 1024
}
fn default_compression_threshold() -> usize {
128 }
fn default_compression_max_threshold() -> usize {
1024 * 1024 }