扒掉红薯的内裤-深入剖析J2Cache

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 摘要: 最近看到红薯的J2Cache强大到不行,居然长期占据开源中国开源项目排行榜,偶就气不打一处来。 话说你是开源中国第一帅,这个咱们大家有共识,确实实力在那里,我们都认了。 话说你口才比@永和 好,这个只要永和没有意见,我们也同意。

最近看到红薯的J2Cache强大到不行,居然长期占据开源中国开源项目排行榜,偶就气不打一处来。
话说你是开源中国第一帅,这个咱们大家有共识,确实实力在那里,我们都认了。
话说你口才比@永和 好,这个只要永和没有意见,我们也同意。
但是,做个J2Cache居然还悬赏好多次,貌似要打造成开源中国第一开源项目,这就有点过分了。不对,不是过分,是相当过分。
所以今天,偶就狠狠的扒掉@红薯 的内裤,对J2Cache进行一下深入剖析。

前面写过一篇文章,标题是吐槽一下J2Cache,吐槽过后发现J2Cache的热度居然火速上升,貌似有成为开源中国第一开源项目的意思,偶这小心脏就有点受不了了,于是决定再写一篇文章,直接狠一点把@红薯 的内裤扒掉,对J2Cache进行一下深入剖析。

Cache接口

/**
 * Implementors define a caching algorithm. All implementors
 * <b>must</b> be threadsafe.
 * @author liudong
 */
public interface Cache {

    /**
     * Get an item from the cache, nontransactionally
     * @param key cache key
     * @return the cached object or null
     */
    public Object get(Object key) throws CacheException;
    
    /**
     * Add an item to the cache, nontransactionally, with
     * failfast semantics
     * @param key cache key
     * @param value cache value
     */
    public void put(Object key, Object value) throws CacheException;
    
    /**
     * Add an item to the cache
     * @param key cache key
     * @param value cache value
     */
    public void update(Object key, Object value) throws CacheException;

    @SuppressWarnings("rawtypes")
    public List keys() throws CacheException ;
    
    /**
     * @param key Cache key
     * Remove an item from the cache
     */
    public void evict(Object key) throws CacheException;
    
    /**
     * Batch remove cache objects
     * @param keys the cache keys to be evicted
     */
    @SuppressWarnings("rawtypes")
    public void evict(List keys) throws CacheException;
    
    /**
     * Clear the cache
     */
    public void clear() throws CacheException;
    
    /**
     * Clean up
     */
    public void destroy() throws CacheException;
    
}

这个没有什么问题,不管谁来做,大致也是这个样子的,所以到这里来说,还是非常不错的。

但是实际上也有改进的余地,比如把Cache换成Cache,这样

public Object get(Object key) throws CacheException;

就可以变成:

public <T> T get(KeyType key) throws CacheException;

这样有个好处,就是可以避免大量的强制类型转换,另外对于不支持Object类型的缓冲框架来说,就可以文件的实现类中如下实现:

public Class XxxCache implements Cache<String){
    public <T> T get(String key)throws CacheException{
    ......
    }
}

而不用加大量的类型强力转,当然这里是个细节问题。

CacheObject类

它的内容如下:

/**
 * 所获取的缓存对象
 * @author winterlau
 */
public class CacheObject {

    private String region;
    private Object key;
    private Object value;
    private byte level;
    public String getRegion() {
        return region;
    }
    public void setRegion(String region) {
        this.region = region;
    }
    public Object getKey() {
        return key;
    }
    public void setKey(Object key) {
        this.key = key;
    }
    public Object getValue() {
        return value;
    }
    public void setValue(Object value) {
        this.value = value;
    }
    public byte getLevel() {
        return level;
    }
    public void setLevel(byte level) {
        this.level = level;
    }
    
}

这个类,从作者的本意来说是增加了一个描述所处理缓冲对象的所在级别及区域相关的对象,但是我感觉,这个类是完全不必要的,而且正是由于它的存在导致后续围绕它的处理都是不必要的。

那么我们来看看它是怎么被使用的:

在J2HibernateCache类中

public Object get(Object key) throws CacheException {
        CacheObject cobj = cache.get(region, key);
        if (log.isDebugEnabled()) {
            log.debug("get value for j2cache which key:" + key + ",value:" + cobj.getValue());
        }
        return cobj.getValue();
    }

看到没,前面折腾了半天,还是把CacheObject中的value属性搞出来的,这不是脱裤子放屁,多了一次手续么?

