扒掉红薯的内裤-深入剖析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类的地方:

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

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框架。

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

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

时间: 2024-10-10 13:00:58

扒掉红薯的内裤-深入剖析J2Cache的相关文章

深入剖析J2Cache

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

扒掉微店的底裤

作者:汗马 最近一段时间身边的很多朋友都在问老马知不知道有个叫微店的东西,然后问我怎么看.接着,老马公开课QQ群也开始有人发微店的广告,才意识到微店这玩意被炒起来了.有好多个版本,微店自己说被阿里巴巴给封杀了,还写了一封给马云的信.而有的版本说微店是马云做的,想跟微信抗衡.老马郁闷了,这么个老掉牙的忽悠模式如今借着微信的风头居然火起来了,看来这世道真的是骗子太少,傻子太多. 干互联网这行有点时间的朋友们都应该知道,微店这种模式老早就有N个网站在做,而且也死掉了N个.搭着传销的架构,披上B2C电商

[盘点]那些被《男人装》扒掉衣服的清纯玉女

<男人装>--一个代表了性感.暴露.美貌.风尘的杂志,一个让男人趋之若鹜的杂志.一个因美丽面孔而吸引众多目光的杂志.究竟有多少清纯玉女为它付出了自己的"第一次"?让她们看到了另一个自己... 曾黎 震撼的来了,<男人装>已经不满足拍"欲女",把"玉女"拍成"欲女"才有成就感,所以,看曾黎裸露比看阿朵裸露更刺激. [page] 蒋勤勤 原来她也拍过<男人装>,那我们只能说,她白白裸了一次,一点

扒掉共享经济装逼外衣:先共享,再社交!

[导读]共享经济存在的合理性,即网络技术降低了人们共享的成本,于是获取资源的方式更加有效率,也更加廉价,从而实现更大规模的参与.在此黑马哥分享这篇 钛媒体(文作)的文章,Airbnb在国内的模仿者众多,Uber租车应用模式也已经进入中国,还有哪些因素会影响到共享经济的生长?找灵感.挖黑马.评热点.抄本质-这里是黑马通讯社:亚马逊老板Jeff Bezos曾说这世界上有两种生意,一种是让你多花钱,一种是帮你省钱.当然这两种都的要能赚钱,否则就不是生意了.受工业革命启蒙,我们知道规模能够经济,也就是说

浅析笔者那些被百度K掉的网站 经验总结

网站做得好,原因大抵相同,而网站被K,却各有各的不幸.本文笔者将自己优化当中那些曾经被K掉的网站拿出来剖析,告诫自己的同时,希望也对你有所帮助. 一.当时粗心留后患 笔者初入seo行业,接了一个站的优化,关键词是XXktv.当初笔者也如大多数网站优化人员一样,看一看百度指数,相关搜索量,关注一下首页及第二页竞争对手网站大致情况,信口道一个月优化到首页去.这网站确实也在二十天左右优化到了首页,因为关键词的竞争度不大.在接下来的二个月里,笔者稍微维护一下,关键词仍然很坚挺.待有一天,老大突然道:"某

河南球迷集体闯高速关卡青岛女球迷上衣竟被扒

昨天下午的青豫之战赛后闹出了不和谐的声音,双方球迷双方了激烈的冲突,青岛当地球迷被河南球迷暴打,一位女球迷的上衣甚至被河南球迷扒光,此外,青岛警方还透露,昨天上午河南球迷强行冲过高速路口收费处,造成了高速路设备损坏. 河南球迷暴力过收费站 昨天上午,满载河南球迷的九辆大客车在青岛的瑞昌路收费口遇到阻拦后竟然强行通过.据警方反应,昨天上午9点左右,当第一批的五辆大客车和一辆黑色轿车到达收费站时,车上下来7名体壮男子将高速路的栏杆拉开,然后开始指挥车辆. 此后收费站虽然加派了人手,但几名男子马上将收

青岛女球迷扒衣图曝光警察为何袖手旁观?(图)

青豫赛后的火爆冲突近日被炒得沸沸扬扬,而冲突的焦点则集中在了"青岛女孩脱衣"上,青岛球迷协会的负责人说是河南球迷将她的上衣全部扒掉,而河南球迷则表示她是自己把衣服撕碎的,那么究竟谁在撒谎呢?通过各种渠道,获取了当时的一张珍贵图片,从图片可以看到,当时这名女球迷上衣已经被扯开,正在主动拉住河南球迷并大声嘶喊.让 网友不解的是,如果是河南球迷主动撕扯该女士衣服,身后的青岛警察会不管不问??? [延伸阅读>>>>河南:女球迷自己脱衣 欲扒男球迷裤子警方制止] 而这个当

河南:女球迷自己脱衣欲扒男球迷裤子警方制止

网易体育付强6月29日北京报道: "我们有铁证,青岛女球迷是自己脱衣服冲向我们男球迷,随后还试图扒我们男球迷的裤子,当即被青岛警方制止."河南建业球迷部王主任回忆当时的冲突时非常气愤. 6月27日,中超联赛第13轮青岛同河南的比赛结束后,双方球迷在场外唱出了不和谐的音符.有消息称,一名青岛女球迷被10多名河南男球迷扒掉上衣,并有上前拍照的记者被河南球迷殴打.一时间,青岛方面的声音铺天盖地,河南方面没有任何回应.笔者拨通了河南建业球迷部主任的电话,王主任在电话的另一端表示:"我

大数据不应成为市场垄断者的隐私盛宴

近段时间,美国"棱镜"计划被批露,美国国家安全局一直进行国内信息监视活动.已收集数以百万计信息数据的消息被披露后,一石激起千层浪.围绕棱镜事件,无论是做安防产品的,还是售卖安全软件的,都借此增大了吆喝的嗓门,也很有意思.我们老说移动互联网时代来了,像这种用户识别信息正大量被恣意泄露,隐私数据的滥用的情况,很像是狼外婆,倒真给国内企业的信息安全敲响了警钟.也许对企业来说,企业的商业机密随时可能被黑被窃,甚至敏感数据在不知不觉间被人备份,才更加危险,如同被人扒掉了内裤,尴尬至极.我们在幸灾