如何优雅地编写缓存代码
![如何优雅地编写缓存代码](https://static001.geekbang.org/infoq/27/27615e040aec8e3af8c06357b8186dd0.png)
作者:巧凡
一、前言
在日常的编码实践中,经常会用到缓存来解决高并发问题,缓存可以说是解决流量洪峰的不二利器。虽然集团中间件团队已经构建了缓存的基础设施,已经帮助我们解决了绝大部分问题,但是在实际的编码使用过程中,应用端调用缓存 API 时还是存在下述几类问题:
使用缓存的逻辑非常通用,基本都是先查缓存,有直接返回,没有查 DB,再放入缓存中。这段通用逻辑散落在系统的各个地方,违反了高内聚低耦合的原则。
缓存代码和业务逻辑代码深度耦合在一起,不仅降低了代码的可读性,还额外增加了系统复杂度。
如果要切换缓存(MDB->LDB)或者 API 升级时,所有涉及代码都需要改动。
如果要解决缓存击穿、缓存穿透、级联缓存等类似通用问题时,都需要通过框架去解决。
因此,缓存是什么,如何选择某一种缓存,都不是本文重点,今天就写写实际编码过程中,如何将缓存代码从业务代码中剥离出来,促使代码更简洁,更便于阅读。
二、实践分析
先读取缓存数据,如果有数据则直接返回,如果没有读取到数据,则读取 DB 数据,等数据返回后,再更新缓存。
![](https://static001.geekbang.org/infoq/fa/fa618bcbb8fc131743989f4ca092c5d2.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
这种场景,在日常编码中,很常见,太简单,但是实际的代码确实很不一样,列举如下几种:
2.1 传统写法
使用什么缓存,就直接使用,嵌入到业务代码中。这种代码不管是 code review,还是后人学习业务代码时,都不想看,道理很简单,跟实际的业务功能无关,我不想知道你用什么缓存,你是怎么编码缓存代码的。
![](https://static001.geekbang.org/infoq/be/be7d313de4d8b6aab11c5dad247e958d.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
![](https://static001.geekbang.org/infoq/f1/f1437b62c0a40502e0ca969ed2bacb95.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
2.2 高级一点的写法
相比传统的写法,为了解决缓存各种数据格式(List、Map 等),各种对象序列化问题(java、json),团队内可以针对缓存这块,封装成简单的 API,方便大家使用。使用简单了,但代码依然嵌入在业务代码中,没有剥离出来。
![](https://static001.geekbang.org/infoq/a1/a1c6477b436236d0f073747fef5685db.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
![](https://static001.geekbang.org/infoq/d8/d8ba93d4106f35e3f3246ef7f2fc47a7.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
2.3 注解写法
最后是注解写法,相对前两种写法,代码已从业务代码中剥离出来,阅读代码的人,只会关心业务功能是如何实现的,使用哪个缓存,如何实现的,完全可以忽略。
![](https://static001.geekbang.org/infoq/89/89eb4012aa9dd9eb444f427845f589cf.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
三、spring cache 方案分析
spring cache 利用动态代理的方式,在代理类中处理缓存的相关操作,同时调用被代理类中的方法,从而可以使操作缓存的代码和业务代码分离,并且后期需要强化缓存能力时,也只需要修改代理类中的方法即可。
![](https://static001.geekbang.org/infoq/95/959fb29133250105e020482407857742.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
以上就是 Spring Cache 的原理。Spring Cache 是 Spring 提供的通用缓存框架。它利用了 AOP,实现了基于注解的缓存功能,使开发者不用关心底层使用了什么缓存框架,只需要在方法上简单地加一个注解,就能实现缓存功能了。用户使用 Spring Cache,可以快速开发一个很不错的缓存功能。
3.1 代码目录
![](https://static001.geekbang.org/infoq/a5/a53ba1961aaf15953a602a1883c439dc.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
3.2 注解导图
![](https://static001.geekbang.org/infoq/e4/e40ab45322493139215162f6632787dd.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
3.3 注解使用示例
3.4 方案分析
Spring Cache 的功能很强大,设计也非常优雅。特别适合缓存控制没有那么细致的场景,比如偏静态展示页面,点赞数、排名等等,这些场景的特点是对数据实时性没有那么严格的要求,只需要将数据源缓存下来,过期之后自动刷新即可。这些场景下,Spring Cache 就是神器,能大幅度提升研发效率。
但在高并发大数据量的场景下,精细的缓存颗粒度的控制上,还是需要做功能扩展。
多级缓存;
缓存定期刷新;
列表缓存;
缓存 cpp 保护机制;
缓存计数。
例如:Spring Cache 并没有二级缓存的实现
![](https://static001.geekbang.org/infoq/35/357001a4a8d96cc268629f674cd930f6.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
四、自定义 cache 方案
学习 spring cache 框架方案,实现自定义 cache 框架,不仅保留 spring cache 框架的优点,同时实现 spring cache 很多缺失的能力,例如缓存击穿、缓存穿透保护,多级缓存等。
4.1 注解代码示例
![](https://static001.geekbang.org/infoq/ff/ff0852ef4df966ee3136ff5b1c22121f.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
![](https://static001.geekbang.org/infoq/15/155c97a9aade2bff4853f124420117cc.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
4.2 方案架构
![](https://static001.geekbang.org/infoq/7e/7e4291962634da0066b97b13ae9529d0.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
五、写在最后面
借助 spring cache 实现方式,构建自定义缓存框架,扩展了很多注解,例如计数、缓存刷新、列表缓存、分布式锁、多级缓存等,不仅实现了缓存代码和业务代码的分离,同时拓展了 spring 缓存的能力,极大的提升了代码的可读性,降低了缓存代码维护的效率。
版权声明: 本文为 InfoQ 作者【阿里技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/ba6483c49d80fa29e19fb58f3】。文章转载请联系作者。
评论