我们来搜索一下使用CacheObject类的地方:

image

目光所及,点进去看,居然没有看到什么特别的地方,因此问题依然是:这个类有存在的必要么?

CacheChannel接口

这个时候又看到一个接口,叫CacheChannel,内容如下:

/**
 * Cache Channel
 * @author winterlau
 */
public interface CacheChannel {

    public final static byte LEVEL_1 = 1;
    public final static byte LEVEL_2 = 2;
    
    /**
     * 获取缓存中的数据
     * @param region: Cache Region name
     * @param key: Cache key
     * @return cache object
     */
    public CacheObject get(String region, Object key);
    
    /**
     * 写入缓存
     * @param region: Cache Region name
     * @param key: Cache key
     * @param value: Cache value
     */
    public void set(String region, Object key, Object value);

    /**
     * 删除缓存
     * @param region:  Cache Region name
     * @param key: Cache key
     */
    public void evict(String region, Object key) ;

    /**
     * 批量删除缓存
     * @param region: Cache region name
     * @param keys: Cache key
     */
    @SuppressWarnings({ "rawtypes" })
    public void batchEvict(String region, List keys) ;

    /**
     * Clear the cache
     * @param region: Cache region name
     */
    public void clear(String region) throws CacheException ;
    
    /**
     * Get cache region keys
     * @param region: Cache region name
     * @return key list
     */
    @SuppressWarnings("rawtypes")
    public List keys(String region) throws CacheException ;

    /**
     * 关闭到通道的连接
     */
    public void close() ;
}

这个接口,就有点疑问,如果说它只是一个通道的话,为什么它还有与缓冲相关的get和set方法?在这里,可以看到它的许多方法都增加了region的参数,表明它是支持区域的一些缓冲处理。

另外看到close方法的时候,感觉这个接口的设置可能定位有改进的余地,因为它把通道层的和缓冲相关的接口混在一个里面了,表面看看问题不大,实际上在实现时会带来一些不爽,同时可能会诱导开发人员做了不合理的实现。或许这个方法应该放在CacheProvider当中去?

CacheProvider

/**
 * Support for pluggable caches.
 * @author liudong
 */
public interface CacheProvider {

    /**
     * 缓存的标识名称
     * @return return cache provider name
     */
    public String name();
    
    /**
     * Configure the cache
     *
     * @param regionName the name of the cache region
     * @param autoCreate autoCreate settings
     * @param listener listener for expired elements
     * @return return cache instance
     * @throws CacheException cache exception
     */
    public Cache buildCache(String regionName, boolean autoCreate, CacheExpiredListener listener) throws CacheException;

    /**
     * Callback to perform any necessary initialization of the underlying cache implementation
     * during SessionFactory construction.
     *
     * @param props current configuration settings.
     */
    public void start(Properties props) throws CacheException;

    /**
     * Callback to perform any necessary cleanup of the underlying cache implementation
     * during SessionFactory.close().
     */
    public void stop();
    
}

这个类实际上就是个工厂类,用于创建Cache实例,问题不大。

CacheTester

/**
 * 缓存测试入口
 * @author Winter Lau
 */
public class CacheTester {

