背景

在高性能的服务架构设计中,缓存是一个不可或缺的环节。在实际的项目中,我们通常会将一些热点数据存储到Redis或Memcached 这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库。在提升访问速度的同时,也能降低数据库的压力。

随着不断的发展,这一架构也产生了改进,在一些场景下可能单纯使用Redis类的远程缓存已经不够了,还需要进一步配合本地缓存使用,例如Guava cache或Caffeine,从而再次提升程序的响应速度与服务性能。于是,就产生了使用本地缓存作为一级缓存,再加上远程缓存作为二级缓存的两级缓存架构。

在先不考虑并发等复杂问题的情况下,两级缓存的访问流程可以用下面这张图来表示:

为什么要使用本地缓存

  • 本地缓存基于本地环境的内存,访问速度非常快,对于一些变更频率低、实时性要求低的数据,可以放在本地缓存中,提升访问速度
  • 使用本地缓存能够减少和Redis类的远程缓存间的数据交互,减少网络I/O开销,降低这一过程中在网络通信上的耗时

设计一个本地内存需要有什么功能

  1. 存储;并可以读、写;
  2. 原子操作(线程安全),如ConcurrentHashMap
  3. 可以设置缓存的最大限制;
  4. 超过最大限制有对应淘汰策略,如LRU、LFU
  5. 过期时间淘汰,如定时、懒式、定期;
  6. 持久化
  7. 统计监控

本地缓存方案选型

1. 使用ConcurrentHashMap实现本地缓存

缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中线程安全的ConcurrentHashMap,但是要实现缓存,还需要考虑淘汰、最大限制、缓存过期时间淘汰等等功能;

优点是实现简单,不需要引入第三方包,比较适合一些简单的业务场景。缺点是如果需要更多的特性,需要定制化开发,成本会比较高,并且稳定性和可靠性也难以保障。对于比较复杂的场景,建议使用比较稳定的开源工具。

2. 基于Guava Cache实现本地缓存

Guava是Google团队开源的一款 Java 核心增强库,包含集合、并发原语、缓存、IO、反射等工具箱,性能和稳定性上都有保障,应用十分广泛。Guava Cache支持很多特性:

  • 支持最大容量限制
  • 支持两种过期删除策略(插入时间和访问时间)
  • 支持简单的统计功能
  • 基于LRU算法实现

使用代码如下:

 <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);// 获取orderInfo,如果key不存在,callable中调用getInfo方法返回数据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缓存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}
}

3. Caffeine

Caffeine是基于java8实现的新一代缓存工具,缓存性能接近理论最优。可以看作是Guava Cache的增强版,功能上两者类似,不同的是Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,在性能上有明显的优越性

使用代码如下:

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.9.3</version>
</dependency>
@Slf4j
public class CaffeineTest {public static void main(String[] args) {Cache<String, String> cache = Caffeine.newBuilder().initialCapacity(5)// 超出时淘汰.maximumSize(10)//设置写缓存后n秒钟过期.expireAfterWrite(60, TimeUnit.SECONDS)//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite//.expireAfterAccess(17, TimeUnit.SECONDS).build();String orderId = String.valueOf(123456789);String orderInfo = cache.get(orderId, key -> getInfo(key));System.out.println(orderInfo);}private static String getInfo(String orderId) {String info = "";// 先查询redis缓存log.info("get data from redis");// 当redis缓存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}
}

4. Encache

Encache是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。同Caffeine和Guava Cache相比,Encache的功能更加丰富,扩展性更强:

  • 支持多种缓存淘汰算法,包括LRU、LFU和FIFO
  • 缓存支持堆内存储、堆外存储、磁盘存储(支持持久化)三种
  • 支持多种集群方案,解决数据共享问题

使用代码如下:

 <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()// 创建cache实例.withCache(ORDER_CACHE, CacheConfigurationBuilder// 声明一个容量为20的堆内缓存.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))).build(true);// 获取cache实例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");// 当redis缓存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}
}

本地缓存问题及解决

1. 缓存一致性

两级缓存与数据库的数据要保持一致,一旦数据发生了修改,在修改数据库的同时,本地缓存、远程缓存应该同步更新。

解决方案1: MQ

一般现在部署都是集群部署,有多个不同节点的本地缓存; 可以使用MQ的广播模式,当数据修改时向MQ发送消息,节点监听并消费消息,删除本地缓存,达到最终一致性;

解决方案2:Canal + MQ

如果你不想在你的业务代码发送MQ消息,还可以适用近几年比较流行的方法:订阅数据库变更日志,再操作缓存。Canal 订阅Mysql的 Binlog日志,当发生变化时向MQ发送消息,进而也实现数据一致性。

2. 如何提供本地缓存命中率

3. 本地内存的技术选型问题

  • 从易用性角度,Guava Cache、Caffeine和Encache都有十分成熟的接入方案,使用简单。
  • 从功能性角度,Guava Cache和Caffeine功能类似,都是只支持堆内缓存,Encache相比功能更为丰富
  • 从性能上进行比较,Caffeine最优、GuavaCache次之,Encache最差(下图是三者的性能对比结果)

对于本地缓存的方案中,我比较推荐Caffeine,性能上遥遥领先。虽然Encache功能更为丰富,甚至提供了持久化和集群的功能,但是这些功能完全可以依靠其他方式实现。真实的业务工程中,建议使用Caffeine作为本地缓存,另外使用redis或者memcache作为分布式缓存,构造多级缓存体系,保证性能和可靠性。

