Dubbo 源码剖析与实战
何辉
平安壹钱包架构师
4711 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 33 讲
开篇词 (1讲)
Dubbo 源码剖析与实战
15
15
1.0x
00:00/00:00
登录|注册

08|缓存操作:如何为接口优雅地提供缓存功能?

你好,我是何辉。今天我们探索 Dubbo 框架的第七道特色风味,缓存操作。
移动端 App 你应该不陌生了,不过最近有个项目引发了用户吐槽:
图中的 App,在首页进行页面渲染加载时会向网关发起请求,网关会从权限系统拿到角色信息列表和菜单信息列表,从用户系统拿到当前登录用户的简单信息,然后把三块信息一并返回给到 App。
然而,就是这样一个看似简单的功能,每当上下班的时候因为 App 被打开的频率非常高,首页加载的请求流量在短时间内居高不下,打开很卡顿,渲染很慢。
经过排查后,发现该 App 只有数十万用户,但意外的是在访问高峰期,权限系统的响应时间比以往增长了近 10 倍,权限系统集群中单机查询数据库的 QPS 高达 500 多,导致数据库的查询压力特别大,从而导致查询请求响应特别慢。
由于目前用户体量尚且不大,架构团队商讨后,为了稳住用户体验,最快的办法就是在网关增加缓存功能,把首页加载请求的结果缓存起来,以提升首页快速渲染页面的时效。
对于这个加缓存的需求,你会如何优雅地处理呢?

缓存疑惑