    public static void main(String[] args) {
        
        System.setProperty("java.net.preferIPv4Stack", "true"); //Disable IPv6 in JVM
        
        CacheChannel cache = J2Cache.getChannel();
        BufferedReader in=new BufferedReader(new InputStreamReader(System.in));

        do{
            try {
                System.out.print("> "); 
                System.out.flush();
                
                String line=in.readLine().trim();
                if(line.equalsIgnoreCase("quit") || line.equalsIgnoreCase("exit"))
                    break;

                String[] cmds = line.split(" ");
                if("get".equalsIgnoreCase(cmds[0])){
                    CacheObject obj = cache.get(cmds[1], cmds[2]);
                    System.out.printf("[%s,%s,L%d]=>%s\n", obj.getRegion(), obj.getKey(), obj.getLevel(), obj.getValue());
                }
                else
                if("set".equalsIgnoreCase(cmds[0])){
                    cache.set(cmds[1], cmds[2],cmds[3]);
                    System.out.printf("[%s,%s]<=%s\n",cmds[1], cmds[2], cmds[3]);
                }
                else
                if("evict".equalsIgnoreCase(cmds[0])){
                    cache.evict(cmds[1], cmds[2]);
                    System.out.printf("[%s,%s]=>null\n",cmds[1], cmds[2]);
                }
                else
                if("clear".equalsIgnoreCase(cmds[0])){
                    cache.clear(cmds[1]);
                    System.out.printf("Cache [%s] clear.\n" , cmds[1]);
                }
                else
                if("help".equalsIgnoreCase(cmds[0])){
                    printHelp();
                }
                else{
                    System.out.println("Unknown command.");
                    printHelp();
                }
            }
            catch(ArrayIndexOutOfBoundsException e) {
                System.out.println("Wrong arguments.");
                printHelp();
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }while(true);
        
        cache.close();
        
        System.exit(0);
    }
    
    private static void printHelp() {
        System.out.println("Usage: [cmd] region key [value]");
        System.out.println("cmd: get/set/evict/quit/exit/help");
    }

}

这个东东的出现就发现了随意之处,从类的名字看,应该是用来做测试的,但是它居然就出现在main/src下,但是也可以解释为这个就是用来做测试的,也可以解释得通,但是总还是有点怪怪的---这里请允许我引用悠然的名言:好的设计是品出来的。不是说这么做不可以,但是放在核心工程显目位置就有点随意了。

同样的还有Command类,

public class Command {        

    private final static Logger log = LoggerFactory.getLogger(Command.class);
    
    private final static int SRC_ID = genRandomSrc(); //命令源标识,随机生成

    public final static byte OPT_DELETE_KEY = 0x01;     //删除缓存
    public final static byte OPT_CLEAR_KEY = 0x02;         //清除缓存
    
    private int src;
    private byte operator;
    private String region;
    private Object key;
    
    private static int genRandomSrc() {
        long ct = System.currentTimeMillis();
        Random rnd_seed = new Random(ct);
        return (int)(rnd_seed.nextInt(10000) * 1000 + ct % 1000);
    }

    public static void main(String[] args) {
        for(int i=0;i<5;i++){
            Command cmd = new Command(OPT_DELETE_KEY, "users", "ld"+i);
            byte[] bufs = cmd.toBuffers();
            System.out.print(cmd.getSrc() + ":");
            for(byte b : bufs){
                System.out.printf("[%s]",Integer.toHexString(b));            
            }
            System.out.println();
            Command cmd2 = Command.parse(bufs);
            System.out.printf("%d -> %d:%s:%s(%s)\n", cmd2.getSrc(), cmd2.getOperator(), cmd2.getRegion(), cmd2.getKey(), cmd2.isLocalCommand());
        }
    }

    public Command(byte o, String r, Object k){
        this.operator = o;
        this.region = r;
        this.key = k;
        this.src = SRC_ID;
    }
    
