RMICache组件设计(remote method cache)

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

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

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

    ​    ​设计图如下:

    ​    ​RMICache组件设计(remote method cache)_第1张图片

上代码(目前还是一个初级版本  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

 

你可能感兴趣的:(cache)