缓存 | 从本地缓存到分布式缓存, Guava, Caffeine, Memcached, Redis
从本地缓存到分布式缓存
本文档中部分代码不保证可以运行
虽然标题为缓存,但在这里不仅仅会涉及缓存,还会涉及一些其他提高应用性能的方案。
在程序设计中,经常能听到的就是以时间换空间
和以空间换时间
。缓存
作为一种能加快程序性能的银弹,它是典型的后者(以空间换时间
).
随着用户数和访问量越来越大,我们的应用需要支撑更多的并发量,同时我们的应用服务器和数据库服务器所做的计算也越来越多。但是往往我们的应用服务器资源是有限的,数据库每秒能接受的请求次数也是有限的(或者文件的读写也是有限的),如何能够有效利用有限的资源来提供尽可能大的吞吐量?一个有效的办法就是引入缓存,每个环节中请求可以从缓存中直接获取目标数据并返回,从而减少计算量,有效提升响应速度,让有限的资源服务更多的用户。
缓存并不是包治百病的银弹
第一次接触缓存`MAP`
我第一次接触缓存的时候是在大三开始出去工作的时候。在一个系统中,基本每个接口都有可能要获取一次用户信息和一些用户配置,当时我们的系统查多改少,这也注定缓存可以大大提高我们的性能,当时的做法是维护一个全局的单例的Map
作为缓存存储.记得当时的类名叫DBMirror
大致如下:
class DBMirror {
private static Map<String, User> userCache = new HashMap<>();
public static void putUser(String key, User user) {
userCache.put(key, user);
}
public static User getUser(String key) {
return userCache.get(key);
}
private DBMirror() {}
}
代码很简单,基本满足了当时系统的要求,减少了很多数据库读写操作,在当时也是第一次开始意识到 数据库
并不是唯一的存储. 原来 Map
还能这样使用
但是上面的代码有个很大的缺点,随着用户的增多,里面并没有合适的剔除算法,会导致 Map
越来越大,极端情况会导致内存溢出
常见淘汰策略
如上所述,如果不使用剔除算法,会导致内存占用越来越大,且无法回收,那下面讲一下常见的淘汰策略
FIFO(first in first out)
先进先出策略,最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制)会被优先被清除掉,以腾出新的空间接受新的数据。
LFU(less frequently used)
最少使用策略,根据元素的被使用次数判断,清除使用次数较少的元素释放空间。
LRU(least recently used)
最近使用策略,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素释放空间。
其他
- 为缓存元素设置过期时间,清理超过过时时间的元素
- 随机清理
- 优先清理大对象
缓存简单分类
本地缓存:指的是在应用中的缓存组件,其最大的优点是应用和cache是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适;同时,它的缺点也是因为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。
分布式缓存:指的是与应用分离的缓存组件或服务,其最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接的共享缓存。缺点是:优点也就是缺点,因为自身是一个独立的应用,本地节点都需要与其进行通信,导致依赖网络,同时如果缓存服务崩溃可能会影响所有依赖节点
对于一些单个实例的服务,或者数据基本不会变化的数据都可以使用本地缓存来提高性能,反之可以使用分布式缓存
技术方案本身没有最好的,只有最合适的.
缓存的使用
Java集合类
在上面提供了一个简单的例子,DBMirror
使用Map
来实现一个简单的内存缓存,同时Set
、List
都可以达到内存缓存的功能,根据并发情况可以选择不同的实现类,例如HashMap
、LinkedHashMap
、TreeMap
、LinkedTreeMap
、ConcurrentHashMap
… 总有一个满足你
这样实现很简单,但是也致命缺点:无法回收不常用的缓存
Guava Cache
说起 Guava, 很多人都不会陌生,它是 Google 提供的一个非常好用的 Java 工具包。Guava Cache 是 Guava 中的一个本地缓存实现,基于LRU算法实现,并提供了多种缓存过期策略,过期时间、容量等. 简化了缓存的使用,方便我们更加大胆的使用缓存
Caffeine
Caffeine是一个基于 Java8 开发的提供了近乎最佳命中率的高性能的缓存库。
在本地缓存方面,SpringFramework5.0(SpringBoot2.0)放弃了Google的GuavaCache,选择了「Caffeine」(Drop Guava caching - superseded by Caffeine [SPR-13797] #18370)。足以见证其在性能和可靠性上的优势.
其性能测试可以查看 https://github.com/ben-manes/caffeine/wiki/Benchmarks
Ehcache
Ehcache是纯Java开源缓存框架,配置简单、结构清晰、功能强大,是一个非常轻量级的缓存实现,我们常用的Hibernate里面就集成了相关缓存功能。
在早期开发的时候也用过这个,现在不知道是否还在使用
Memcached
一个高性能的、分布式的基于内存的key-value对象存储系统,用来存储小块的任意数据(字符串、对象)
通过访问其来较少数据库的读写压力
Redis
Redis 同样是一个高性能的基于内存中数据结构存储,用作数据库,缓存和消息代理。
它支持更多的数据结构,例如 strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes。
Redis具有内置的复制,Lua脚本,LRU逐出,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性
Spring Cache
Spring Cache 并不是缓存的实现,而是一个缓存管理的抽象解决方案,这种方案消除了样板方法的使用,屏蔽了缓存的使用细节,而这是 Spring 最擅长干的.
Spring 的缓存技术还具备相当的灵活性,可以使用 SpEL 来定义缓存的 key 和各种 condition,提供了灵活的开箱即用的解决方案.
注意事项
在使用缓存的过程中,我们还要注意缓存不一致、缓存穿透、缓存击穿与缓存雪崩等问题,每种问题都是不小的问题
这篇写的并不长,每种都是简单介绍了一下,马上分几篇分别介绍一下各自的具体使用方法,敬请期待
参考
- spring cache
- https://github.com/google/guava/wiki/CachesExplained
- https://github.com/ben-manes/caffeine
- https://www.memcached.org/
- https://redis.io/
- 企业级Tomcat安全管理优化方案
- “妈妈”域名Mommy.com66万高价易主 现已建站
- 脸书Messenger已中招,新的加密货币挖矿病毒出现!
- Linux中MySQL配置文件my.cnf参数优化
- 干货,比较全面的c#.net公共帮助类(Common.Utility)
- Linux中如何恢复rm命令误删除的文件之extundelete编译安装及使用
- JDK1.7 安装加(一劳永逸的环境配置)
- Jquery 遍历数组之$().each方法与$.each()方法介绍
- ExtJs学习笔记(2)_Basic GridPanel[基本网格]
- Jquery 遍历数组之grep()方法介绍
- ExtJs学习笔记(1)_Hello World!
- Jquery filter()方法简介
- 加拿大滑铁卢大学刘腾博士:平行增强学习及其无人驾驶应用
- 曾六位数被秒的yadea.com 终端真是雅迪!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用镜像部署 Hexo 静态页面
- 基于zmq RPC简单C++实现
- App为什么会crash?一篇文章带你探究根本原因 ,事情没有你想得那么简单!
- MySQL 8.0新特性 — 用户管理
- Java15的新特性
- ZooKeeper到底为Kafka的做了什么牺牲?
- Flutter原理:三棵重要的树(渲染过程、布局约束、应用视图的构建等)
- FlutterDojo设计之道—状态管理之路(六)
- Day9.函数进阶
- 关于requests.exceptions.SSLError: HTTPSConnectionPool
- Day10.高阶函数介绍
- python提示警告InsecureRequestWarning
- Day11.类和对象这回事儿
- Day12.魔法方法&方法重写
- Linux基础第一课——基础知识了解