    public byte[] toBuffers(){
        byte[] keyBuffers = null;
        try {
            keyBuffers = SerializationUtils.serialize(key);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        int r_len = region.getBytes().length;
        int k_len = keyBuffers.length;

        byte[] buffers = new byte[9 + r_len + k_len];
        System.arraycopy(int2bytes(this.src), 0, buffers, 0, 4);
        int idx = 4;
        buffers[idx] = operator;
        buffers[++idx] = (byte)(r_len >> 8);
        buffers[++idx] = (byte)(r_len & 0xFF);
        System.arraycopy(region.getBytes(), 0, buffers, ++idx, r_len);
        idx += r_len;
        buffers[idx++] = (byte)(k_len >> 8);
        buffers[idx++] = (byte)(k_len & 0xFF);
        System.arraycopy(keyBuffers, 0, buffers, idx, k_len);
        return buffers;
    }
    
    public static Command parse(byte[] buffers) {
        Command cmd = null;
        try{
            int idx = 4;
            byte opt = buffers[idx];
            int r_len = buffers[++idx] << 8;
            r_len += buffers[++idx];
            if(r_len > 0){
                String region = new String(buffers, ++idx, r_len);
                idx += r_len;
                int k_len = buffers[idx++] << 8;
                k_len += buffers[idx++];
                if(k_len > 0){
                    //String key = new String(buffers, idx, k_len);
                    byte[] keyBuffers = new byte[k_len];
                    System.arraycopy(buffers, idx, keyBuffers, 0, k_len);
                    Object key = SerializationUtils.deserialize(keyBuffers);
                    cmd = new Command(opt, region, key);
                    cmd.src = bytes2int(buffers);
                }
            }
        }catch(Exception e){
            log.error("Unabled to parse received command.", e);
        }
        return cmd;
    }
    
    private static byte[] int2bytes(int i) {
        byte[] b = new byte[4];

        b[0] = (byte) (0xff&i);
        b[1] = (byte) ((0xff00&i) >> 8);
        b[2] = (byte) ((0xff0000&i) >> 16);
        b[3] = (byte) ((0xff000000&i) >> 24);
        
        return b;
    }
    
    private static int bytes2int(byte[] bytes) {
        int num = bytes[0] & 0xFF;
        num |= ((bytes[1] << 8) & 0xFF00);
        num |= ((bytes[2] << 16) & 0xFF0000);
        num |= ((bytes[3] << 24) & 0xFF000000);
        return num;
    }
    
    public boolean isLocalCommand() {
        return this.src == SRC_ID;
    }
    
    public byte getOperator() {
        return operator;
    }

    public void setOperator(byte operator) {
        this.operator = operator;
    }

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    public Object getKey() {
        return key;
    }

    public void setKey(Object key) {
        this.key = key;
    }

    public int getSrc() {
        return src;
    }
}

这个类里面也有一些随意之外,比如,产生随机数的算法:

private static int genRandomSrc() {
        long ct = System.currentTimeMillis();
        Random rnd_seed = new Random(ct);
        return (int)(rnd_seed.nextInt(10000) * 1000 + ct % 1000);
    }

如果做这个API的同学看到这么使用的,几乎会一口气上不来的,合理的怎么写,请同学样在后面回帖。

再比如:

private static byte[] int2bytes(int i) {
        byte[] b = new byte[4];

        b[0] = (byte) (0xff&i);
        b[1] = (byte) ((0xff00&i) >> 8);
        b[2] = (byte) ((0xff0000&i) >> 16);
        b[3] = (byte) ((0xff000000&i) >> 24);
        
        return b;
    }
    
    private static int bytes2int(byte[] bytes) {
        int num = bytes[0] & 0xFF;
        num |= ((bytes[1] << 8) & 0xFF00);
        num |= ((bytes[2] << 16) & 0xFF0000);
        num |= ((bytes[3] << 24) & 0xFF000000);
        return num;
    }

这些个东东本来应该是在Common类的工具类中的,结果也出现在这个类中。

这个类的许多算法,也比较奇葩,个人觉得不用这么复杂,可以用更简单的方式来实现。

J2Cache

public class J2Cache {

    private final static Logger log = LoggerFactory.getLogger(J2Cache.class);

    private final static String CONFIG_FILE = "/j2cache.properties";
    private final static CacheChannel channel;
    private final static Properties config;

    static {
        try {
            config = loadConfig();
            String cache_broadcast = config.getProperty("cache.broadcast");
            if ("redis".equalsIgnoreCase(cache_broadcast))
                channel = RedisCacheChannel.getInstance();
            else if ("jgroups".equalsIgnoreCase(cache_broadcast))
                channel = JGroupsCacheChannel.getInstance();
            else
                throw new CacheException("Cache Channel not defined. name = " + cache_broadcast);
        } catch (IOException e) {
            throw new CacheException("Unabled to load j2cache configuration " + CONFIG_FILE, e);
        }
    }

    public static CacheChannel getChannel(){
        return channel;
    }

    public static Properties getConfig(){
        return config;
    }

