后端系统的缓存使用浅谈
来源:互联网 发布:桃园恋歌动作数据 编辑:程序博客网 时间:2024/06/11 23:51
作者 | 周明岐
杏仁医生后端负责人,中青年码农,关注 Scala / Go 和团队成长。
1. 什么是缓存
缓存有很多种,从 CPU 缓存、磁盘缓存到浏览器缓存等,本文所说的缓存,主要针对后端系统的缓存。也就是将程序或系统经常要使用的对象存在内存中,以便在使用时可以快速调用,也可以避免加载数据或者创建重复的实例,以达到减少系统开销,提高系统效率的目的。
2. 为什么要用缓存
我们一般都会把数据存放在关系型数据库中,不管数据库的性能有多么好,一个简单的查询也要消耗毫秒级的时间,这样我们常说的 QPS 就会被数据库的性能所限制,我们想要提高QPS,只能选择更快的存储设备。
在日常开发有这样的一种场景:某些数据的数据量不大、不经常变动,但访问却很频繁。受限于硬盘 IO 性能或者远程网络等原因,每次都直接获取会消耗大量的资源。可能会导致我们的响应变慢甚至造成系统压力过大,这在一些业务上是不能忍的,而缓存正是解决这类问题的神器。
但是有一点需要注意,就是缓存的占用空间以及缓存的失效策略,下文也会提到。
使用缓存的场景
对于缓存来说,数据不常变更且查询比较频繁是最好的场景,如果查询量不够大或者数据变动太频繁,缓存也就是失去了意义。
3. 缓存的使用
日常工作使用的缓存可以分为内部缓存和外部缓存。
内部缓存一般是指存放在运行实例内部并使用实例内存的缓存,这种缓存可以使用代码直接访问。
外部缓存一般是指存放在运行实例外部的缓存,通常是通过网络获取,反序列化后进行访问。
一般来说对于不需要实例间同步的,都更加推荐内部缓存,因为内部缓存有访问方便,性能好的特点;需要实例间同步的数据可以使用外部缓存。
下面对这两种类型的缓存分别的进行介绍。
3.1 内部缓存
为什么要是用内部缓存
在系统中,有些数据量不大、不常变化,但是访问十分频繁,例如省、市、区数据。针对这种场景,可以将数据加载到应用的内存中,以提升系统的访问效率,减少无谓的数据库和网路的访问。
内部缓存的限制就是存放的数据总量不能超出内存容量,毕竟还是在 JVM 里的。
最简单的内部缓存 - Map
如果只是需要将一些数据缓存起来,避免不必要的数据库查询,那么 Map 就可以满足。
对于字典型的数据,在项目启动的时候加载到 Map 中,程序就可以使用了,也很容易更新。
// 配置存放的MapMap<String, String> configs = new HashMap<String, String>();// 初始化或者刷新配置的Mappublic void reloadConfigs() { Map<String, String> m = loadConfigFromDB(); configs = m;}// 使用configs.getOrDefault("auth.id", "1");
功能强大的内部缓存 - Guava Cache / Caffeine
如果你需要缓存有强大的性能,或者对缓存有更多的控制,可以使用 Guava 里的 Cache 组件。
它是 Guava 中的缓存工具包,是非常简单易用且功能强大的 JVM 内缓存,支持多种缓存过期策略。
LoadingCache<String, String> configs = CacheBuilder.newBuilder()
.maximumSize(1000) // 设置最大大小 .expireAfterWrite(10, TimeUnit.MINUTES) // 设置过期时间, 10分钟 .build( new CacheLoader<String, String>() { // 加载缓存内容 public String load(String key) throws Exception { return getConfigFromDB(key); } public Map<String, String> loadAll() throws Exception { return loadConfigFromDB(); } }); //CacheLoader.loadAll// 获取某个key的值try { return configs.get(key);} catch (ExecutionException e) { throw new OtherException(e.getCause());
}
// 显式的放入缓存
configs.put(key, value)
// 个别清除缓存
configs.invalidate(key)
// 批量清除缓存
configs.invalidateAll(keys)
// 清除所有缓存项
configs.invalidateAll()
本地缓存的优点:
直接使用内存,速度快,通常存取的性能可以达到每秒千万级
可以直接使用 Java 对象存取
本地缓存的缺点:
数据保存在当前实例中,无法共享
重启应用会丢失
Guava Cache 的替代者 Caffeine
Spring 5 使用 Caffeine 来代替 Guava Cache,应该是从性能的角度考虑的。从很多性能测试来看 Caffeine 各方面的性能都要比 Guava 要好。
Caffeine 的 API 的操作功能和 Guava 是基本保持一致的,并且 Caffeine 为了兼容之前 Guava 的用户,做了一个 Guava 的 Adapter, 也是十分的贴心。
如果想了解更多请参考:是什么让 Spring 5 放弃了使用 Guava Cache?
3.2 外部缓存
最著名的外部缓存 - Redis / Memcached
也许是 Redis 太有名,只要一提到缓存,基本上都会说起 Redis。但其实这类缓存的鼻祖应该是 LiveJournal 开发的 Memcached。
Redis / Memcached 都是使用内存作为存储,所以性能上要比数据库要好很多,再加上Redis 还支持很多种数据结构,使用起来也挺方便,所以作为很多人的首选。
Redis 确实不错,不过即便是使用内存,也还是需要通过网络来访问,所以网络的性能决定了 Reids 的性能;
我曾经做过一些性能测试,在万兆网卡的情况下,对于 Key 和 Value 都是长度为 20 Byte 的字符串的 get 和 set 是每秒10w左右的,如果 Key 或者 Value 的长度更大或者使用数据结构,这个会更慢一些;
作为一般的系统来使用已经绰绰有余了,从目前来看,Redis 确实很适合来做系统中的缓存。
如果考虑多实例或者分布式,可以考虑下面的方式:
Jedis 的 ShardedJedis( 调用端自己实现分片 )
twemproxy / codis( 第三方组件实现代理 )
Redis Cluster( 3.0 之后官方提供的集群方案 )
这些方案各有特点,这次先不展开讨论,有兴趣的可以先研究一下。
Redis有很多优点:
很容易做数据分片、分布式,可以做到很大的容量
使用基数比较大,库比较成熟
同时也有一些缺点:
Java 对象需要序列化才能保存
如果服务器重启,再不做持久化的情况下会丢失数据,即使有持久化也容易出现各种各样的问题
4. 缓存的更新策略
使用缓存时,更新策略是非常重要的。最常见的缓存更新策略是 Cache Aside Pattern:
失效:应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从 cache 中取数据,取到后返回。
更新:先把数据存到数据库中,成功后,再让缓存失效。
不管是内部缓存还是外部缓存,都可以使用这样的更新策略,如果缓存系统支持,也可以通过设置过期时间来更新缓存。
更多的更新策略可以参考左耳朵耗子的这篇缓存更新的套路。
5. 缓存使用常见误区
序列化方案的选择
序列化的选择,尽量避免使用 Java 原生的机制,因为原生的序列化依赖 serialVersionUID 来判断版本,如果改变就无法正常的反序列化。
一般推荐使用 Json 或者 Hessian、ProtoBuf 等二进制方式。
缓存大对象
在缓存中存放大对象,存取的代价都比较高。实际使用时,往往只是需要其中的一部分,这样会导致每一次读取都消耗更多的网络和内存资源,也会浪费缓存的容量。
当然如果每次都是用完整的对象,这样做是没有问题的。
使用缓存进行数据共享
使用缓存来当作线程甚至进程之间的数据共享方式,会让系统间产生隐形的依赖,并且也可能会产生一些竞争,常常会发生问题。所以不推荐使用这种方式来共享数据。
没有及时更新或者删除缓存中已经过期或失效的数据
这个理解起来就很简单了,如果没有及时更新或者删除,就有可能读取到错误的数据,从而导致业务的错误。
对于支持设置过期时间的缓存系统,可以对每一个数据设置合适的过期时间,来尽量避免这样的情况。
全文完
以下文章您可能也会感兴趣:
乐高式微服务化改造(上)
乐高式微服务化改造(下)
一个创业公司的容器化之路(一) - 容器化之前
一个创业公司的容器化之路(二) - 容器化
一个创业公司的容器化之路(三) - 容器即未来
四维阅读法 - 我的高效学习“秘技”
工程师成长的必备技能
iOS 屏幕适配浅谈
Web 与 App 数据交互原理和实现
响应式编程(上):总览
响应式编程(下):Spring 5
苹果在医疗健康领域的三个 Kit
聊聊移动端跨平台数据库 Realm
复杂业务状态的处理:从状态模式到 FSM
如何成为一名数据分析师:数据的初步认知
从 React 到 Preact 迁移指南
杏仁技术站
长按左侧二维码关注我们,这里有一群热血青年期待着与您相会。
- 后端系统的缓存使用浅谈
- 浅谈缓存的使用
- 浅谈 Web 中前后端模板引擎的使用
- 浅谈SpaceBuilder系统的缓存机制_缓存思想
- 解决PHP后端生成的图片无法使用CDN缓存的方法
- 后端开发-关于缓存的理解
- 浅谈Android系统中drawable的使用
- 使用 Java 缓存系统缓存频繁查看的数据
- 大型web系统中缓存的使用
- 大型web系统中缓存的使用
- 缓存__浅谈Discuz的缓存机制
- 浅谈CPU的缓存策略
- 浅谈Discuz的缓存机制
- 浅谈浏览器的缓存机制
- 【redis-demo】使用Jedis api 实现后端缓存优化
- Bmob后端云的使用
- 后端云Bomb的使用
- 后端云Bomb的使用
- 09_脏读
- TextView文本展开缩放
- python3安装与Ipython notebook的安装【Linux】
- oracle入门很简单:八、oracle数据表
- 常见算法基础题思路简析(一)-排序篇
- 后端系统的缓存使用浅谈
- SSIS学习(二)
- changePassword
- Android 日历归档
- 关于junnit测试
- HTTP遭弃用,微信公众平台将停止调用http!
- ORACLE快速增量备份技术
- Keras快速上手——打造个人的第一个“圣诞老人”图像分类模型
- 【nodejs express框架】一些小结