如何复制Pinpoint中一条调用链的完整数据

如何复制Pinpoint中一条调用链的完整数据

如果你熟悉Pinpoint的话,你应该知道一条调用链包含哪些数据
在这里我指的是com.navercorp.pinpoint.web.controller.BusinessTransactionController#transactionInfo方法中查询会涉及到的HBase数据。
为什么会产生这个需求呢,HBase中的数据都是配置了TTL的,过一段时间会被清理,你可能就和要这条骨骼惊奇的调用链说拜拜了。
如果能够离线或者保存另外的HBase里,可以更快的复现场景进行调试和排查。


image.png

先看一下到底会查哪些表吧。

  • TraceV2
  • ApiMetaData
  • StringMetaData
  • SqlMetaData_Ver2

Pinpoint在构造调用链界面需要的信息的时候,TraceV2是一行数据,用事务号就能查询出来。
然后遍历这行数据里的Span和SpanEvent,使用包含的apiId,stringId,sqlId去后三个表对应查询所关联的数据。
后面三个,一个复杂的调用链会查询很多次。怎么才能知道呢?

下面只是记录一下本地试验的方法,仅在测试环境中使用。

利用Spring AOP 将hbase查询结果 插入到另外的hbase中

我想了下,如果在hbase查询的时候进行AOP拦截,并且把数据发送到另外一个hbase的话,这样不就能把一条调用链的数据给剥离出来了么?
我把将查询出来的数据插入到别的hbase的过程叫做逆转

我们要寻找一些合适的spring bean,因为spring的aop只能作用在spring创建的对象上。

首先我注意到 com.navercorp.pinpoint.common.hbase.RowMapper#mapRow

public interface RowMapper<T> {

    T mapRow(Result result, int rowNum) throws Exception;
}

第一个参数org.apache.hadoop.hbase.client.Result包含了查询出来的一行数据,现在就差个表名了。

仔细看pinpoint的代码,每次执行hbase操作前都会调用com.navercorp.pinpoint.common.hbase.TableDescriptor#getTableName获取表名。
这样设置一个线程上下文(https://github.com/apache/shiro/blob/master/core/src/main/java/org/apache/shiro/util/ThreadContext.java),就能将表名和多行数据完整联系在一起了。

最后线程上下文里面还需要设置一个是否逆转查询数据的标志。
对于查询单条调用链来说就是com.navercorp.pinpoint.web.service.SpanService#selectSpan方法进入的时候开启标志。

实现

首先仿照shiro弄个线程上下文。

package com.navercorp.pinpoint.web.dao;

import java.util.HashMap;
import java.util.Map;

/**
 * @author tankilo
 * https://github.com/apache/shiro/blob/master/core/src/main/java/org/apache/shiro/util/ThreadContext.java
 */
public final class ThreadContext {
    private ThreadContext() {

    }

    public static final String REVERSE = "REVERSE";
    public static final String TABLE_NAME = "TABLE_NAME";
    private static ThreadLocal<Map<String, Object>> resources = new InheritableThreadLocal<Map<String, Object>>() {
        @Override
        protected Map<String, Object> initialValue() {
            return new HashMap<>(8);
        }
    };

    public static void put(String key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }

        if (value == null) {
            remove(key);
            return;
        }
        ensureResourcesInitialized();
        resources.get().put(key, value);
    }

    private static void ensureResourcesInitialized() {
        if (resources.get() == null) {
            resources.set(new HashMap<>(8));
        }
    }

    public static void remove(String key) {
        Map<String, Object> map = resources.get();
        if (map != null) {
            map.remove(key);
        }
    }

    public static Object get(String key) {
        Map<String, Object> map = resources.get();
        if (map != null) {
            return map.get(key);
        } else {
            return null;
        }
    }

    private static Object getValue(Object key) {
        Map<String, Object> perThreadResources = resources.get();
        return perThreadResources != null ? perThreadResources.get(key) : null;
    }

    private static Boolean getBoolean(String key, Boolean defaultValue) {
        Object value = getValue(key);
        if (null != value) {
            return Boolean.valueOf(value.toString());
        }
        return defaultValue;
    }

    private static String getString(String key) {
        Object value = getValue(key);
        if (null != value) {
            return value.toString();
        } else {
            return null;
        }
    }

    public static void setReverse(boolean reverse) {
        put(REVERSE, reverse);

    }

    public static boolean isReverse() {
        return getBoolean(REVERSE, false);
    }

    public static void setTableName(String tableName) {
        put(TABLE_NAME, tableName);
    }

    public static String getTableName() {
        return getString(TABLE_NAME);
    }

    public static void remove() {
        resources.remove();
    }
}

切面

@Aspect
public class HbaseTemplateReverseAspect {

    @Autowired
    @Qualifier("hbaseTemplateReverse")
    private HbaseOperations2 template2;

    @Autowired
    private HbaseTableNameProvider tableNameProvider;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("execution(public * com.navercorp.pinpoint.common.hbase.RowMapper.mapRow(..))")
    public void pointCut() {
    }

    @After("pointCut() && args(result,rowNum)")
    public void doBefore(Result result, int rowNum) {
        if (ThreadContext.isReverse()) {
            String tableNameStr = ThreadContext.getTableName();
            TableName tableName = tableNameProvider.getTableName(tableNameStr);
            Put put = new Put(result.getRow());
            Cell[] rawCells = result.rawCells();
            for (Cell cell : rawCells) {
                put.addColumn(CellUtil.cloneFamily(cell), CellUtil.cloneQualifier(cell), cell.getTimestamp(), CellUtil.cloneValue(cell));
            }
            template2.asyncPut(tableName, put);
        }
    }

    @Before("execution(public * com.navercorp.pinpoint.web.dao.hbase.HbaseTraceDaoV2.selectSpan(..))")
    public void selectSpan() {
        ThreadContext.setTableName(HbaseTable.TRACE_V2.getName());
    }

    @AfterReturning(returning = "tableName", pointcut = "execution(public * com.navercorp.pinpoint.common.hbase.TableDescriptor.getTableName())")
    public void getTableName(TableName tableName) {
        ThreadContext.setTableName(tableName.getNameAsString());
    }

    @Around("execution(public * com.navercorp.pinpoint.web.service.SpanService.selectSpan(..))")
    public Object dealContext(ProceedingJoinPoint jp) throws Throwable {
        Object result;
        try {
            ThreadContext.setReverse(true);
            result = jp.proceed();
        } finally {
            ThreadContext.remove();
        }
        return result;
    }
}

遗憾

里面有些曲折,看上面代码也知道。
com.navercorp.pinpoint.web.dao.hbase.HbaseTraceDaoV2#spanMapperV2 不是spring bean,而是手动构造的。
这里我就没有继续弄下去了,因为感觉spring aop的局限性很大,计划用java instrumentation api,和pinpoint的agent一样重新弄下。

代码提交备份在我的github上 https://github.com/tankilo/pinpoint/tree/spring-aop-hbase-reverse

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容