    /**
     * 加载配置
     * @return
     * @throws IOException
     */
    private static Properties loadConfig() throws IOException {
        log.info("Load J2Cache Config File : [{}].", CONFIG_FILE);
        InputStream configStream = J2Cache.class.getClassLoader().getParent().getResourceAsStream(CONFIG_FILE);
        if(configStream == null)
            configStream = CacheManager.class.getResourceAsStream(CONFIG_FILE);
        if(configStream == null)
            throw new CacheException("Cannot find " + CONFIG_FILE + " !!!");

        Properties props = new Properties();

        try{
            props.load(configStream);
        }finally{
            configStream.close();
        }

        return props;
    }

}

这个就是著名的J2Cache类了,简单看来,存在如下问题:

第一、它是个单实例的类,也就是说无法启动多个实例。但然也不能说这样就不行,但是如果我的应用场景中需要在不同场景用不同的实例呢?就无法满足了。

第二、它和具体的实现相关:

String cache_broadcast = config.getProperty("cache.broadcast");
if ("redis".equalsIgnoreCase(cache_broadcast))
    channel = RedisCacheChannel.getInstance();
else if ("jgroups".equalsIgnoreCase(cache_broadcast))
    channel = JGroupsCacheChannel.getInstance();
else
    throw new CacheException("Cache Channel not defined. name = " + cache_broadcast);

这个实现是非常不妥的实现,最起码对于未来的扩展性、可变性方面都存在非常大的限制。

第三、命名的不合理性

本来从名字和定位来看,我觉得它应该是Cache或CacheChannel的一个实现类,结果发现它不是,它居然只是起个工厂类作用的类。

第四、config的处理

Config居然是由J2Cache读进来,然后让别的类来获取的,居然是这样实现的???这会导致非常严重的问题,依赖关系势必出现混乱,实际上也会出现循环依赖的问题,当然@红薯 的解决方案就是都放在一个工程里面,循环依赖的问题就没有了,但是就会出现其它的问题,后续再来说明。

好的,上面就把J2Cache应用根目录中的内容都走马观花看了一遍,当然只还只算扒了一下外套,现在开始慢慢扒内裤。

产品的说明

J2Cache —— 基于 Ehcache 和 Redis 实现的两级 Java 缓存框架
J2Cache 是 OSChina 目前正在使用的两级缓存框架。第一级缓存使用 Ehcache,第二级缓存使用 Redis 。由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的 Ehcache 缓存数据丢失。

J2Cache 从 1.3.0 版本开始支持 JGroups 和 Redis Subscribe 两种方式进行缓存时间的通知。在某些云平台上可能无法使用 JGroups 组播方式,可以采用 Redis 发布订阅的方式。详情请看 j2cache.properties 配置文件的说明。

视频介绍:http://v.youku.com/v_show/id_XNzAzMTY5MjUy.html
该项目提供付费咨询服务,详情请看:https://zb.oschina.net/market/opus/12_277

J2Cache 的两级缓存结构

L1: 进程内缓存(ehcache)
L2: 集中式缓存,支持多种集中式缓存服务器,如 Redis、Memcached 等

由于大量的缓存读取会导致 L2 的网络带宽成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数

我来看看看它的依赖树:

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] j2cache
[INFO] j2cache-core
[INFO] j2cache-hibernate3
[INFO] j2cache-hibernate4
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building j2cache 1.3.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache ---
[INFO] net.oschina.j2cache:j2cache:pom:1.3.0
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] |  +- org.javassist:javassist:jar:3.19.0-GA:compile
[INFO] |  \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] |  \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] |  \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building j2cache-core 1.3.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache-core ---
[INFO] net.oschina.j2cache:j2cache-core:jar:1.3.0
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] |  +- org.javassist:javassist:jar:3.19.0-GA:compile
[INFO] |  \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] |  \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] |  \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]
[INFO] Building j2cache-hibernate3 1.3.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache-hibernate3 ---
[INFO] net.oschina.j2cache:j2cache-hibernate3:jar:1.3.0
[INFO] +- net.oschina.j2cache:j2cache-core:jar:1.3.0:compile
[INFO] +- org.hibernate:hibernate-core:jar:3.3.2.GA:compile
[INFO] |  +- antlr:antlr:jar:2.7.6:compile
[INFO] |  +- commons-collections:commons-collections:jar:3.1:compile
[INFO] |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  |  \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] |  \- javax.transaction:jta:jar:1.1:compile
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] |  +- org.javassist:javassist:jar:3.19.0-GA:compile
[INFO] |  \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] |  \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] |  \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building j2cache-hibernate4 1.3.0
[INFO] ------------------------------------------------------------------------
[WARNING] The POM for avalon-framework:avalon-framework-api:jar:4.1.5-dev is missing, no dependency information available
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache-hibernate4 ---
[INFO] net.oschina.j2cache:j2cache-hibernate4:jar:1.3.0
[INFO] +- net.oschina.j2cache:j2cache-core:jar:1.3.0:compile
[INFO] +- org.hibernate:hibernate-core:jar:4.3.5.Final:compile
[INFO] |  +- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile
[INFO] |  +- org.jboss.logging:jboss-logging-annotations:jar:1.2.0.Beta1:compile
[INFO] |  +- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:jar:1.0.0.Final:compile
[INFO] |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  |  \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] |  +- org.hibernate.common:hibernate-commons-annotations:jar:4.0.4.Final:compile
[INFO] |  +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile
[INFO] |  +- org.javassist:javassist:jar:3.18.1-GA:compile
[INFO] |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  \- org.jboss:jandex:jar:1.1.0.Final:compile
[INFO] +- org.hibernate:hibernate-ehcache:jar:4.3.5.Final:compile
[INFO] +- com.cloudhopper.proxool:proxool:jar:0.9.1:test
[INFO] |  \- avalon-framework:avalon-framework-api:jar:4.3:test (version selected from constraint [4.1.5,))
[INFO] |     \- avalon-logkit:avalon-logkit:jar:2.1:test
[INFO] |        +- javax.servlet:servlet-api:jar:2.3:test
[INFO] |        +- geronimo-spec:geronimo-spec-javamail:jar:1.3.1-rc3:test
[INFO] |        \- geronimo-spec:geronimo-spec-jms:jar:1.1-rc4:test
[INFO] +- com.cloudhopper.proxool:proxool-cglib:jar:0.9.1:test
[INFO] +- org.springframework:spring-context:jar:4.1.0.RELEASE:compile
[INFO] |  +- org.springframework:spring-aop:jar:4.1.0.RELEASE:compile
[INFO] |  |  \- aopalliance:aopalliance:jar:1.0:compile
[INFO] |  +- org.springframework:spring-core:jar:4.1.0.RELEASE:compile
[INFO] |  \- org.springframework:spring-expression:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-web:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-context-support:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-orm:jar:4.1.0.RELEASE:compile
[INFO] |  +- org.springframework:spring-jdbc:jar:4.1.0.RELEASE:compile
[INFO] |  \- org.springframework:spring-tx:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-beans:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-test:jar:4.1.0.RELEASE:test
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] |  \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] |  \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] |  \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] ------------------------------------------------------------------------

