Java系列 - 缓存方案

cache缓存方案

本文目的:囊括介绍市面通行的缓存方案,为选型做准备。具体集成在文末有推介阅读

缓存是什么?缓存是为了减轻数据库的压力而存在。
比如查询某个数据时,首先从缓存中查找,缓存中没有,才会从数据库中查询,也就一定程度减轻了数据库的压力

/**
 * 这个例子仅仅作为演示,市面上的缓存框架通常都提供通过注解的方式来创建/获取/更新缓存的数据,很方便
 **/
 
puulic UserEntity getUserInfo(){
    // 第一级本地缓存: 从缓存查询
    UserEntity user =  CacheService.use('user','getUser','123456')
    
    if(user==null){

      // 第二级远程缓存: 从Redis查询
      user = redisService.get("userId","123456")

	  if(user==null){
	       // 缓存中都没数据: 从数据库查询
	      user = UserService.getByid("123456")
      }
      
    }

    return user;
}

缓存又分为两种,JVM虚拟机(本地)缓存和远程缓存.其实就是根据 数据存储位置 进行分类。

本地缓存

  • 根据HashMap自实现本地缓存
  • Guava Cache
  • Caffeine
  • Encache
  • Alibaba JetCache
  • memcached
  • Layering Cache 框架

远程缓存

  • Redis

springboot 中默认提供了以通用的缓存接口,因为JAVA语言缓存是有协议规范的,市面上的缓存框架都是根据通行协议编写,所以spring中可以很方便切换具体的缓存实现。

通常的缓存方案都是: 本地缓存 + Redis(远程)缓存 +数据库

Tips: 推介一个二级缓存框架 :J2Cache.当你看玩本文,一眼就知道此框架是怎那么回事儿了

介绍一下:本地缓存

本地缓存仅仅介绍一具自实现的具体实现访问,其他方案仅介绍特性:

  • 根据HashMap自实现本地缓存
  • Guava Cache
  • Caffeine
  • Encache
  • JetCache
  1. 根据HashMap自定义实现本地缓存

缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中的HashMap,但是要实现缓存,还需要考虑并发安全性、容量限制等策略,下面简单介绍一种利用LinkedHashMap实现缓存的方式。

LinkedHashMap维持了一个链表结构,用来存储节点的插入顺序或者访问顺序(二选一),并且内部封装了一些业务逻辑,只需要覆盖removeEldestEntry方法,便可以实现缓存的LRU淘汰策略。

此外我们利用读写锁,保障缓存的并发安全性。需要注意的是,这个示例并不支持过期时间淘汰的策略。

自实现缓存的方式,优点是实现简单,不需要引入第三方包,比较适合一些简单的业务场景。对于比较复杂的场景,建议使用比较稳定的开源工具。

public class LRUCache extends LinkedHashMap {

    /**
     * 可重入读写锁,保证并发读写安全性
     */
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock readLock = readWriteLock.readLock();
    private Lock writeLock = readWriteLock.writeLock();

    /**
     * 缓存大小限制
     */
    private int maxSize;

    public LRUCache(int maxSize) {
        super(maxSize + 1, 1.0f, true);
        this.maxSize = maxSize;
    }

