背景:最近在忙规则引擎降级服务,一个从0到1的项目,这个项目是因为原有的规则引擎服务是在本地内网,最近由于网络的迁移导致和阿里云上的前端交易中心经常网络不通,而且由于原有的规则引擎服务的决策引擎内核是厂商提供的,导致在排查问题时困难重重,在经历了几次生产问题之后,公司CTO要求开发一个降级服务,便于在原有规则引擎出问题之后的自动熔断降级切换。我在动手搭建这个项目过程中,遇到过许许多多的小问题,这次先分享下,遇到的使用SpringBoot自带缓存注解失效的问题。因为规则引擎有很多配置的数据,为了提高相应效率,很多地方决定将数据放入缓存,本来打算使用Redis,但是之前的Redis集群是在线下的服务器,这次阿里云上如果搭建Redis还需要申请新的服务器,由于此次需要缓存的数据都是配置数据,所以选择用Springboot框架自带的缓存框架。

使用过程:

1. 简单使用 在使用SpringBoot自带缓存的过程中对于被Spring管理的Bean,倒是没什么问题,Cacheable注解标识的方法会被Spring通过Aop的方式进行代理。

一般在使用SpringBoot自带的缓存时会配置一个CacheManage用于管理全局的缓存。

可以看到Spring自带支持的CacheManage管理器包括以下:

spring 忽略注解_java

本次项目中采用的是Caffeine缓存,一个高性能,接近最佳的缓存库。

* @description: 缓存配置
 * @author: zhanghailang
 * @date: 2021-4-15 10:19
 */
@Configuration
public class CacheConfig {
    @Bean("caffeineCacheManager")
    public CacheManager cacheManager(){
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.SECONDS)
                .initialCapacity(100)
                .maximumSize(10000));
        return cacheManager;
    }
}

在使用缓存的类上指定CacheManager

spring 忽略注解_java_02

在需要使用缓存的方法上使用@Cacheable的注解,此处方法的作用是读取配置的规则校验文件,对于校验文件。只有第一次请求才会从项目路径下读取,后面每次都从缓存存中获取,当然了由于我在上面CacheManage中配置了过期时间 60s,只是为了测试用,一般在生产上的话要根据当前业务量来考虑,脱离了业务来考虑的话就是耍流氓。
此处Key为入参,支持Spel表达式编写,如果没有接触过Spel表达式的可以看看:点击查看Spel表达式基础

spring 忽略注解_spring 忽略注解_03

当然简单的使用也可以不指定Key,SpringBoot很贴心的为你准备了默认的Key生成策略。此处SpringBoot默认是将所有参数当做Key的生成策略,另外Key的生成策略是可以在CacheManger中配置的

spring 忽略注解_java_04


@CacheEvict删除当前指定缓存,此处我将allEntries配置为true,代表将所有Key都给清除

spring 忽略注解_规则引擎_05

当然上面这些只是简单的使用,在本次项目中使用缓存基本上都是为了提升接口效率,缓存的数据基本也都是配置数据。

2. 问题描述

在项目中使用缓存的时候,在一个调用外部系统的方法中,为了防止相同的客户在短时间内的重复调用外部系统,如下图所示,我照往常一样,@Cacheable注解已加,Key值已改,自信一跑,卧槽 ,不对劲,为什么缓存失效了呢。

spring 忽略注解_spring 忽略注解_06


不应该啊,我想了下此处这个类是继承Aviator(一个轻量级的表达式框架)的AbstracFunction接口,用于实现自定义函数,有兴趣的可以看下Aviator的介绍:

Aviator 而我加注解的地方正是Call方法

spring 忽略注解_java_07


此处call方法调用的是Aviator内部父类的方法,会导致缓存失效。

所以我猜想应该是这里出了问题。

第一次尝试解决 我决定把调用外部系统的逻辑拆出来,独立成一个方法,在这个方法上加注解进行处理。如下图这种

spring 忽略注解_缓存_08

spring 忽略注解_java_09


但是我又自信满满的打脸了,缓存方法在当前类方法中自调用会导致缓存失效,这个和@Transactional注解类似。

第二次尝试解决 我决定使用在调用的时候再一次获取当前类的实例,AopContext.currentProxy(),获取当前类的代理实现类,再一次发起调用,果然缓存不在失效。问题解决了。

spring 忽略注解_spring 忽略注解_10

后记

此次规则引擎降级服务系统,我花了两周的时间从0到1,一个完整的SpringBott项目到前天终于基本搭建完成,包括缓存、注解、切面、自定义异常、异步线程池,集成Nacos,MB,Logback日志框架、RocketMQ消息队列,到最后部署到Linux环境,都是我一手完成的还是很有成就感的系统。整体逻辑并不复杂,配置一定数量的指定规则,并对每个投保请求进行校验。但是由于接口的实时性和并发性,所以还是有待考验,目前压测看TPS只能50以下。
在本次服务搭建开发过程中,有很多问题都是第一次的遇见,踩了很多坑,接下来有时间我会一一记录。