上面是J2Cache的依赖树

可以看到,上面的工程里面依赖了许许多多的包,这个时候就可能会引入大量的依赖冲突问题。另外,我只想用你这个东东,为什么还把hibernate引入了?

为什么必须用ehcache,虽然ehcache非常不错,但是如果把它变成可选件是可以接受的,变成必选件就要了命了。

同样的,这里面引入的许多内容从道理上都讲不通,比如:

redis.clients:jedis:jar:2.8.0:compile

为什么必须得是2.8,万一我工程已经用其它版本了呢?如果高版本可以完全兼容老版本还好说,如果不能完全兼容,又怎么办??

当然有的同学又说了,“你觉得不好就别用,觉得好就用”,当然这是另外一个话题,和今天的话题不太相关,今天的任务是扒@红薯 内裤。

上一次我写吐槽J2Cache的时候,其实就有人说了,你吐槽了半天,然并卵,You can you up, no can no BB。话虽不太好听,但是还是有点道理的。

今天我就说说如果是我来设计J2Cache,那么它是怎么个样子的。

其实所有的问题都是可以解决的,而这些问题的根源就在于设计和实现的不合理导致的,那么下面就来讲讲悠然版J2Cache怎么设计。

悠然建议的J2Cache设计

呵呵,继续说一句悠然名言:你做得不够好,是因为你分得不够细。

工程一:Cache接口设计
这个工程啥实现也没有,就是一个Cache的接口,用来规范Cache访问规范,具体怎么写,不同人有不同的写法。

工程二:

各种不同种类的Cache实现,比如:ehcache,jedis,memcache,JCS,等等,如果精力过剩,各种版本的都来一个也可以。当然,实际上这里每一个具体实现都是一个独立的工程了。

工程三:

J2Cache,它也是Cache接口的一个实现类,在它里面注入两个Cache实例,然后在处理逻辑上做两级缓冲的处理。

当然,如果你想要弄个CacheManager也是可以的,这个不是关键因素,随便都可以。

最后就贴一下Tiny自己的二级缓冲实现类:

public class MultiCache implements Cache{

    /**
     * 1级缓存
     */
    Cache cacheFirst;
    
    /**
     * 2级缓存
     */
    Cache cacheSecond;
    
    public MultiCache() {
    }
    
    public MultiCache(Cache cacheFirst, Cache cacheSecond) {
        this.cacheFirst = cacheFirst;
        this.cacheSecond = cacheSecond;
    }

    public Object get(String key) {
        Object obj = cacheFirst.get(key);
        if (obj == null) {
            obj = cacheSecond.get(key);
            cacheFirst.put(key, obj);
        }
        return obj;
    }

