文档章节

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

悠悠然然
 悠悠然然
发布于 2015/12/02 15:14
字数 5324
阅读 15649
收藏 242

最近看到红薯的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<KeyType>,这样

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

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

如果您对悠然的博客感兴趣,请关注悠然,以便及时收到相关的推送。悠然出品,必是精品。

另外,悠然正在招收嫡传弟子,欢迎感兴趣的少年才俊们私信。



© 著作权归作者所有

共有 人打赏支持
悠悠然然

悠悠然然

粉丝 2389
博文 184
码字总数 360373
作品 14
杭州
架构师
加载中

评论(93)

悠悠然然
悠悠然然

引用来自“抢小孩糖吃”的评论

棒棒哒,我犯了很多很红薯一样的错误。回去修改框架去

回复@抢小孩糖吃 : 发个1分的打赏也好啊....
抢小孩糖吃
抢小孩糖吃
棒棒哒,我犯了很多很红薯一样的错误。回去修改框架去
蓝风970655147
蓝风970655147
哈哈 趁周末有空瞅了瞅J2Cache, 也发表发表自己的看法, 因为水平有限, 还请指正
——-------------------------------——-------------------------------
Cache 提供一个基本的实现, 里面提供listener之类的方便以后功能的扩展[限定容量, 监听缓存的curd之类的]
RedisCache.getKeyName 抽取接口, 避免key, value覆盖
某些字段的空校验, 合理性校验, 比如 RedisCache.redisCacheProxy等等[RedisCacheProvider.build执行的时候没有初始化redisCacheProxy 或者用户自己创建RedisCache的时候可能会出现这种情况],
某些地方缺少一些剪枝条件, 比如 RedisCacheChannel中的相关方法

CacheManager 可以更新为继承自Cache, 改改名字[上面的MultiCache之类的] 做成可复用的工具, 组合两个Cache处理"业务"
CacheManager 使用的地方提供CacheManager实例, CacheChannel中的相关业务直接委托给CacheManager
初始化之后不可修改的"策略"放在构造函数中

level做成枚举

J2Cache.getConfig使用的相关地方, 提供一次配置[constructor] 或者多次配置[setter]的方法
J2Cache, 我觉得应该是提供整个项目 结合"./j2cache.properties"的具体配置的所有的对用户开放的接口, 比如 组合一个CacheManager, CacheChannel, config, 然后 提供获取CacheManager, CacheChannel, config的接口, 或者直接提供核心业务方法set, get, evict等等

各个包的层级关系, 命名之类的.. 个人觉得需要review一下
通篇看了一下, 还是学到了不少东西, 感觉作者
——-------------------------------——-------------------------------
蓝风970655147
蓝风970655147
请问int2bytes, bytes2int 这两个如何改进呢, 感觉 差不多是这样吧[这里的实现似乎有点"非主流"., 常用一点的是((xxx) & 0xff) )
s
sdwanghuailiang
建议悠然大师写个缓存框架,等J2Cache2.0等的花都谢了
湖水没了
湖水没了

引用来自“MarkHsiu”的评论

随机数;直接使用api就可以了
private static int genRandom() {
return new Random().nextInt(1000000);
}
不带种子的话 不要每次new
悠悠然然
悠悠然然

引用来自“reesoft”的评论

全文很赞,特别是末尾的配图,太亮太亮!唯一糟点是英文不够地道,cacheFirst、cacheSecond 应该用 l1Cache、l2Cache(准确地说是 l3Cache)。@红薯
有道理,呵呵,马上就改。
reesoft
reesoft

引用来自“悠悠然然”的评论

引用来自“罗格林”的评论

@悠悠然然 ,关于MultiCache,想知道,如果二级缓存被其他节点更新了,这个更新如何反应到当前节点中来?看你的实现,get总是先去一级缓存,如果为空值才会去访问二级缓存,这样以来如何保证分布式环境下的缓存同步?

所以二级缓存不是在所有时刻都是一致的,如果任何时候都一致,最后都只能用数据库了,这个时候性能又肯定上不去,所以任何方案都是尤其前置条件和使用场合的。
Redis 有 subscribe 机制可以用来对付这个问题
reesoft
reesoft
全文很赞,特别是末尾的配图,太亮太亮!唯一糟点是英文不够地道,cacheFirst、cacheSecond 应该用 l1Cache、l2Cache(准确地说是 l3Cache)。@红薯
Silencesk
Silencesk
怎样才算基础好
关于J2cache ...

刚刚开始接触红薯大大的J2cache,在此感谢! 请教大家关于j2cache的一些问题: 1. j2cache的优缺点是什么呢? 2.它适用于什么场景,该怎样去用它? 在此拜谢诸位英雄好汉!谢谢!!...

磁爆步兵001
06/01
0
0
j2cache居然不支持集群的redis?

@红薯 是不是j2cache不支持集群模式的reids,我的redis是集群部署的,然后我看j2cache客户端好像用的是jedisclient,报异常redis.clients.jedis.exceptions.JedisMovedDataException : MOVED ...

潘meallon
07/10
0
0
J2Cache 2.7.2 发布,解决多个实例并存问题

@红薯:当你碰到一个百思不得其解的奇怪问题,折腾半天搞不定的时候,这个问题基本上可以判断是因为一个愚蠢错误引起的。 ------- 华丽分割线 ------- J2Cache 2.7.2 版本更新了,该版本主要...

红薯
10/16
0
0
J2Cache 新增 Hibernate 5 支持,感谢 @tandy 贡献

想了解 J2Cache 是一个什么开源项目,请阅读 这篇博客。 J2Cache 刚新增对 Hibernate 5 的支持,提供了 j2cache-hibernate5 模块,可以方便在 Hibernate 5 中启用 J2Cache 缓存。 该模块由 ...

红薯
09/28
0
1
J2Cache 新增 Mybatis 支持模块,代码少到没 Bug

花了点时间撸了个 MyBatis 的 J2Cache 支持模块,含注释共八十多行代码 (J2CacheAdapter.java),再有 Bug 我就真的要退役了。 使用方法很简单,请看 https://gitee.com/ld/J2Cache/tree/mast...

红薯
05/15
0
30

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 小心着凉 @红薯

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子:5.33起,其声呜呜然,如怨如慕,如泣如诉。余音袅袅,不绝如缕。分享Arch Enemy的单曲《Bridge Of Destiny (2009)》 《Bridge Of...

小小编辑
28分钟前
34
0
what f,,

anlve
今天
2
0
初级开发-编程题

` public static void main(String[] args) { System.out.println(changeStrToUpperCase("user_name_abc")); System.out.println(changeStrToLowerCase(changeStrToUpperCase("user_name_abc......

小池仔
今天
11
0
现场看路演了!

HiBlock
昨天
20
0
Rabbit MQ基本概念介绍

RabbitMQ介绍 • RabbitMQ是一个消息中间件,是一个很好用的消息队列框架。 • ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。Connection是RabbitMQ的s...

寰宇01
昨天
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部