一个复杂的系统,可能外部一个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
|