    public Object get(String group, String key) {
        Object obj = cacheFirst.get(group ,key);
        if (obj == null) {
            obj = cacheSecond.get(group ,key);
            cacheFirst.put(group,key, obj);
        }
        return obj;
    }

    public Object[] get(String[] keys) {
        List<Object> objs = new ArrayList<Object>();
        if (keys != null && keys.length > 0) {
            for (int i = 0; i < keys.length; i++) {
                objs.add(get(keys[i]));
            }
        }
        return objs.toArray();
    }

    public Object[] get(String group, String[] keys) {
        List<Object> objs = new ArrayList<Object>();
        if (keys != null && keys.length > 0) {
            for (int i = 0; i < keys.length; i++) {
                objs.add(get(group ,keys[i]));
            }
        }
        return objs.toArray();
    }

    public void put(String key, Object object) {
        cacheFirst.put(key ,object);
        cacheSecond.put(key ,object);
    }

    public void putSafe(String key, Object object) {
        cacheFirst.putSafe(key ,object);
        cacheSecond.putSafe(key ,object);
    }

    public void put(String groupName, String key, Object object) {
        cacheFirst.put(groupName ,key ,object);
        cacheSecond.put(groupName ,key ,object);
    }

    public void remove(String key) {
        cacheFirst.remove(key);
        cacheSecond.remove(key);
    }
    public void remove(String group, String key) {
        cacheFirst.remove(group ,key);
        cacheSecond.remove(group ,key);
        
    }
    public void remove(String[] keys) {
        cacheFirst.remove(keys);
        cacheSecond.remove(keys);
    }
    public void remove(String group, String[] keys) {
        cacheFirst.remove(group ,keys);
        cacheSecond.remove(group ,keys);
    }
}

这样的作法,就会把一二级缓冲的实现安全分离出去,才不管它是具体是什么技术实现的,给最终的使用人员以最大的自由度。

同时,由于每种不同的缓冲,基于不同的缓冲框架或版本,也可以有多个实例存在,用户在使用的时候,也可以直接引用具体的缓冲实现就好,当然也可以自己根据接口实现一个就OK了,这样避免了大量不需要的Jar包的引入,对于工程化处理是非常有好处的。

总结

开篇有讲,要扒@红薯 的内裤,实际上就是要红薯把一切冗余的东西都去去掉,只留下光溜溜、赤裸裸的缓冲、二级缓冲,其它都作为扩展包由使用者选用或者自行扩展,这样整体弄下来,就会是一个分离有度、协作良好、易于使用的J2Cache框架。

附被扒了内裤的红薯真容:

image

文章转载自 开源中国社区[https://www.oschina.net]

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
7月前
|
存储 缓存 Linux
入职后,我才明白什么叫Cache
入职后,我才明白什么叫Cache
|
9月前
|
XML 安全 Java
什么是webservices?为啥现在还未被淘汰?
什么是webservices?为啥现在还未被淘汰?
81 0
|
11月前
|
SQL 安全 Shell
[永久开源] vulntarget-b_打靶记录(上)
[永久开源] vulntarget-b_打靶记录
175 0
|
11月前
|
安全 Ubuntu 网络协议
【永久开源】vulntarget-c 打靶记录(上)
【永久开源】vulntarget-c 打靶记录
128 0
|
11月前
|
安全 Shell 网络安全
[永久开源] vulntarget-b_打靶记录(下)
[永久开源] vulntarget-b_打靶记录
143 0
|
11月前
|
SQL Web App开发 安全
【永久开源】vulntarget-c 打靶记录(下)
【永久开源】vulntarget-c 打靶记录
131 0
|
canal 缓存 NoSQL
公司缓存问题和解决办法
公司缓存问题和解决办法
84 0
|
编译器
rpath失效是怎么回事
rpath失效是怎么回事
91 0
|
安全 算法 Java
面试:为了进阿里,又把并发CAS(Compare and Swap)实现重新精读一遍
在面试中,并发线程安全提问必然是不会缺少的,那基础的CAS原理也必须了解,这样在面试中才能加分,
6128 1
面试:为了进阿里,又把并发CAS(Compare and Swap)实现重新精读一遍
|
SQL 关系型数据库 MySQL
聊聊索引失效的10种场景,太坑了
聊聊索引失效的10种场景,太坑了
聊聊索引失效的10种场景,太坑了