微服务手册:高速查询?除了Redis我们还有另外的选择

互联网应用架构:专注编程教学,架构,JAVA,Python,微服务,机器学习等领域,欢迎关注,一起学习。

微服务手册:高速查询?除了Redis我们还有另外的选择

 

前言

现在可以说缓存是Redis横行的时代,无论什么项目一提到缓存就上Redis,好比无论啥项目一来就是微服务架构。其实这些想法都无可否非,但是关键是如何因地制宜去处理现有的项目问题,主要是看应用的多大。

缓存如何选

技术方面:在面对一些分布式系统的时候,我们这时候由于需要作缓存,这时可以采用redis来作为缓存的中间存储地,如果面对一些简单的项目应用,我们可以考虑Spring官方推荐的Caffeine(咖啡因)来实现。

项目方面:很多时候项目的开发进度都是非常赶的,如何在有限的时间做出当前最优化的选择,这是我们需要考虑的问题。

资源方面:很多公司在人力资源,技术资源,服务器资源等的沉淀并没有,进行架构设计的时候需要考虑这些因素进去,举个例子,只有2台服务器,4~5个开发人员,项目比较紧,此时就要考虑人员的学习成本,服务器的资源成本,毕竟网络消耗也是需要我们考虑进去的。

微服务手册:高速查询?除了Redis我们还有另外的选择

 

官方推荐Caffeine

在Caffeine出现之前,本地缓存经常使用Google Guava来实现,它提供了一个非常简便易用的本地缓存来实现,这个实现是基于LRU算法,同时支持多种缓存过期的策略。Caffeine是在Java8里面,有人采用Java8对缓存进行重写的版本,可以理解为一个升级版,同样基于LRU算法并且支持多种缓存过期策略,在SpringBoot2.0的时代,官方把它作为默认的缓存工具。

在更早之前还有用EhCache,这个也是非常优秀的进程内缓存框架,是Hibernate默认的集成工具。

Caffeine驱逐策略

这里着重讲一下驱逐策略,笔者认为这一块会比较重要,主要涉及到缓存的命中问题,这才是缓存的根本所在。缓存中的驱逐策略主要是预测了哪些数据可以在短期之内被再次用到,因而增加了缓存的命中率,LRU(最少最近使用)算法是现在最流行的驱逐策略,但是这个算法有一定的确定,因为默认会认为最后到达的数据最有可能被访问,因此会授予它最高级别的优先,这对其他数据是不公平的,这个特点看起来有点像分类里面经常遇到的不平衡分类问题。

针对这种情况,Caffeine也进行了改进,提供了三种驱逐策略

1、大小策略

Caffeine针对大小策略提供了两种方式,一种是基于缓存大小,另外一种是基于权重。

注意:maximumSize与maximumWeight这两个不能同时使用

// 基于缓存大小
Caffeine.maximumSize(long)
// 基于权重
Caffeine.maximumWeight(long) 

2、时间策略

时间策略主要有三个方法,

// 自缓存条目最后一次读取或写入时开始,如果超过了该方法设定的时长,标记条目过期
Caffeine.expireAfterAccess()
// 自缓存条目创建或最后一次写入的时间点开始,如果超过了该方法设定的时长,标记条目过期
Caffeine.expireAfterWrite()
// 在可变持续时间过去之后标记条目过期
Caffeine.expireAfter(Expiry)

3、引用策略

在开始介绍引用策略之前,先来了解一下各个引用之间的区别,从表格我们

微服务手册:高速查询?除了Redis我们还有另外的选择

 

针对不同的引用,我们设置不同的策略进行驱逐。

微服务手册:高速查询?除了Redis我们还有另外的选择

 

自动化缓存

现在很多时候,项目总是与MyBatis进行整合对数据库进行操作,那我们现在针对Caffeine进行整合,让每次查询的时候都可以走缓存

引入需要的项目包


		<!-- SpringBoot缓存组件 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
			<version>${spring-boot.version}</version>
		</dependency>

		<!-- 本地缓存caffeine组件 -->
		<dependency>
			<groupId>com.github.ben-manes.caffeine</groupId>
			<artifactId>caffeine</artifactId>
			<version>${caffeine.version}</version>
		</dependency>

		<!--mybatis整合 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>${mybatis.version}</version>
		</dependency>

配置Caffeine

@Configuration
@EnableCaching
public class CaffeineConfig {

    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
                // 设置最后一次访问多长时间后过期
                .expireAfterAccess(20, TimeUnit.SECONDS)
                // 初始化缓存空间大小
                .initialCapacity(2048)
                // 初始化缓存最大条数
                .maximumSize(2000)
                .build();
    }
}

继承mybatis的cache并采用Caffeine进行实现

@SuppressWarnings("unchecked")
public class MybatisCaffeineCache implements org.apache.ibatis.cache.Cache{

    @Autowired
    private Cache<String, Object> caffeineCache;

    // 读写锁
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    private final String id;

    public MybatisCaffeineCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        if (caffeineCache == null) {
            // MybatisRedisCache没有注入,采用手动获取caffeineCache对象
            caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
        }
        caffeineCache.put(key.toString(), value);
    }

    @Override
    public Object getObject(Object key) {
        if (caffeineCache == null) {
            // MybatisRedisCache没有注入,采用手动获取caffeineCache对象
            caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
        }
        return caffeineCache.getIfPresent(key.toString());
    }

    @Override
    public Object removeObject(Object key) {
        if (caffeineCache == null) {
            // MybatisRedisCache没有注入,采用手动获取caffeineCache对象
            caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
        }
        return caffeineCache.asMap().remove(key.toString());
    }

    @Override
    public void clear() {
        if (caffeineCache == null) {
            // MybatisRedisCache没有注入,采用手动获取caffeineCache对象
            caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
        }
        caffeineCache.cleanUp();
    }

    @Override
    public int getSize() {
        if (caffeineCache == null) {
            // MybatisRedisCache没有注入,采用手动获取caffeineCache对象
            caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
        }
        final Long size = caffeineCache.estimatedSize();
        return size.intValue();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
}

如何使用

1. 在Mapper类上加入以下信息
@CacheNamespace(implementation = MybatisCaffeineCache.class, eviction = MybatisCaffeineCache.class)

参考:
@Mapper
@CacheNamespace(implementation = MybatisCaffeineCache.class, eviction = MybatisCaffeineCache.class)
public interface IDataDictDataDao extends BaseMapper<DataDictData> 

2. 在XML文件上加入缓存Mapper,避免缓存无效
<cache-ref namespace="xx.xxx.xxx.xxx.xx.IDataDictDataDao"/>

这样子一个本地缓存就完成了。

缓存拓展

在使用Caffeine的时候虽然很简便,但是也存在一个问题就是分布式问题,那我们这里可以进行拓展,采用redis做二级缓存,同样的直接实现mybatis的cache类即可,与本地缓存类似,这里就不一一拓展开来,有兴趣的同学可以自行实验。

 

--END--

作者:@互联网应用架构

原创作品,抄袭必究

如需要源码或转发,关注后私信我

部分图片或代码来源网络,如侵权请联系删除,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网应用架构

码字不易,各位施主行行好吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值