本地缓存:为什么要用本地缓存?用它会有什么问题?相关推荐

  1. java 项目做多级缓存_【开源项目系列】如何基于 Spring Cache 实现多级缓存(同时整合本地缓存 Ehcache 和分布式缓存 Redis)...

    一.缓存 当系统的并发量上来了,如果我们频繁地去访问数据库,那么会使数据库的压力不断增大,在高峰时甚至可以出现数据库崩溃的现象.所以一般我们会使用缓存来解决这个数据库并发访问问题,用户访问进来,会先从 ...

  2. Android图片三级缓存(网络,本地,内存)介绍及简单实现

    三级缓存使用的必要性 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量.在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响 当我们想要重复 ...

  3. 网卡清空缓存命令_怎么清除dns缓存 查看与刷新本地DNS缓存方法 (全文)

    由于近几日百事网更换了cdn加速服务商,导致不少地区朋友无法访问百事网,包括小编电脑也是经常打不开,目前主要可以通过清除dns缓存来解决.一般来说,电脑在第一次访问一个网站后,在一定时间内会有本地DN ...

  4. android自定义图片缓存,适用于Android的本地图像缓存解决方案:Squ...

    更新于2018年9月:几年后,我需要与本地图像缓存解决方案几乎相同的东西.这一次,UIL尚未积极开发.我比较了流行的库,结论很简单:只需使用Glide.它功能强大且可配置.多年前我不得不分叉并对UIL ...

  5. 【GPU结构与CUDA系列4】GPU存储资源:寄存器,本地内存,共享内存,缓存,显存等存储器细节

    0 软件抽象和硬件结构对应关系的例子 把GPU跟一个学校对应起来,学校里有教学楼.操场.食堂,还有老师和学生们:很快有领导(CPU)来检查卫生(需要执行的任务Host程序),因此这个学校的学生们要完成 ...

  6. Redis 缓存穿透、雪崩、缓存数据库不一致、持久化方式、分布式锁、过期策略

    1. Redis 缓存穿透 1.1 Redis 缓存穿透概念 访问了不存在的 key,缓存未命中,请求会穿透到 DB,量大时可能会对 DB 造成压力导致服务异常. 由于不恰当的业务功能实现,或者外部恶 ...

  7. ubuntu 14.04 64 bit上开启nscd服务缓存加速及清除dns缓存

    简介 linux本身是没有dns缓存的,想使用dns缓存的话需要自己安装一个服务程序NSCD(name service cache daemon).  Nscd会缓存libc接口(比如 getpwna ...

  8. 看完这部缓存进化史,还不懂缓存,请给我差评

    作者:咖啡拿铁,现就职于美团点评,后端研发 来自:公众号咖啡拿铁(ID:code_3092860495) 1 背景 本文是上周去技术沙龙听了一下爱奇艺的Java缓存之路有感写出来的.先简单介绍一下爱奇 ...

  9. 同时存多个变量缓存 微信小程序_CPU缓存一致性协议MESI,memory barrier和java volatile...

    MESI协议 MESI协议是一个被广泛使用的CPU缓存一致性协议.我们都知道在CPU中存在着多级缓存,缓存级别越低,容量就越小,速度也越快.有了缓存,CPU就不需要每次都向主存读写数据,这提高了CPU ...

  10. Java内存缓存-通过Google Guava创建缓存

    谷歌Guava缓存 Guava介绍 Guava是Google guava中的一个内存缓存模块,用于将数据缓存到JVM内存中.实际项目开发中经常将一些公共或者常用的数据缓存起来方便快速访问. Guava ...

最新文章

  1. 操作系统 作业调度实验报告
  2. 关于配置Bhuman通用平台环境心得
  3. 计算机英语作文句子,英语作文经典句子
  4. android url回调json,【求助】本地页面如何取某个URL返回的json
  5. [No0000D6]端口-进程查询.bat
  6. 主从模式在不同场景下的解释
  7. CNN图像识别_算法篇
  8. 360搜索核心算法,被K后如何恢复?
  9. TOEFL wordlist 17
  10. HTML5尚未迎来爆发:标准不统一日益碎片化
  11. CIO圈子里的“老行家”:太平绅士赖锡璋
  12. 程序员应该每天写代码
  13. linux 浏览器崩溃,Firefox DoS漏洞导致浏览器崩溃 影响到Windows操作系统
  14. 字节跳动校招提前批面试
  15. Cocos别踩白块儿案列1
  16. Deepin Linux设置环境变量时出现【Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp】的解决方法
  17. d191虚拟服务器,dlink无线路由器的端口映射设置教程
  18. python打开本地浏览器_python如何实现打开浏览器
  19. 七步教你从0到1创建客户服务团队
  20. xp 计算机信息服务器,个人电脑xp搭建云服务器

热门文章

  1. 泊松分布与美国枪击案
  2. 淘宝秒杀助手-小助手可以用在聚划算秒杀,付定金秒杀,百亿补贴秒杀
  3. linux setsockopt
  4. 如何使用手机APP进行库房的库存管理
  5. error: ‘FILE‘ undeclared (first use in this function)
  6. 西数云存储 重置 使用手册_黑莓BB10、BBOS系统手机重置复位手机方法
  7. 力扣1884. 鸡蛋掉落-两枚鸡蛋
  8. linux查看磁盘硬件日志,Linux下如何查看硬件信息
  9. 《念奴娇.赤壁怀古》
  10. 谨防上当:揭露那些披着大数据外衣的假大数据课程