网站首页 > java教程 正文
在高性能服务架构设计中,缓存是不可或缺的环节。在实际项目中,我们通常会将一些热点数据存储在Redis或Memcached等缓存中间件中,只有在缓存访问未命中时才查询数据库。
在提高访问速度的同时,还可以减轻数据库的压力。
为什么要使用本地缓存?
随着不断的发展,这个架构也得到了完善。在某些场景下,仅仅使用Redis类的远程缓存可能还不够。需要进一步与本地缓存配合使用,比如Guava或者Caffeine,从而再次提高程序的响应速度和服务性能。
由此,形成了以本地缓存作为一级缓存、远程缓存作为二级缓存的二级缓存架构。
总结:
- 本地缓存基于本地环境的内存,访问速度非常快。对于一些变化频率不高、实时性要求不高的数据,可以放在本地缓存中,以提高访问速度。
- 使用本地缓存可以减少与Redis类的远程缓存的数据交互,减少网络I/O开销,减少这个过程中网络通信的耗时。
本地存储的基本功能
- 它可以存储、读取和写入。
- 原子操作(线程安全),例如ConcurrentHashMap。
- 可以设置缓存的最大限制。
- 超过最大限制有相应的淘汰策略,如LRU、LFU。
- 统计监控。
方案选择
1.使用ConcurrentHashMap。
缓存的本质是KV存储在内存中的数据结构,对应JDK中的线程安全ConcurrentHashMap,但是要实现缓存,需要考虑消除、最大限制、消除缓存过期时间等功能。
优点ConcurrentHashMap是实现简单,不需要引入第三方包,所以比较适合一些简单的业务场景。
缺点是如果需要更多的功能,需要定制开发,成本会比较高,稳定性和可靠性难以保证。
对于更复杂的场景,建议使用相对稳定的开源工具。
2. 使用Guava缓存
Guava是Google团队开源的一个Java核心增强库。它包括集合、并发原语、缓存、IO、反射和其他工具箱。性能和稳定性有保证,应用广泛。
- Guava Cache 支持许多功能:
- 支持最大容量限制。
- 支持两种过期删除策略。
- 支持简单的统计功能。 它是基于LRU算法实现的。
示例代码如下
<dependency><dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
@Slf4j
public class GuavaCacheTest {
public static void main(String[] args) throws ExecutionException {
Cache<String, String> cache = CacheBuilder.newBuilder()
.initialCapacity(5) // 初始容量
.maximumSize(10) // 缓存的最大数量,超过该数量将被淘汰
.expireAfterWrite(60, TimeUnit.SECONDS) // 过期时间
.build();
String orderId = String.valueOf(123456789);
String orderInfo = cache.get(orderId, () -> getInfo(orderId));
log.info("orderInfo = {}", orderInfo);
}
private static String getInfo(String orderId) {
String info = "";
// 首先查redis
log.info("get data from redis");
// redis不存在 查db
log.info("get data from mysql");
info = String.format("{orderId=%s}", orderId);
return info;
}
}
3. 使用Encache
Encache是?一个纯Java进程内缓存框架,快速且精简。它是 Hibernate 中默认的 CacheProvider。 与Caffeine和Guava Cache相比,Encache功能更丰富,可扩展性更强。 支持LRU、LFU、FIFO等多种缓存淘汰算法。
缓存支持三种类型:堆内存储、堆外存储、磁盘存储(支持持久化)。
支持多种集群方案,解决数据共享问题。
使用方法如下:
<dependency><dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.7</version>
</dependency>
@Slf4j
public class EhcacheTest {
private static final String ORDER_CACHE = "orderCache";
public static void main(String[] args) {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
// 创建实例
.withCache(ORDER_CACHE, CacheConfigurationBuilder
// 声明一个容量为20的堆内缓存
.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20)))
.build(true);
// 得到缓存实例
Cache<String, String> cache = cacheManager.getCache(ORDER_CACHE, String.class, String.class);
String orderId = String.valueOf(123456789);
String orderInfo = cache.get(orderId);
if (StrUtil.isBlank(orderInfo)) {
orderInfo = getInfo(orderId);
cache.put(orderId, orderInfo);
}
log.info("orderInfo = {}", orderInfo);
}
private static String getInfo(String orderId) {
String info = "";
// 首先从redis查
log.info("get data from redis");
// 不存在 查db
log.info("get data from mysql");
info = String.format("{orderId=%s}", orderId);
return info;
}
}
本地缓存常见问题
1. 缓存一致性。
二级缓存和数据库中的数据必须一致。一旦数据被修改,本地缓存和远程缓存应在数据库修改的同时同步更新。
解决方案1:使用MQ。
目前的部署一般都是集群部署,不同节点有多个本地缓存。 可以利用MQ的广播模式,在数据修改时向MQ发送消息,由节点监听并消费该消息,删除本地缓存,达到最终一致性。
解决方案二:Canal+MQ。
如果你的业务代码中不想发送MQ消息,也可以应用近年来比较流行的方法:订阅数据库变更日志,然后操作缓存。 Canal订阅Mysql的Binlog日志,当发生变化时向MQ发送消息,从而也实现了数据的一致性。
2.如何提高缓存命中率?
- 根据业务场景设计合理的时效性。
缓存适用于读多写少的场景,尽可能关注访问频率高、时效性要求不高的热点业务。 访问频率越高,点击率越高。 时效性越低,缓存时间越长,相同key、相同请求数下命中率越高。
- 设计合理的缓存粒度。
缓存的粒度越小,缓存命中率越高。 单个key缓存的数据单元越小,被改变的可能性就越小。
- 设计合理的缓存过期策略。
这里的缓存过期策略并不是redis内置的定期删除和惰性删除策略,而是根据业务场景优化了key的过期时间。 例如,如果用户的关键信息同时过期,那么当多个用户同时查询时,都会落入数据库,也就是说避免缓存同时失效。
- 合理的缓存预加载。
redis缓存必须从数据库加载,所以当第一次使用数据时,redis需要从数据库加载数据。 我们可以在用户访问之前将需要的数据提前加载到缓存中,这样用户第一次访问时就可以直接去缓存而不用去查询数据库。
- 防止缓存崩溃和击穿。
缓存击穿和崩溃也会影响缓存命中率。当然,如果发生的话,应用失败的可能性很大。
- 设计合理的缓存容量。
注意缓存容量,如果太小,会触发redis内存淘汰机制。线上redis一般配置maxmemory-policy allkeys-lru算法进行内存消除。
这样就会删除一些key,造成缓存穿透,从而降低缓存命中率,所以需要合理配置缓存容量。
如果喜欢这篇文章,点赞支持一下,微信搜索:京城小人物,关注我第一时间查看更多内容!感谢支持!
- 上一篇: 缓存:本地缓存优缺点、适用场景与实现分析(一)
- 下一篇: 如何设计一个本地缓存?(本地缓存的作用)
猜你喜欢
- 2024-10-25 Caffeine高性能本地缓存框架初探(caffeine缓存原理)
- 2024-10-25 Redis学习3——Redis应用之缓存(redis的缓存的使用方式)
- 2024-10-25 Java高级——缓存的使用场景(java缓存机制)
- 2024-10-25 java中常用的几种缓存类型介绍(java常用缓存技术)
- 2024-10-25 本地缓存之王caffeine#代码(本地缓存 js)
- 2024-10-25 skywalking agent 本地缓存队列参数设置
- 2024-10-25 史上最实用的:分布式缓存方案(分布式缓存设计方案)
- 2024-10-25 使用Guava作为本地缓存让系统飞起来
- 2024-10-25 深究分布式缓存的九个点(分布式缓存技术有哪些)
- 2024-10-25 面试题之java缓存总结,从单机缓存到分布式缓存架构
你 发表评论:
欢迎- 最近发表
-
- pyinstaller打包python程序高级技巧
- 将python打包成exe的方式(python打包成exe的方法)
- Python打包:如何将 Flask 项目打包成exe程序
- py2exe实现python文件打包为.exe可执行程序(上篇)
- 如何将 Python 项目打包成 exe,另带卸载功能!
- Python打包成 exe,太大了该怎么解决?
- 可视化 Python 打包 exe,这个神器绝了!
- 案例详解pyinstaller将python程序打包为可执行文件exe
- Cocos 3.x 菜鸟一起玩:打包window程序
- 怎么把 Python + Flet 开发的程序,打包为 exe ?这个方法很简单!
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)