在正式思考处理思路前,不知道你对架构团队的结论有没有疑惑,为什么增加一个简单的缓存功能,就能提升接口响应时效呢?
我以前也有过这样的疑惑,直到有一天研究 volatile 原理时,看到了一张关于系统存储媒介的延时量化图(你可以搜索 “Latency numbers every programmer should know” 关键字),才恍然大悟:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了在Dubbo框架中如何优雅地为接口提供缓存功能。首先描述了移动端App在首页加载时频繁向网关发起请求,导致页面卡顿和渲染缓慢的问题。经过排查,发现是权限系统响应时间增长导致的数据库查询压力过大。为了改善用户体验,架构团队决定在网关增加缓存功能。文章解释了增加缓存能提升接口响应时效的原因,通过比较不同存储媒介的延时关系,阐述了缓存对接口响应时间的减少具有质的飞越的作用。接着,文章介绍了在网关增加缓存功能的处理思路,包括简单处理和统一处理两种方式。在简单处理中,通过在网关增加一层缓存模块,减少远程调用次数,从而降低权限系统查询数据库的频次,提升性能。最后,文章提出了对简单处理方式的改进,通过统一处理方式提炼出通用的缓存调用结果的方法,避免重复代码和坏代码味道的产生。文章还通过源码解析的方式展示了Dubbo框架中如何实现缓存功能,为读者提供了清晰的实践指导。文章还讨论了改造方案可能带来的影响,包括内存容量、缓存数据刷新和过期剔除、流量峰值可能引发的缓存雪崩、穿透、击穿等问题。通过实际案例和技术原理,深入浅出地介绍了如何为接口提供缓存功能,对于需要优化接口性能的开发人员具有一定的参考价值。文章内容丰富,涵盖了缓存操作的应用、总结和思考题,为读者提供了全面的技术知识和实践经验。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Dubbo 源码剖析与实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(7)

  • 最新
  • 精选
  • RocketMQ
    为什么lru的方式下第二次和第三次调用结果相同,也就是第二次相同的调用才会缓存下来?但jcache方式下三次都是一样的

    作者回复: 你好,RocketMQ:关于 lru 的这个细节现象,观察的非常仔细,问的非常好。 关于 lru 在存储层面,具体的实现类是 LRU2Cache 这个类,同时这个 LRU2Cache 也是继承了 LinkedHashMap,说明 LRU2Cache 自己本身就是一个 Map 类型的派生类,但是呢 LRU2Cache 自己也持有了一个【private PreCache<K, Boolean> preCache】变量,该变量的容量默认是 1000。 可以看看这段 get/put 的代码: ======================== @Override public V get(Object key) { lock.lock(); try { return super.get(key); } finally { lock.unlock(); } } @Override public V put(K key, V value) { lock.lock(); try { if (preCache.containsKey(key)) { // add it to cache preCache.remove(key); return super.put(key, value); } else { // add it to history list preCache.put(key, true); return value; } } finally { lock.unlock(); } } ======================== LRU2Cache 在 get 的时候,总是从 LRU2Cache 自己的父类中去 get 数据,但是在 put 的时候,针对同一个 key 进行第二次 put 的时候才会放到 LRU2Cache 自己的父类中去。 这其实是 LRU2Cache 自身针对不常用的缓存数据一种考量,若请求的结果 R1 调用的频率极低的时候,那么 LRU2Cache 就会考虑优先将 R1 放到 preCache 中,在以后的 999 次请求中,R1 若还会被用到的话,那就会将 R1 转移到 LRU2Cache 的父类缓存中去;若在以后的 999 次请求中还是不会用到 R1 的话,那么就只好依赖 PreCache#removeEldestEntry 方法移除这种百八十年不动的“僵尸冷数据”。

    2023-03-17归属地:广东
    1
  • 飞飞
    实际开发真的会真么用吗?一般不都是服务提供者直接在自己的业务逻辑里面使用redis等直接缓存结果吗?不需要配置这么多东西呀?

    作者回复: 你好,飞飞:实际开发这个问的好,我只是众多实际开发中的一员,不能以点代面。看不同开发人员的诉求,如果不想引入其他框架或不想写,也可以直接使用 dubbo 的,又或者有些公司主推使用 dubbo 特性啥的,也可能有其他特别喜好使用 dubbo 人员啥的,这个都不不好说,具体场景具体问题,因地制宜看待罢了,只要适合自己的就好。

    2023-05-30归属地:北京
    2
  • Lum
    这个dubbo中的cache支持过期时间等定制化功能吗 看cache的接口只有两个方法

    作者回复: 你好,Lum:这个我教你一个墨守成规的快速找到的方式,在源码的【org.apache.dubbo.cache】包下,进行【Ctrl + Shift + F】快捷键搜索,然后输入【"cache.】这样的内容,然后就可以快速看到哪些是与时间相关的配置参数键了。

    2023-03-05归属地:北京
  • 杨老师
    在平时工作中,可能不会通过@DubboReference(cache = "jcache")来引入redis。 而是就直接使用redis了。 这俩方案没啥区别吧?而且第一种好像更麻烦些了

    作者回复: 你好,杨老师:这个看不同人的喜好吧~如果是少部分接口,或者是少部分系统内部的功能,需要使用缓存的话,怎么书写都无大碍。 但是如果一旦这种少部分功能的慢慢累积增多时,就得慢慢考虑雷同重复代码的抽象封装了,至于是考虑在Dubbo层面扩展,还是自行横切拦截扩展,那只是不同的方式而已。 而直接在 Dubbo 层面扩展,意思就很明确,缓存的粒度,就是调用下游的接口,或者系统暴露的接口,比较精准聚焦。 而除了需要对Dubbo的接口进行缓存,还需要对一些数据库的查询操作,系统内的业务聚合操作做缓存的话,那么Dubbo框架就无法满足了,这个时候就得考虑自定义横切拦截了。

    2023-02-22归属地:北京
    2
  • 星期八
    服务端提供缓存功能是为了缓存方法调用后的结果的,在下一次客户端调用过来,服务端只需要取缓存的吗?

    作者回复: 你好,星期八:是的,我们可以试着反推一下,如果缓存调用前的数据,可是调用之前还不知道调用结果,那缓存什么呢?缓存空对象么?如果缓存空对象的话,那么当接收消费方请求时,提供方发现有一个空对象缓存,如果把空对象返回给消费方,从功能的角度来说,功能是有缺失的,不加缓存还能拿到数据,加了缓存反而拿到一个空对象,明显不是消费方想要的结果。

    2023-01-15归属地:浙江
  • 孙升
    if (iterator.hasNext()) { CachingProvider provider = iterator.next(); if (iterator.hasNext()) { throw new CacheException("Multiple CachingProviders have been configured when only a single CachingProvider is expected"); } else { return provider; } } else { throw new CacheException("No CachingProviders have been configured"); } 自问自答了,这里会校验是否有多个实现类

    作者回复: 你好,Mandone:哈哈,好像是哦,自问自答啦,没关系啦,发现问题,找到问题,解答疑惑,这挺好的,非常棒~

    2023-01-06归属地:广东
  • 孙升
    如果同时引入除redisson支持Jcache规范的其他maven包会怎么样?如何判断要使用哪个包的Cache呢?是通过spi配置吗

    作者回复: 你好,Mandone:在这个 javax.cache.Caching.CachingProviderRegistry#getCachingProvider(java.lang.ClassLoader) 方法中,会进行判断,如果利用 SPI 机制加载 CachingProvider 的所有实现类后,发现有多个实现类则会抛出异常的,抛出的异常信息代码为:throw new CacheException("Multiple CachingProviders have been configured when only a single CachingProvider is expected")。

    2023-01-06归属地:广东
收起评论
显示
设置
留言
7
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部