    @Override
    public Object get(Object key) {
        readLock.lock();
        try {
            return super.get(key);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public Object put(Object key, Object value) {
        writeLock.lock();
        try {
            return super.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return this.size() > maxSize;
    }
}
  1. Guava Cache

基于Guava Cache实现本地缓存。Guava是Google团队开源的一款 Java 核心增强库

特性:

  • 支持最大容量限制
  • 支持两种过期删除策略(插入时间和访问时间)
  • 支持简单的统计功能
  • 基于LRU算法实现
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>
  1. Caffeine

Caffeine是以 GuavaCache 为原型而开发的一个本地缓存框架, 相对GuavaCache, 它有更高的性能与命中率, 更强大的功能, 更灵活的配置方式

特性:

  • 具备GuavaCache几乎所有特性, 并提供了适配器, 方便Guava用户迁移至Caffeine, 两者差异见: https://github.com/ben-manes/caffeine/wiki/Guava-zh-CN
  • 通过异步维护/异步通知/异步刷新等方式, 达到了极致的读写性能
  • 实现了JSR-107 JCache接口API
  1. EhCache

EhCache是一个轻量级开源的缓存框架, Hibernate使用的默认缓存框架就是EhCache, 它支持多种缓存模型, 将缓存管理在 堆内 / 堆外 / 磁盘 / 远程 多地.

特性:

  • 实现了JSR107的规范, 并支持无缝集成 SpringCache/Hibernate/Tomcat等
  • 轻量级核心, 除slf4j外无其他依赖
  • 支持 堆内内存 / 堆外内存 / 磁盘 / 远程缓存服务 三层存储
  • 支持多种缓存模型: 独立缓存 / 分布式缓存 / 复制式缓存 , 具体描述如下:
  • 独立缓存: 每个应用实例在本地维护缓存, 意味着在其中一个实例中修改了缓存, 会导致与其他实例的缓存信息不一致
  • 分布式缓存: 每个应用实例本地维护少量热点缓存, 并有一个远程缓存服务端来管理更多的缓存信息, 本地缓存未命中时则请求远程服务获取缓存信息, 这解决了缓存空间的问题, 但也无法保证实例间的本地缓存一致性
  1. JetCache

JetCache 是阿里开源的通用缓存访问框架, 它统一了多级缓存的访问方式, 封装了类似于SpringCache的注解, 以及GuavaCache类似的Builder, 来简化项目中使用缓存的难度

特性:

  • 提供统一的, 类似jsr-107风格的API访问Cache, 并可通过注解创建并配置Cache实例
  • 提供类似SpringCache风格的注解, 实现声明式的方法缓存, 并支持TTL和两级缓存
  • 支持缓存自动刷新与家在保护, 防止高并发下缓存未命中时打爆数据库
  • 支持缓存的统计指标

缓存一致性

上面介绍了几种本地缓存方案,选哪种你完全可以根据自己的熟练度来,如果都不熟悉就是用默认spring cache即可。

但其实在工作中,很多人偷懒,本地缓存也不加,只添加redis来做一级远程缓存,如果redis中没有,那么再从数据库中查询。

因为使用了缓存技术后,通常都涉及一个问题:缓存一致性.

在这里插入图片描述

这个问题很头疼,涉及到你对系统架构的理解,以及根据架构去选择合适的一致性方案。所以通常能少上一点缓存手段,对于开发就能少一点事儿.

如何解决这个问题,推介阅读文末文章。

通过注解方式使用

介绍一下 Encache 的注解使用方式

  • @Cacheable:启用缓存,首先从缓存中查找数据,如果存在,则从缓存读取数据;如果不存在,则执行方法,并将方法返回值添加到缓存
  • @CachePut:更新缓存,如果 condition 计算结果为 true,则将方法返回值添加到缓存中
  • @CacheEvict:删除缓存,根据 value 与 key 字段计算缓存地址,将缓存数据删除

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDAO userDAO;

    @Override
    @Cacheable(value = "user", key = "#userId")
    public User findById(Integer userId) {
        return userDAO.findById(userId);
    }

    @Override
    @CachePut(value = "user", key = "#user.id", condition = "#user.id != null")
    public User save(User user) {
        user.setUpdateTime(new Date());
        userDAO.save(user);
        return userDAO.findById(user.getId());
    }

    @Override
    @CacheEvict(value = "user", key = "#userId")
    public boolean deleteById(Integer userId) {
        return userDAO.deleteById(userId);
    }

    @Override
    public List<User> findAll() {
        return userDAO.findAll();
    }
}
其他阅读

------ 如果文章对你有用,感谢右上角 >>>点赞 | 收藏 <<<

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《大话Java性能优化》是一本关于提升Java应用程序性能的指南。这本书通过深入讲解Java程序的运行原理、关键性能指标和常见性能问题,提供了一系列实用的优化技巧和工具的使用方法,帮助读者更好地理解和应用性能优化的方法和技术。 首先,这本书详细介绍了Java程序的运行原理,包括Java虚拟机(JVM)的内部结构、垃圾回收机制、类加载和字节码执行等关键概念。通过深入了解Java运行机制,读者可以更好地理解性能优化的原理和方法。 其次,书中重点讲解了常见的Java性能问题,并提供了对应的解决方案。作者通过实例和案例分析,指导读者如何寻找和修复内存泄漏、线程竞争、不合理的IO操作等常见问题,从而提升程序的性能和稳定性。 除了问题解决方案,这本书还介绍了一些性能优化的基本原则和技巧。例如,作者讲解了如何选择合适的数据结构、如何优化算法、如何合理使用多线程等。这些优化原则和技巧可以帮助读者在开发过程中避免常见的性能问题,并提升程序的效率和响应能力。 此外,书中还介绍了一些常用的性能优化工具和技术,如性能测试工具、代码分析工具和调优工具等。通过使用这些工具,读者可以对自己的程序进行性能测试和分析,并找出潜在的性能瓶颈。同时,书中还介绍了一些常用的性能调优技术,如缓存技术、异步处理和负载均衡等,帮助读者优化自己的开发和运维流程。 总之,《大话Java性能优化》是一本从理论到实践都相当详细和实用的Java性能优化指南。通过学习这本书,读者可以更好地理解和应用Java程序的运行原理和性能优化技巧,提升自己的开发水平和应用程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值