Elixir中是否有reader-writer lock实现?或其他可避免脏读的解决方案?
解决方案
一、读写锁实现方案
如果希望直接用读写锁逻辑解决问题,Elixir生态有成熟的实现可以直接使用:
- 单节点部署场景:可以直接调用Erlang的
:rwlock库,原生支持多读者单写者的锁逻辑,读锁在无写锁持有时完全不阻塞,符合读多写少的性能要求。示例用法:
# 应用启动时初始化锁进程 :rwlock.start_link(:shared_state_lock) # 读请求逻辑 def read_shared_state do :rwlock.read_lock(:shared_state_lock) # 读取Redis、ElasticSearch中状态的原有逻辑 after :rwlock.read_unlock(:shared_state_lock) end # 写请求逻辑 def update_shared_state do :rwlock.write_lock(:shared_state_lock) # 执行3秒的全量状态更新逻辑,全部完成后再释放锁 after :rwlock.write_unlock(:shared_state_lock) end
- 多节点部署场景:可以基于Redis实现分布式读写锁,使用
redlock库的Elixir封装即可,锁逻辑和单节点一致,只是锁存储在Redis中跨节点共享。
二、更优的无锁方案(推荐)
由于你的状态存储在外部的Redis和ElasticSearch中,且读请求频率远高于写请求,版本快照双写切换的无锁方案可以实现读性能零损失,同时完全避免脏读,比读写锁更适配你的场景:
- 新增一个全局版本号字段存储在Redis中,比如键为
shared_state:version,初始值为1 - 所有读请求首先读取当前版本号,再读取对应版本的状态数据:Redis读取
shared_state:v{version}键,ElasticSearch读取shared_state_v{version}别名指向的索引 - 写入时完全不修改当前版本的任何数据,先生成新版本号(当前版本+1),将新状态全量写入新版本对应的Redis键和ElasticSearch索引
- 等待3秒的写入操作全部执行成功后,原子修改Redis中的
shared_state:version值为新版本号 - 后续所有读请求会自动切换到新版本数据,旧版本的冗余数据可以异步清理
这个方案没有任何锁开销,读请求全程无阻塞,写入过程完全不影响现有读请求,不会出现任何脏读,唯一的额外成本是写入时需要临时存储双份状态,对于3秒即可完成更新的状态来说成本可以忽略。
内容的提问来源于stack exchange,提问作者ShenLei
相关产品推荐
相关产品推荐

