`
iamzhongyong
  • 浏览: 797224 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

RMICache组件设计(remote method cache)

阅读更多

    ​    ​一个复杂的系统,可能外部一个web请求,调用到服务端之后,会变成多个请求,可能是再次请求外部,也可能是请求外部的DB,这时候就面临一个问题,就是一个请求,如何不被重复发送,例如根据userId在数据库查询用户信息,这个操作,可能会由于新人改代码,明明线程内已经请求过一次了,还会再继续请求,这个时候就多了一次网络开销。

    ​    ​这种问题如何避免呢?也可能有答案,就是通过review代码的形式,之前已经获取的用户信息中,放在一个变量中,把这个变量不断的传递下去,先从这个变量中获取数据,如果变量中没有,则从远端(例如数据库端)获取这个数据,但是这样有个问题,就是这个变量会冗余的作为方法体的参数,看起来有点不爽。

    ​    ​所以,基于此,我觉得可以尝试用ThreadLocal来缓存一个线程中以及调用过的方法的返回结果。这样的话,非常复杂的系统,再也不用反复review代码去看有哪些调用是被重复搞的,也不用再通过上下文去传递变量了。

    ​    ​设计图如下:

    ​    ​

上代码(目前还是一个初级版本  https://github.com/iamzhongyong/RMICache ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package rmicache;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 缓存信息的持有
 *
 */
public class RMICacheHolder {
 
    private RMICacheHolder() {}
     
    private static RMICacheHolder rmiCacheHolder = new RMICacheHolder();
     
    /** 缓存的实体对象, map结构,一个线程体内,可能缓存多个方法的对象 */
    private ThreadLocal<Map<String/*方法的签名信息*/,Object/*线程缓存的对象*/>> cacheEntry = new ThreadLocal<Map<String,Object>>(){
        @Override
        protected Map<String, Object> initialValue() {
            return new HashMap<String,Object>();
        }
    };
     
    /**构造单例*/
    public static RMICacheHolder newInstance(){
        return rmiCacheHolder;
    }
     
    /**根据方法签名,获取缓存对象,如果没有,则通过callback的形式来放入到缓存中*/
    public Object getEntry(String methedSign,RMICacheCallback callback){
        Map<String,Object> cacheObject = RMICacheHolder.newInstance().cacheEntry.get();
        Object cacheValue = cacheObject.get(methedSign);
        if(null == cacheValue){
            cacheValue = callback.RMIGet();
            cacheObject.put(methedSign, cacheValue);
        }
        return cacheValue;
    }
     
    /**根据方法的签名,获取方法的缓存对象*/
    public Object getEntry(String methodSign){
        Map<String,Object> cacheObject = RMICacheHolder.newInstance().cacheEntry.get();
        return cacheObject.get(methodSign);
    }
     
    /**缓存之中,放入数据*/
    public void putEntry(String methodSign,Object obj){
        Map<String,Object> cacheObject = RMICacheHolder.newInstance().cacheEntry.get();
        cacheObject.put(methodSign, obj);
    }
     
    /**清理线程缓存中的数据,由于现在大多数都是基于线程池的使用,所以这不清理操作必须存在*/
    public void clearThreadLocal(){
        RMICacheHolder.newInstance().cacheEntry.set(new HashMap<String,Object>());
    }
     
}

 

下面一个是一个callback的接口定义:

1
2
3
4
5
6
7
8
9
10
package rmicache;
 
public interface RMICacheCallback {
    /**
     * 远程获取这个结果
     * @return
     */
    public Object RMIGet();
 
}

 

结合一个例子使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package test;
 
import rmicache.RMICacheCallback;
import rmicache.RMICacheHolder;
 
/**
 * 辅助进行测试的方法
 *
 */
public class BizService {
 
    /**
     * 假设这个数据的获取非常消耗时间,在一个线程处理过程中,由于逻辑复杂,不能保证这个方法被人调用多次
     * 如果系统对于高并发和响应时间有很高的要求,那么多一个耗时的调用,是非常致命的。
     */
    public UserDO getUserDOByID(Long userId){
        try {
            Thread.sleep(1000);
        catch (InterruptedException e) {
        }
        UserDO user = new UserDO();
         
        user.setName("iamzhongyong_"+Thread.currentThread().getName());
        user.setAge(18);
        return user;
    }
    /**
     * 远程的包装类
     */
    public UserDO getUserDOByIdWraper(final Long userId){
        return (UserDO) RMICacheHolder.newInstance().getEntry("getUserDOByID"new RMICacheCallback() {    
            public Object RMIGet() {
                return getUserDOByID(userId);
            }
        });
    }
 
}

 

测试类:

1
2
3
4
5
6
直接调用远程,方法调用三次,耗时:3004
三次包装类调用,方法调用三次,有缓存,耗时:1010
线程数据清理,调用一次包装类,耗时:1000
异步方法调用三次,一次远程,一次缓存,耗时:2001
异步方法调用三次,全部是远程,耗时:3001
线程缓存清理后,异步方法调用三次,一次远程,一次缓存,耗时:2000

 

0
0
分享到:
评论
1 楼 7454103 2013-12-25  
仔细看完了!
写的还不错哦!
但是个人感觉 你是要打算实现一个方法级别的缓存!如果是这样的话 可以参考下Spring的方法 缓存设计!
至于RMI 关系不大!

相关推荐

Global site tag (gtag.js) - Google Analytics