由于监控链路升级的需要,需要对应用访问redis的性能进行监控,即需要在redis操作前后加上如下代码:
{
java.util.Map<String, String> tags = new java.util.HashMap<>();
tags.put("ops", "scan");
tags.put("appName", "xxx");
long begin = System.currentTimeMillis();
try {
String result = get("key");
return result;
} catch (Exception e) {
//record exception metric
throw new RuntimeException(e);
} finally {
//record count metric
//record time metric
}}
手动修改jedis源码并重新deploy效率太低,因此采用javassist修改源码的方式完成自动化埋点。
基本思路如下:
1.获取所有jedis & pipeline实现的接口中所定义的方法
2.获取所有jedis & pipeline自身定义的新方法
3.修改1 & 2中获取的方法的body,进行监控埋点
使用上述方法在demo中修改时能够完成目标,但将jar包引入生产项目启动时,发现如下异常:
javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of org/apache/catalina/loader/WebappClassLoader): attempted duplicate class definition for name: "redis/clients/jedis/Jedis"
at javassist.ClassPool.toClass(ClassPool.java:1099)
at javassist.CtClass.toClass(CtClass.java:1265)
错误的原因在于一个类一旦被加载,就无法再修改字节码的内容了,这是JVM运行机制所决定的。
检查项目代码发现,该项目将redis封装为一个jndi数据源,spring容器使用JndiObjectFactoryBean初始化config并生成对应的client。在此过程中jedis类已经被加载,从而导致后续修改字节码失败。
为了解决该问题,我们尝试在ObjectFactory的实现类(RedisConfigFactory)中使用static代码块提前修改jedis字节码,成功启动并输出监控指标。
另附小坑一个:
2.9.0版本的Jedis中不是所有方法都由接口类定义,如以下方法:
public String set(final String key, final String value, final String nxxx, final String expx,
final int time)
不是实现接口类方法而是在类中直接定义的,因此查找类方法时容易忽略。
在3.2.0版本中,set方法的众多参数已经由SetParam定义,因此不存在该问题。