微服務整合J2cache並改造使用

我們的微服務架構中,存在一個單獨的基礎數據中心,存放了各個服務、頁面、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 的兩種方法都寫完了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章