目的:監(jiān)控Redis操作的執(zhí)行時(shí)間腻窒、執(zhí)行命令栅表、執(zhí)行參數(shù)和執(zhí)行結(jié)果
監(jiān)控操作時(shí)間基本邏輯
//開始統(tǒng)計(jì)
//do something
//結(jié)束統(tǒng)計(jì)
RedisTemplate核心執(zhí)行方法
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline){
//邏輯代碼
}
所有的方法調(diào)用最終都會(huì)執(zhí)行到這個(gè)方法上
而這個(gè)方法會(huì)獲取到與Redis的連接對(duì)象,將這個(gè)對(duì)象傳到回調(diào)函數(shù)中翰意,在回調(diào)函數(shù)中調(diào)用Redis。
所以可以在這個(gè)方法的執(zhí)行前后增加埋點(diǎn)操作,統(tǒng)計(jì)redis執(zhí)行時(shí)間
如繼承RedisTemplate類条辟,重寫execute方法,就可以在redis操作執(zhí)行前后統(tǒng)計(jì)執(zhí)行時(shí)間
@Override
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline){
//開始統(tǒng)計(jì)
super.execute(action,exposeConnection,pipeline);
//結(jié)束統(tǒng)計(jì)
//統(tǒng)計(jì)結(jié)果
}
但是excute方法的參數(shù)只有一個(gè)回調(diào)函數(shù)和兩個(gè)判斷條件宏胯,具體在回調(diào)函數(shù)調(diào)用的是redis的哪個(gè)操作羽嫡,是set還是delete等都無法知道,無法針對(duì)性的統(tǒng)計(jì)每個(gè)具體方法的執(zhí)行時(shí)間肩袍,無法獲取到相關(guān)的執(zhí)行參數(shù)杭棵,比如具體執(zhí)行了哪個(gè)方法、執(zhí)行的參數(shù)是哪些等氛赐。
所以閱讀execute方法的執(zhí)行邏輯魂爪,發(fā)現(xiàn)RedisTemplate在執(zhí)行回調(diào)函數(shù)的前后,給子類留了功能擴(kuò)展的方法艰管,很明顯是為了能夠在操作執(zhí)行前后能夠執(zhí)行相關(guān)的擴(kuò)展功能的操作滓侍,關(guān)鍵代碼如下
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
//從工廠中獲取連接
this.preProcessConnection(conn, existingConnection); //執(zhí)行回調(diào)函數(shù)方法前
Object result = action.doInRedis(connToExpose); //執(zhí)行回調(diào)函數(shù)方法
this.postProcessResult(result, connToUse, existingConnection); //執(zhí)行回調(diào)函數(shù)方法后
//釋放連接
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return connection;
}
protected <T> T postProcessResult(T result, RedisConnection conn, boolean existingConnection) {
return result;
}
可以看到preProcessConnection和postProcessResult這兩個(gè)方法,在RedisTemplate中都是留空的牲芋,并且方法類型都是protected撩笆,而且執(zhí)行的順序分別是在action.doInRedis()這個(gè)回調(diào)函數(shù)前后,所以很明顯是為了留給子類繼承缸浦,來進(jìn)行功能擴(kuò)展夕冲。所以,我們可以繼承這兩個(gè)方法裂逐,在這兩個(gè)方法中對(duì)Redis執(zhí)行時(shí)間進(jìn)行埋點(diǎn)統(tǒng)計(jì)歹鱼。
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
//開始統(tǒng)計(jì)
return connection;
}
protected <T> T postProcessResult(T result, RedisConnection conn, boolean existingConnection) {
//結(jié)束統(tǒng)計(jì)
//記錄執(zhí)行結(jié)果
return result;
}
但是這種方法同樣會(huì)出現(xiàn)上一種方法的問題,無法獲取到執(zhí)行的具體方法卜高、參數(shù)醉冤。因此,繼續(xù)單步至回調(diào)函數(shù)中篙悯,查看回調(diào)函數(shù)的執(zhí)行過程能否有機(jī)會(huì)進(jìn)行埋點(diǎn)蚁阳。對(duì)常用的redisTemplate.opsForValue().get()方法進(jìn)行跟蹤
public ValueOperations<K, V> opsForValue() {
if(this.valueOps == null) {
this.valueOps = new DefaultValueOperations(this);
}
return this.valueOps;
}
public V get(final Object key) {
return this.execute(new ValueDeserializingRedisCallback(this, key) {
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
return connection.get(rawKey);
}
}, true);
}
abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
private Object key;
public ValueDeserializingRedisCallback(Object key) {
this.key = key;
}
public final V doInRedis(RedisConnection connection) {
byte[] result = this.inRedis(AbstractOperations.this.rawKey(this.key), connection);
return AbstractOperations.this.deserializeValue(result);
}
protected abstract byte[] inRedis(byte[] var1, RedisConnection var2);
}
可以看到,這個(gè)方法最終的確是調(diào)用了redisTemplate的exeute方法鸽照,并且是在回調(diào)函數(shù)中調(diào)用RedisConnection的get方法來獲取redis的數(shù)據(jù)螺捐。所以我們需要對(duì)connection.set(rawKey, rawValue)這個(gè)方法進(jìn)行攔截,就可以知道執(zhí)行的參數(shù)、執(zhí)行的結(jié)果定血,更進(jìn)一步的思考赔癌,如果知道調(diào)用的是RedisConnection哪個(gè)方法,就可以知道在redis中澜沟,執(zhí)行的是那一個(gè)redis命令灾票。
//開始統(tǒng)計(jì)
//獲取參數(shù)
return connection.get(rawKey); //獲取命令
//結(jié)束統(tǒng)計(jì)
//獲取結(jié)果
查看RedisConnection的代碼,發(fā)現(xiàn)它是一個(gè)接口茫虽,繼承RedisCommands刊苍,而RedisCommands繼承了各種與Redis相關(guān)的命令接口。因此濒析,知道知道在調(diào)用RedisConnection的時(shí)候正什,調(diào)用的是哪一個(gè)接口,就可以知道執(zhí)行的是redis的哪一個(gè)命令号杏。并且婴氮,由于我們不可能修改RediTemplate的源碼,來在每一個(gè)操作的地方進(jìn)行埋點(diǎn)盾致,所以我們就可以從RedisConnection這個(gè)對(duì)象進(jìn)行處理主经,在RedisConnection的方法執(zhí)行前后,進(jìn)行攔截庭惜,于是可以考慮用動(dòng)態(tài)代理的方式旨怠,攔截RedisConnection對(duì)象的方法,在執(zhí)行方法的前后進(jìn)行埋點(diǎn)統(tǒng)計(jì)工作蜈块。于是我們可以繼承RedisTemplate的preProcessConnection函數(shù)鉴腻,對(duì)RedisConnection進(jìn)行動(dòng)態(tài)代理并返回代理后的RedisConnection對(duì)象,這樣實(shí)際執(zhí)行的時(shí)候百揭,doInRedis這個(gè)回調(diào)函數(shù)中執(zhí)行的RedisConnection就是我們動(dòng)態(tài)代理后的對(duì)象爽哎。
@Override
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
//對(duì)Connection對(duì)象進(jìn)行動(dòng)態(tài)代理
Class[] interfacesForClass = ClassUtils.getAllInterfacesForClass(connection.getClass(), this.getClass().getClassLoader());
connection = (RedisConnection)
Proxy.newProxyInstance(connection.getClass().getClassLoader(),interfacesForClass,new MyRedisConnection(connection,this)) ;
return super.preProcessConnection(connection, existingConnection);
}
private class MyRedisConnection implements InvocationHandler{
private final RedisConnection target ;
protected CatRedisConnection(RedisConnection target){
this.target = target ;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//開始統(tǒng)計(jì)
result = method.invoke(this.target,args) ;
//結(jié)束統(tǒng)計(jì)
//獲取redis命令、參數(shù)器一、結(jié)果
return result;
}
}
這樣就實(shí)現(xiàn)了對(duì)Redis操作的埋點(diǎn)课锌,能夠統(tǒng)計(jì)每一個(gè)Redis操作的執(zhí)行時(shí)間、執(zhí)行命令祈秕、執(zhí)行參數(shù)和執(zhí)行結(jié)果渺贤。