学习了一下 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 没有输出。
信渊格格,得永生