学习了一下 second_level_cache, data service 的确做了一些和 second_level_cache 类似的事情, 场景的区别主要因为我们前后端项目分离后, Write-Through 在 ruby, Read-Through 需要在 node 端发起. 在单体 ruby 项目中 SecondLevelCache 是个非常不错的选择!
不过有些场景还是不能容忍数据在缓存中 miss 后再去触发生成, 这个节点容易造成 5XX, data service 主要就是加强缓存异步刷新, 在 redis 数据没有 expire 之前去刷新数据.
#5 楼 @i5ting 感谢指教. 这里和直接 RPC 还是有点区别, 在文中的最后一张图「周内 TP 变化情况」, 该页面是在 22 号之前很多数据是走的 http/thrift RPC, 22 号之前的很多不可用 (超时/5XX) 都是因为 RPC 调用偶尔的失败导致, 而 22 后接入了 data service 后, 基本保证所有数据在 redis 中都有, 基本不会发生 miss 的情况, 页面很稳定.
对比 RPC 封装缓存和 data service 采用的异步更新, 忘了哪里看来的比喻还挺形象的:
发现缓存过期时, 线程都去获取并更新缓存, 容易引发雪崩, 5XX 的原因:
5 个工人(线程)去港口取同样 Key 的货(get),发现货已经过期被扔掉了,这时 5 个工人各自分别去对岸取新货,然后返回
异步更新数据:
5 个工人(线程)去港口取同样 Key 的货(get), 货一直存在, 即使货过期了也不会扔掉 (很长的有效期). 但港口的货物还是比较新鲜, 因为对岸有个家伙在不时更新着货物, 这个家伙在 data service 中就是 ruby 端的 after_commit 或者定时 rake
#1 楼 @huacnlee
看了代码, 目前的确是用的after_commit
感谢提醒~ 稍后更新文档.
1) 数据更新有 2 种途径:
对于需要实时更新的数据, 使用after_commit+sidekiq
, 只是稍微进行了封装, 调用方式形如: Monitor.watch(Product, SomeSidekiqWorker, :title, :price)
会给 Product 加上after_commit
, after_commit
中判断如果 title 或 price 有变化, 会扔到 sidekiq 中更新.
对于旧数据有一定容忍度的业务, 采用 rake 定时更新.
2) 缓存存储前面需要有一个应用服务, 两者都可能成为瓶颈, Redis 自然高效, 应用服务使用 node 的好处之一在于异步 IO:
let data = yield {
'商品': dataService.fetch(...),
'广告': dataService.fetch(...),
'友情链接': dataService.fetch(...),
}
dataService.fetch
返回的是 promise, 3 次 IO 的时间为: max(查商品时间, 查广告时间, 查友链时间)
如果采用阻塞 IO 的实现, 时间将是: 查商品时间 + 查广告时间 + 查友链时间
3) 缓存时间是各模型必须设置的, 是在 redis cluster 中的缓存时间, 我们有用 varnish 作为反向代理, 主要用于页面缓存.
Data Service 的目的在于管理那些可缓存的数据, Data Service 提供的数据, 前端在 Node 中还可能进行加工再展示, 这也是之前引入 node 的一个原因 -- 前端希望管理 view 层.
另外不同页面的 Data Service 数据可以在 node 端方便的组合, 比如很多页面都需要「友情链接」的数据, 在 Data Service 中只需要存一份.
采用 redis 的另一个原因是可以比较容易的进行分布式扩展, 可选哨兵 + 主从或者 cluster.
#16 楼 @killernova 2 个 puts 之间其实有 200 多行代码, 篇幅原因, 在文章中省略掉了中间代码. 当时是希望通过 singleton_methods 去判断类方法是否加上了, 意外发现了第二个 puts 没有输出.
信渊格格,得永生