侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计线程安全的可重入缓存系统

2025-12-11 / 0 评论 / 4 阅读

题目

设计线程安全的可重入缓存系统

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

并发编程, 元编程, 缓存策略, 线程安全, 异常处理

快速回答

实现线程安全的可重入缓存需解决以下核心问题:

  • 使用 Concurrent::MapMutex 保证基础存储的线程安全
  • 通过 Thread.current 跟踪递归调用避免死锁
  • 实现缓存击穿保护(如锁优化或占位符)
  • 添加 TTL 过期机制和缓存清理策略
  • 处理异常和缓存雪崩(如随机过期时间)
## 解析

核心挑战与设计目标

在并发环境下实现缓存系统需解决:1) 多线程竞争导致数据不一致;2) 递归调用触发死锁;3) 缓存失效时的雪崩/击穿问题。

完整实现代码

require 'concurrent'

class ReentrantCache
def initialize
@store = Concurrent::Map.new
@mutex = Mutex.new
end

def fetch(key, ttl: nil, &block)
# 检查当前线程递归深度
thread_data = Thread.current[:cache_recursion] ||= {}
if thread_data[key]
return yield # 直接执行块避免死锁
end

entry = @store[key]

# 缓存有效检查
if entry_valid?(entry, ttl)
entry[:value]
else
# 标记递归状态
thread_data[key] = true

@mutex.synchronize do
# 双重检查锁定模式
entry = @store[key]
if entry_valid?(entry, ttl)
thread_data.delete(key)
return entry[:value]
end

begin
result = yield
@store[key] = { value: result, created_at: Time.now }
result
ensure
thread_data.delete(key)
end
end
end
end

def clear
@store.clear
end

private

def entry_valid?(entry, ttl)
entry && (!ttl || Time.now - entry[:created_at] < ttl)
end
end

# 使用示例
cache = ReentrantCache.new
cache.fetch(:api_data, ttl: 60) { expensive_operation }

关键技术解析

  • 线程安全存储:使用 Concurrent::Map 替代 Hash,或配合 Mutex 同步关键操作
  • 可重入设计:通过 Thread.current[:cache_recursion] 记录当前线程的调用栈,遇到递归时直接执行块代码
  • 缓存击穿防护:双重检查锁定(Double-Checked Locking)确保只有一个线程执行耗时操作
  • TTL 实现:存储创建时间戳,在读取时校验时间差

最佳实践

  • 对高频热点数据使用 Concurrent::Map#compute_if_absent 原子操作
  • 添加随机抖动到 TTL(如 ttl * (0.8 + rand(0.4)))避免批量失效
  • 大型系统可结合 ActiveSupport::Cache::Store 扩展

常见错误

  • 死锁风险:未处理可重入时,同一线程二次获取锁导致死锁
  • 脏读问题:非原子操作下可能返回过期数据(如先读后校验 TTL)
  • 内存泄漏:未清理无效缓存或递归标记数据

扩展知识

  • 缓存雪崩对策:分层缓存 + 熔断机制
  • 高级优化
    - 使用 Concurrent::LazyRegister 延迟加载
    - 通过 ObjectSpace::WeakMap 实现弱引用缓存
    - 结合 Ractor 隔离(Ruby 3+)
  • 性能监控:添加 hit_rate 统计和慢查询日志