一直以来,我们都是将数据字典等信息放在Redis缓存中,避免使用的时候,穿透到数据库层面,同时提升性能。最近突然发现线上频繁出现Redis连接超时等异常,经过跟踪,发现新增了一个字典表,有三万多行记录,始料未及的事情终究还是发生了。于是需要增加应用内存级别的缓存,同时还要保持与redis一致。
最近红薯的J2Cache发版有点频繁(快成娱乐新闻了),成功的引起了我的注意,基本满足此需求,由于J2Cache底层基于Ehcache,每次启动,都要往内存加载缓存数据,导致本地调试每次重启变得很慢,所以后续会基于J2Cache进行扩展(ehcache的磁盘缓存策略,必须确保应用正常退出才行)。
注意事项1:配置参数名不能直接使用j2cache.properties中示例
由于项目本身使用了配置中心,只能基于动态构建J2Cache的方式进行集成。此时发现了J2Cache的两个BUG,第一个直接导致红薯连夜发版(说好无BUG,反手就是一巴掌,再次给红薯道歉);第二个问题,每次启动的时候,没有读取到配置信息,而代码转换出现空指针异常(我认为这种强转是会出事的,建议官方在后续版本改一下写法)经过红薯的指点,在使用Properties方式进行动态添加配置的时候,需要将redis.的前缀去掉,所以只能在J2CacheBuilder.init(config);之前通过代码去掉前缀,此坑必须绕开。
代码参见下面的removePrefix方法。
注意事项2:不想使用集群广播通知应该如何设置
另外如果不想使用Redis集群或者集群广播功能,需要config.setBroadcast("none");里面的参数,其实建议官方能改成枚举。
示例代码如下:
package com.chz.apps.common.j2cache;
import com.chz.apps.common.cache.LocalCache;
import com.chz.apps.common.redisson.RedissionTools;
import net.oschina.j2cache.CacheChannel;
import net.oschina.j2cache.J2CacheBuilder;
import net.oschina.j2cache.J2CacheConfig;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
/**
* J2Cache工具类
* @Author gongstring(gongstring@foxmail.com)
*/
public class J2CacheUtil {
private static Logger logger = LoggerFactory.getLogger(J2CacheUtil.class);
private static CacheChannel channel = null;
/**
* 初始化启动
*/
public static void start(){
Object server = LocalCache.getInstance().get(RedissionTools.REDIS_SERVER_IP);
Object port = LocalCache.getInstance().get(RedissionTools.REDIS_SERVER_PORT);
Object timeout = LocalCache.getInstance().get(RedissionTools.REDIS_TIMEOUT);
Object auth = LocalCache.getInstance().get(RedissionTools.REDIS_AUTH);
Object database = LocalCache.getInstance().get(RedissionTools.REDIS_DATABASE);
if(server != null && StringUtils.isNotBlank(server.toString()) && port != null && Integer.parseInt(port.toString()) > 0){
try {
Properties l1cacheProperties = new Properties();
l1cacheProperties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("j2cache/caffeine.properties"));
Properties l2cacheProperties = new Properties();
l2cacheProperties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("j2cache/j2cache.properties"));
//自定义redis配置连接
l2cacheProperties.put("redis.hosts",server+":"+port);
l2cacheProperties.put("redis.timeout",timeout);
if(auth != null){
l2cacheProperties.put("redis.password",auth);
}
if(database != null){
l2cacheProperties.put("redis.database",database);
}
J2CacheConfig config = new J2CacheConfig();
//不使用集群通知
config.setBroadcast("none");
config.setL1CacheName("caffeine");
config.setL1CacheProperties(l1cacheProperties);
config.setL2CacheName("redis");
config.setL2CacheProperties(removePrefix(l2cacheProperties,"redis."));
J2CacheBuilder builder = J2CacheBuilder.init(config);
channel = builder.getChannel();
logger.info("J2Cache启动成功");
}catch (Exception e){
e.printStackTrace();
logger.error("J2Cache启动失败:{}",e);
}
}else{
logger.info("J2Cache启动失败,没有配置二级缓存Redis的参数信息");
}
}
public static void close(){
if(channel != null){
channel.close();
}
}
public static CacheChannel getChannel(){
return channel;
}
private static Properties removePrefix(Properties properties,String prefix){
if(properties == null)
return null;
Properties result = new Properties();
Iterator<Map.Entry<Object, Object>> it = properties.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Object, Object> entry = it.next();
if(entry.getKey().toString().startsWith(prefix)){
result.put(entry.getKey().toString().substring(prefix.length()),entry.getValue());
}else{
result.put(entry.getKey(),entry.getValue());
}
}
return result;
}
public static void main(String[] args) throws Exception{
start();
// 通用没有具体业务意义的代码,只是为了保证主线程不退出
synchronized (J2CacheUtil.class) {
J2CacheUtil.class.wait();
}
}
}