微服务整合J2cache并改造使用
我们的微服务架构中,存在一个单独的基础数据中心,存放了各个服务、页面、app端的所需要的基础数据信息。这些数据的特点就是不易变,查询量大;最适合的场景就是进行缓存。经过一番商讨,决定使用J2Cache二级缓存。整个缓存架构过程:具体更多关于J2cache可以去查看官网文档。我这里简述我们的使用方法,因为我们是springboot项目,Spring的IOC可以让我们...
我们的微服务架构中,存在一个单独的基础数据中心,存放了各个服务、页面、app端的所需要的基础数据信息。这些数据的特点就是不易变,查询量大;最适合的场景就是进行缓存。经过一番商讨,决定使用J2Cache二级缓存。
整个缓存架构过程:
具体更多关于J2cache可以去查看官网文档。
我这里简述我们的使用方法,因为我们是springboot项目,Spring的IOC可以让我们将所有单例组件都交给他来管理。
J2cache原生使用的是静态类启动,使用。这既涉及到一个问题我们项目区分了4套环境,都是使用spring的profile来控制的。
所以我们的缓存框架就需要将多环境做进去。这里我就将他改造成了@component组件。默认L1缓存使用caffeine。L2使用redis
首先将J2cache的配置文件抽离出来,以configurationProperties的形式进行管理。
package org.codingfly.common.cache;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* j2cache配置项
*
* @author: wangmeng
* @date: 2019/12/26
**/
@ConfigurationProperties("codingfly.j2cache")
@Component
@Getter
@Setter
public class J2cacheProperties {
private boolean enable = true;
private String broadcast = "lettuce";
private L1cacheDefinition l1cache = new L1cacheDefinition();
private L2cacheDefinition l2cache = new L2cacheDefinition();
private String serialization = "json";
private boolean syncTtlToRedis = true;
private boolean cacheNullObject = true;
private List<CaffeineDefinition> caffeine = new ArrayList<>();
}
package org.codingfly.common.cache;
import lombok.Getter;
import lombok.Setter;
/**
* 一级缓存定义
*
* @author: wangmeng
* @date: 2019/12/26
**/
@Getter
@Setter
class L1cacheDefinition {
private String name = "caffeine";
}
package org.codingfly.common.cache;
import lombok.Getter;
import lombok.Setter;
/**
* 二级缓存定义
*
* @author: wangmeng
* @date: 2019/12/26
**/
@Getter
@Setter
class L2cacheDefinition {
private String name = "lettuce";
}
然后我们使用@conditionsProperties注解配置cacheConfig类
package org.codingfly.common.cache;
import net.oschina.j2cache.J2CacheConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* J2CacheConfig配置
*
* @author: wangmeng
* @date:
**/
@Configuration
@ConditionalOnProperty(prefix = "codingfly.j2cache", name = "enable", havingValue = "true", matchIfMissing = true)
public class CacheConfig {
private final J2cacheProperties j2cacheProperties;
private final RedisProperties redisProperties;
public CacheConfig(J2cacheProperties j2cacheProperties, RedisProperties redisProperties) {
this.j2cacheProperties = j2cacheProperties;
this.redisProperties = redisProperties;
}
@Bean
public J2CacheConfig j2CacheConfig() {
J2CacheConfig j2CacheConfig = new J2CacheConfig();
j2CacheConfig.setBroadcast(j2cacheProperties.getBroadcast());
j2CacheConfig.setL1CacheName(j2cacheProperties.getL1cache().getName());
j2CacheConfig.setL2CacheName(j2cacheProperties.getL2cache().getName());
j2CacheConfig.setSerialization(j2cacheProperties.getSerialization());
j2CacheConfig.setSyncTtlToRedis(j2cacheProperties.isSyncTtlToRedis());
j2CacheConfig.setDefaultCacheNullObject(j2cacheProperties.isCacheNullObject());
j2CacheConfig.setBroadcastProperties(getBroadcastProperties());
j2CacheConfig.setL1CacheProperties(getL1CacheProperties());
j2CacheConfig.setL2CacheProperties(getBroadcastProperties());
return j2CacheConfig;
}
private Properties getBroadcastProperties() {
Properties broadcastProperties = new Properties();
broadcastProperties.setProperty("namespace", "");
//storage的另一个配置broadcastProperties.setProperty("storage", "hash")
broadcastProperties.setProperty("storage", "generic");
broadcastProperties.setProperty("channel", "j2cache");
broadcastProperties.setProperty("scheme", "redis");
broadcastProperties.setProperty("hosts", redisProperties.getHost() + ":" + redisProperties.getPort());
broadcastProperties.setProperty("password", "");
broadcastProperties.setProperty("database", String.valueOf(redisProperties.getDatabase()));
broadcastProperties.setProperty("sentinelMasterId", "");
broadcastProperties.setProperty("maxTotal", "100");
broadcastProperties.setProperty("maxIdle", "10");
broadcastProperties.setProperty("minIdle", "10");
broadcastProperties.setProperty("timeout", "1000");
return broadcastProperties;
}
private Properties getL1CacheProperties() {
Properties l1CacheProperties = new Properties();
l1CacheProperties.setProperty("region.default", "1000, 30m");
j2cacheProperties.getCaffeine().forEach(data ->
l1CacheProperties.setProperty("region." + data.getKey(), data.getValue()));
return l1CacheProperties;
}
}
这时候我们就已经将J2cache需要的J2cacheconfig类注入到spring管理了。
package org.codingfly.common.cache;
import net.oschina.j2cache.CacheChannel;
import net.oschina.j2cache.J2CacheBuilder;
import net.oschina.j2cache.J2CacheConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* channel配置
*
* @author: wangmeng
* @date: 2019/12/25
**/
@Configuration
@ConditionalOnBean(CacheConfig.class)
public class CacheChannelConfig {
private final J2CacheConfig j2CacheConfig;
public CacheChannelConfig(J2CacheConfig j2CacheConfig) {
this.j2CacheConfig = j2CacheConfig;
}
@Bean
public CacheChannel cacheChannel() {
return J2CacheBuilder.init(j2CacheConfig).getChannel();
}
}
接下来我们需要配置我们的cachechannel类注入。
这时候我们的完成了注入cacheChannel组件。这里我们注入时因为技术选型已经选好了,所以配置都使用了默认值。即使不配置自定义配置也可以使用。只要配置了redis即可。默认的redis连接使用lettuce。
默认还增加了caffeine的default配置。其他的缓存配置,默认会遵照此配置。
我们还可以将它和Spring cache注解集成,这里是官网提供的方法 对其进行了改造。
package org.codingfly.common.cache;
import net.oschina.j2cache.CacheChannel;
import net.oschina.j2cache.NullObject;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import java.util.concurrent.Callable;
/**
* Spring Cache Adapter
*
* @author wangmeng
* @date: 2019/12/27
*/
public class J2CacheSpringCacheAdapter extends AbstractValueAdaptingCache {
private final String name;
private final CacheChannel j2Cache;
private boolean allowNullValues;
/**
* Create an {@code AbstractValueAdaptingCache} with the given setting.
*
* @param allowNullValues whether to allow for {@code null} values
* @param j2Cache j2cache instance
* @param name cache region name
*/
J2CacheSpringCacheAdapter(boolean allowNullValues, CacheChannel j2Cache, String name) {
super(allowNullValues);
this.allowNullValues = allowNullValues;
this.name = name;
this.j2Cache = j2Cache;
}
/**
* Perform an actual lookup in the underlying store.
*
* @param key the key whose associated value is to be returned
* @return the raw store value for the key
*/
private static String getKey(Object key) {
return key.toString();
}
@Override
protected Object lookup(Object key) {
Object value = j2Cache.get(name, getKey(key)).rawValue();
if (value == null || value.getClass().equals(Object.class)) {
return null;
}
return value;
}
/**
* Return the cache name.
*/
@Override
public String getName() {
return name;
}
/**
* Return the underlying native cache provider.
*/
@Override
public Object getNativeCache() {
return j2Cache;
}
/**
* Return the value to which this cache maps the specified key, obtaining
* that value from {@code valueLoader} if necessary. This method provides
* a simple substitute for the conventional "if cached, return; otherwise
* create, cache and return" pattern.
* <p>If possible, implementations should ensure that the loading operation
* is synchronized so that the specified {@code valueLoader} is only called
* once in case of concurrent access on the same key.
* <p>If the {@code valueLoader} throws an exception, it is wrapped in
* a {@link ValueRetrievalException}
*
* @param key the key whose associated value is to be returned
* @param valueLoader value loader
* @return the value to which this cache maps the specified key
* @throws ValueRetrievalException if the {@code valueLoader} throws an exception
* @since 4.3
*/
@Override
public <T> T get(Object key, Callable<T> valueLoader) throws ValueRetrievalException {
ValueWrapper val = get(key);
if (val != null) {
if (val.get() instanceof NullObject) {
return null;
}
return (T) val.get();
}
T t;
try {
t = valueLoader.call();
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
this.put(key, t);
return t;
}
/**
* Associate the specified value with the specified key in this cache.
* <p>If the cache previously contained a mapping for this key, the old
* value is replaced by the specified value.
*
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
*/
@Override
public void put(Object key, Object value) {
j2Cache.set(name, getKey(key), value, allowNullValues);
}
/**
* Atomically associate the specified value with the specified key in this cache
* if it is not set already.
* <p>This is equivalent to:
* <pre><code>
* Object existingValue = cache.get(key);
* if (existingValue == null) {
* cache.put(key, value);
* return null;
* } else {
* return existingValue;
* }
* </code></pre>
* except that the action is performed atomically. While all out-of-the-box
* {@link CacheManager} implementations are able to perform the put atomically,
* the operation may also be implemented in two steps, e.g. with a check for
* presence and a subsequent put, in a non-atomic way. Check the documentation
* of the native cache implementation that you are using for more details.
*
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
* @return the value to which this cache maps the specified key (which may be
* {@code null} itself), or also {@code null} if the cache did not contain any
* mapping for that key prior to this call. Returning {@code null} is therefore
* an indicator that the given {@code value} has been associated with the key.
* @since 4.1
*/
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
ValueWrapper existingValue = get(getKey(key));
if (existingValue == null) {
put(key, value);
return null;
} else {
return existingValue;
}
}
/**
* Evict the mapping for this key from this cache if it is present.
*
* @param key the key whose mapping is to be removed from the cache
*/
@Override
public void evict(Object key) {
j2Cache.evict(name, getKey(key));
}
/**
* Remove all mappings from the cache.
*/
@Override
public void clear() {
j2Cache.clear(name);
}
}
package org.codingfly.common.cache;
import net.oschina.j2cache.CacheChannel;
import org.springframework.cache.Cache;
import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager;
import java.util.Collection;
/**
* @author Chen
*/
public class J2CacheSpringCacheManageAdapter extends AbstractTransactionSupportingCacheManager {
/**
* Load the initial caches for this cache manager.
* <p>Called by {@link #afterPropertiesSet()} on startup.
* The returned collection may be empty but must not be {@code null}.
*/
@Override
protected Collection<? extends Cache> loadCaches() {
return null;
}
private CacheChannel cacheChannel;
private boolean allowNullValues;
/**
* @param allowNullValues 默认 true
*/
J2CacheSpringCacheManageAdapter(CacheChannel cacheChannel, boolean allowNullValues) {
this.cacheChannel = cacheChannel;
this.allowNullValues = allowNullValues;
}
@Override
protected Cache getMissingCache(String name) {
return new J2CacheSpringCacheAdapter(allowNullValues, cacheChannel, name);
}
}
package org.codingfly.common.cache;
import net.oschina.j2cache.CacheChannel;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
/**
* j2cache集成spring cache注解使用
*
* @author: wangmeng
* @date: 2019/12/26
**/
@Configuration
@ConditionalOnBean(CacheChannel.class)
@EnableCaching
public class SpringCacheConfig extends CachingConfigurerSupport {
private final CacheChannel cacheChannel;
public SpringCacheConfig(CacheChannel cacheChannel) {
this.cacheChannel = cacheChannel;
}
@Override
public CacheManager cacheManager() {
return new J2CacheSpringCacheManageAdapter(cacheChannel, true);
}
}
这时我们已经将它集成到了Spring cache的注解中,这里嗨解决了一个spring cache'使用redis做缓存时,不可以设置超时时间的一个问题。
使用方法同spring cache。
到底spring boot 集成J2cache 的两种方法都写完了。
更多推荐
所有评论(0)