Ali-dp

相关Java基础

spring boot starter

你一直用的 Spring Boot Starter 是怎么回事

spi

Java进阶之SPI机制详解

相关阿里中间件

HSF

hsf参考手册
HSF(High-speed Service Framework)作为阿里巴巴的基础中间件,联通不同的业务系统,解耦系统间的实现依赖。HSF 从分布式应用的层面,统一了服务的发布/调用方式,从而帮助用户可以方便、快速的开发分布式应用,以及提供或使用公共功能模块。为用户屏蔽了分布式领域中的各种复杂技术细节,如:远程通讯、序列化实现、性能损耗、同步/异步调用方式的实现等。

整体架构

hsf_architecture_优化.png

使用

Provider

  • API形式配置HSF服务

    // ---------------------- 装配 -----------------------//
    // [设置] HSF服务发布逻辑
    HSFApiProviderBean hsfApiProviderBean = new HSFApiProviderBean();
    // [设置] 发布服务的接口
    hsfApiProviderBean.setServiceInterface("com.alibaba.middleware.hsf.guide.api.service.OrderService");
    // [设置] 服务的实现对象
    hsfApiProviderBean.setTarget(target);
    // [设置] 服务的版本
    hsfApiProviderBean.setServiceVersion("1.0.0");
    // [设置] 服务的归组
    hsfApiProviderBean.setServiceGroup("HSF");
    // [设置] 服务的响应时间
    hsfApiProviderBean.setClientTimeout(3000);
    // [设置] 服务传输业务对象时的序列化类型
    hsfApiProviderBean.setPreferSerializeType("hessian2");
    // ---------------------- 发布 -----------------------//
    // [发布] HSF服务
    hsfApiProviderBean.init();
    
  • xml配置HSF服务

    <bean class="com.taobao.hsf.app.spring.util.HSFSpringProviderBean" init-method="init">
      <!--[设置] 发布服务的接口-->
      <property name="serviceInterface" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
      <!--[设置] 服务的实现对象 target必须配置[ref],为需要发布为HSF服务的spring bean id-->
      <property name="target" ref="引用的BeanId"/>
      <!--[设置] 服务的版本-->
      <property name="serviceVersion" value="1.0.0"/>
      <!--[设置] 服务的归组-->
      <property name="serviceGroup" value="HSF"/>
      <!--[设置] 服务的响应时间-->
      <property name="clientTimeout" value="3000"/>
      <!--[设置] 服务传输业务对象时的序列化类型-->
      <property name="preferSerializeType" value="hessian2"/>
    </bean>
    
  • 注解配置HSF服务

首先是在项目中增加依赖starter:

<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>

然后将@HSFProvider配置到实现的类型上,上述例子中的API配置等同于如下注解配置:

@HSFProvider(serviceInterface = OrderService.class, serviceGroup = "HSF", serviceVersion = "1.0.0", clientTimeout = 3000)
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDAO orderDAO;

    @Override
    public OrderModel queryOrder(Long id) {
        return orderDAO.queryOrder(id);
    }
}

Consumer

  • API形式配置HSF服务

    // ---------------------- 装配 -----------------------//
    // [设置] HSF服务订阅逻辑
    HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
    // [设置] 订阅服务的接口
    hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.OrderService");
    // [设置] 服务的版本
    hsfApiConsumerBean.setVersion("1.0.0");
    // [设置] 服务的归组
    hsfApiConsumerBean.setGroup("HSF");
    // ---------------------- 订阅 -----------------------//
    // [订阅] HSF服务,同步等待地址推送,默认false(异步),同步默认超时时间3000毫秒
    hsfApiConsumerBean.init(true);
    

// ---------------------- 代理 -----------------------//
// [代理] 获取HSF代理
OrderService orderService = (OrderService) hsfApiConsumerBean.getObject();
// ---------------------- 调用 -----------------------//
// [调用] 发起HSF调用
OrderModel orderModel = orderService.queryOrder(1L);

- xml配置HSF服务
```xml
<bean id="CallHelloWorld" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean">
    <!--[设置] 订阅服务的接口-->
    <property name="interfaceName" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
    <!--[设置] 服务的版本-->
    <property name="version" value="1.0.0"/>
    <!--[设置] 服务的归组-->
    <property name="group" value="HSF"/>
</bean>
  • 注解配置HSF服务

首先是在项目中增加依赖starter:

<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>

通常一个HSF Consumer需要在多个地方使用,但并不需要在每次使用的地方都用@HSFConsumer来标记。只需要写一个统一个Config类,然后在其它需要使用的地方,直接@Autowired注入即可上述例子中的API配置等同于如下注解配置:

@Configuration
public class HsfConfig {
    @HSFConsumer(serviceVersion = "1.0.0", serviceGroup = "HSF")
    OrderService orderService;

}

RPCFilter

//com.taobao.hsf.invocation.filter.RPCFilter
@Deprecated
@Scope(Scope.Option.PROTOTYPE)
public interface RPCFilter {
    ListenableFuture<RPCResult> invoke(InvocationHandler nextHandler, Invocation invocation) throws Throwable;

    void onResponse(Invocation invocation, RPCResult rpcResult);

}

HSF 内部以SPI方式设计了调用过滤器,并且能够主动发现用户的调用过滤器扩展点,将其集成到 HSF 调用链路中,使扩展方能够方便的对 HSF 请求进行扩展处理:

//com.taobao.hsf.filter.FilterInvocationHandler#start
@Override
    public void start() {
        if (RPCFilterBuilder.hasOldExtension(serviceMetadata)) {
            hasOldExtension = true;
            InvocationHandlerAdapter invocationHandlerAdapter = new     InvocationHandlerAdapter(delegate);
            //spi方式集成调用链
            filterDelegate = RPCFilterBuilder.buildInvokerChain(invocationHandlerAdapter, serviceMetadata);
        } else {
            hasOldExtension = false;
        }
    }



// com.taobao.hsf.filter.RPCFilterBuilder#buildInvokerChain
public static InvocationHandler buildInvokerChain(InvocationHandler nextHandler, ServiceMetadata metadata) {
        List<RPCFilter> filters = HSFServiceContainer.getInstances(RPCFilter.class);
        setMetadata(metadata, filters);
        filters = sift(filters, metadata.isProvider());

        List<BlockAwareFilter> blockAwareFilters = getBlockAwareFilter(filters);

        RPCFilterNode head = new HeadNode(blockAwareFilters);
        RPCFilterNode tail = new TailNode(nextHandler);
        head.next = tail;
        tail.pre = head;

        for (RPCFilter rpcFilter : filters) {
            RPCFilterNode current = new RPCFilterNode(rpcFilter);
            RPCFilterNode pre = tail.pre;
            current.pre = pre;
            current.next = tail;
            pre.next = current;
            tail.pre = current;
        }
        return head;
    }

鹰眼系统、Ali-dp等多种中间件都通过实现RPCFliter的方式来扩展HSF操作。

鹰眼系统

EagleEye (鹰眼)
通过收集和分析在不同的网络调用中间件上的日志埋点,可以得到同一次请求上的各个系统的调用链关系,有助于梳理应用的请求入口与服务的调用来源、依赖关系,同时,也对分析系统调用瓶颈、估算链路容量、快速定位异常有很大帮助。另外,业务方也可以在调用链上添加自己的业务埋点,使网络调用和实际业务内容得到关联。

整体架构

eagleId001.png
eagleId002.png

EagleEye.java

鹰眼的信息和操作都是通过EagleEye类来实现的。

日志地址

// com.taobao.eagleeye;

/**
 * EagleEye 日志默认放置的位置:~/logs/eagleeye/
*/
static final String EAGLEEYE_LOG_DIR = locateEagleEyeLogPath();

当前线程关联的RPC调用上下文

EagleEye基于ThreadLocal实现上下文的存储,所以当业务方使用同步的方式时对使用者透明,根据线程维度来操作鹰眼信息:

public final class RpcContext_inner extends AbstractContext implements SpanContext {

    //EagleEye内容存放在线程的RpcContext_inner中
    private static final ThreadLocal<RpcContext_inner> THREAD_LOCAL;
    // .....
 }
// com.taobao.eagleeye;

/**
     * 直接取得当前的 RpcContext,用于备份 RPC 调用上下文(不做 Map 转换),同时记录当前操作的线程ID,作为模式切换的判断条件
     * @return
     * @see #setRpcContext(RpcContext_inner) 还原 RpcContext
     */
    public static RpcContext_inner getRpcContext() {
        RpcContext_inner ctx = RpcContext_inner.get();
        try {
            if (ctx != null) {
                ctx.setThreadId(Thread.currentThread().getId());
            }
        } catch (Throwable re) {
            selfLog("[ERROR] getRpcContext", re);
        }
        return ctx;
    }

    /**
     * @param context 通过传入context,设置threadlocal变量
     * @see #getRpcContext() 直接获取 RpcContext 对象
     */
    public static void setRpcContext(RpcContext_inner context) {
        if (context != null) {
            // @since 1.5.1 避免多线程的 setRpcContext 后,对 UserData/Attribute 的操作互相干扰
            context = context.cloneInstance();
            if (context.getThreadId() != Thread.currentThread().getId()) {
                context.setAsyncMode(true);
            }
        }
        RpcContext_inner.set(context);
    }

    /**
     * 清理全部调用上下文信息
     */
    public static void clearRpcContext() {
        RpcContext_inner.clear();
    }

获取traceId

TraceID是RPC链路的全局唯一标识,通过TraceId,Eagleye处理日志时可以把一次前端请求在不同服务器记录的调用日志关联起来,重新组合成当时这个请求的调用链。理论上TraceID在一次请求中只应该被生成一次,并跟随请求传递至RPC调用经过的各个节点.

// com.taobao.eagleeye;

/**
     * 获取全局唯一的Traceid
     * @return
     */
    public static String getTraceId() {
        RpcContext_inner ctx = RpcContext_inner.get();
        return null == ctx ? null : ctx.traceId;
    }

获取rpcId

为了区别同一个调用链下多个网络调用的顺序和嵌套层次,EagleEye还需要传输和记录RpcId,RpcId用0.X1.X2.X3.....Xi来表示,Xi都是非负整数,根节点的RpcId固定从0开始,第一层网络调用的RpcId是0.X1,第二层的则为0.X1.X2,依次类推,通过RpcId,可以准确的还原出调用链上每次调用的层次关系和兄弟调用之间的先后顺序。

    /**
     * 获取当前rpc调用层次
     * @return
     */
    public static String getRpcId() {
        RpcContext_inner ctx = RpcContext_inner.get();
        return null == ctx ? null : ctx.rpcId;
    }

获取userData

EagleEye日志中的UserData字段为业务自定义的数据,该字段包含了RPC层的业务数据,该数据分为两种: 1.本地数据:记录在本地日志中; 2.链路数据:记录在本地日志中,并会跟随调用链传递至下游。

public static String getUserData(String key) {
        RpcContext_inner ctx = RpcContext_inner.get();
        return null != ctx ? ctx.getUserData(key) : null;
}

开启新的trace

// com.taobao.eagleeye;

/**
     * 开启新的trace,该接口仅提供给最源头的前中间件或自己启动的定时程序调用,
     * 支持配置 rpcId 来开启一个嵌套的调用链。使用该接口时,必须最后调用endTrace结束。
     * @param traceId 全局唯一的id,如果传入的值为空或者null,系统会自动生成
     * @param rpcId 额外指定 rpcId,如果为 <code>null</code>,使用 {@link #ROOT_RPC_ID}
     * @param traceName 用户自定义的入口标识值,不能为 <code>null</code>,
     *                 建议传入能够唯一标识入口的数据,例如用户访问网络的 http url
     * @param rpcType EagleEye.TYPE_TRACE EagleEye.TYPE_CUSTOM_TRACE
     * @param params 调用参数
     * @since 1.2.6
     */
    public static void startTrace(String traceId, String rpcId, String traceName, int rpcType, Object... params) {
        if (traceName == null) {
            return;
        }

        traceId = EagleEyeCoreUtils.trim(traceId);

        if (!EagleEyeCoreUtils.isValidTraceId(traceId)) {
            traceId = TraceIdGenerator.generate();
            rpcId = ROOT_RPC_ID;
        } else if (!EagleEyeCoreUtils.isValidRpcId(rpcId)) {
            rpcId = ROOT_RPC_ID;
        }

        RpcContext_inner ctx = RpcContext_inner.get();
        if (ctx != null && ctx.traceId != null) {
            // 重复 startTrace 的检测
            if (!ctx.traceId.equals(traceId) || !traceName.equals(ctx.traceName)) {
                // 说明有潜在的埋点问题,先把前面那个调用链结束掉
                selfLog("[WARN] duplicated startTrace detected, overrided " + ctx.traceId +
                        " (" + ctx.traceName + ") to " + traceId + " (" + traceName + ")");
                endTrace();
            } else {
                // traceId 和 traceName 都相同,说明是 EagleEyeFilter 和 tbsession 有重复埋点
                return;
            }
        }

        try {

            ctx = new RpcContext_inner(traceId, rpcId);
            RpcContext_inner.set(ctx);
            ctx.startTrace(traceName, rpcType, params);
        } catch (Throwable re) {
            selfLog("[ERROR] startTrace", re);
        }
    }

结束一次跟踪

// com.taobao.eagleeye;

/**
     * 结束一次跟踪,Threadlocal 变量会被清空,调用了 startTrace 及 startTrace4Top 的必须在finally或者最后调用该接口。
     * @param resultCode
     * @param type
     * @since 1.2.0
     * @deprecated 请使用 {@link #endTrace(String)}
     */
    public static void endTrace(String resultCode, int type) {
        RpcContext_inner root = getRootCtx();
        if (null == root) {
            return;
        }
        root.setRpcType(type);
        endTraceInner(root, resultCode);
    }

private static void endTraceInner(RpcContext_inner root, String resultCode) {
        try {
            root.endTrace0(resultCode);
            commitRpcContext(root);
        } catch (IllegalContextException ice) {
            // quietly
        } catch (Throwable re) {
            selfLog("[ERROR] endTrace", re);
        } finally {
            clearRpcContext();
        }
    }

hsf调用时如何传递鹰眼信息

com.taobao.eagleeye.EagleEyeFilter: 获取invocation中的RpcContext,并更新当前线程的RpcContext.

// com.taobao.hsf.plugins.eagleeye;

@Order(250)
public class EagleEyeServerFilter implements ServerFilter, BlockAwareFilter {


    public ListenableFuture<RPCResult> invoke(InvocationHandler invocationHandler,
                                              Invocation invocation) throws Throwable {
        Object oldContext = EagleEye.getRpcContext();
        try {
            invocation.put(EAGLEEYE_EXECUTED_KEY, Boolean.TRUE);

            handleEagleEyeServerRecv(invocation);

            invocation.put(INVOCATION_EAGLEEYE_CONTEXT_KEY, logService.getRpcContext());
            invocation.addContextAware(eagleEyeServerContextAware);

            //把traceId,rpcId放到invocation的context里,打日志的时候把invocation.context打出来,不要把HSFRequest里的context打出来,内容太多了
            invocation.put(EagleEyeConstants.EAGLEEYE_TRACE_ID_KEY, logService.getTraceId());
            invocation.put(EagleEyeConstants.EAGLEEYE_RPC_ID_KEY, logService.getRpcId());

            ListenableFuture<RPCResult> rpcFuture = invocationHandler.invoke(invocation);
            return rpcFuture;
        } finally {
            EagleEye.setRpcContext(oldContext);
        }
    }


//获取invocation中的hsfRequest
private void handleEagleEyeServerRecv(Invocation invocation) {
        final HSFRequest hsfRequest = invocation.getHsfRequest();
        final String serviceUniqueName = hsfRequest.getTargetServiceUniqueName();
        final String methodName = hsfRequest.getMethodName();
        final String[] sig = hsfRequest.getMethodArgSigs();

        String methodNameToLog = methodLogNameService.convertToLogName(invocation, methodName, sig,
                hsfRequest.getMethodArgs());

        Object rpcCtx = hsfRequest.getRequestProp(EagleEyeConstants.REQUEST_EAGLEEYE_CONTEXT);

        if (Boolean.TRUE == invocation.get(IS_LOCAL_INVOKE_ATTRIBUTE_KEY) && (rpcCtx instanceof Map)) {
            Map<String, String> contextMap = (Map<String, String>) rpcCtx;
            contextMap.put(EagleEye.ATTRIBUTE_KEY, EagleEye.exportAttribute());
        }

        logService.setRpcContext(rpcCtx);

        logService.rpcServerRecv(serviceUniqueName, methodNameToLog, EagleEye.TYPE_HSF_SERVER, hsfRequest.getMethodArgs());

        if (hsfRequest.getRequestProp(RemotingConstants.REQUEST_OF_MTOP_UNCENTER_FLAG) != null) {
            try {
                if ((Boolean) hsfRequest.getRequestProp(RemotingConstants.REQUEST_OF_MTOP_UNCENTER_FLAG)) {
                    logService.remoteIp(env.getPubHost());
                }
            } catch (Exception e) {
                logService.remoteIp(invocation.getPeerIP());
            }
        } else {
            logService.remoteIp(invocation.getPeerIP());
        }

        //set consumer's container id
        Object containerId = invocation.getRequestProp(HSFConstants.CONTAINER_ID);
        if (containerId != null) {
            logService.setContainerId((String) containerId);
        }

        Object httpEagleEyeContext = hsfRequest.getRequestProp(HttpHeaderKey.HTTP_EAGLEEYE_CONTEXT);
        if (httpEagleEyeContext == null || !(httpEagleEyeContext instanceof Map))
            return;

        for (Map.Entry<String, String> entry : ((Map<String, String>) httpEagleEyeContext).entrySet()) {
            logService.putUserData(entry.getKey(), entry.getValue());
        }
    }

}

Ali-dp

如何使用

pom.xml引入依赖

<dependency>
     <groupId>com.alibaba.nrep.dp</groupId>
     <artifactId>debug-platform-starter</artifactId>
     <version>${ali-dp-version}</version>
</dependency>

设置trace策略

预发(application-dev.properties):

# 开启预发采集
dev.trace.preEnable=true
# 建议开启 开启HSF Provider端采集
dev.trace.hsf.server=true
# 建议开启 开启HSF Consumer采集
dev.trace.hsf.client=true
# 建议开启 开启Metaq采集
dev.trace.metaq.enable=true
# 建议开启 开启DPTrace采集采集
dev.trace.dptrace.enable=true
# 按需开启 开启HTTP采集采集,
dev.trace.http.enable =false

线上(application-prod.properties):

#总控开关
dev.trace.onlineEnable=true

# 开启HSF Provider端采集
dev.trace.hsf.server=true
# 开启HSF Consumer采集
dev.trace.hsf.client=true
# 开启Metaq采集
dev.trace.metaq.enable=true
# 开启DPTrace采集采集
dev.trace.dptrace.enable=true
# 开启HTTP采集采集
dev.trace.http.enable=true

查看日志,是否启动

> cd {APP_NAME}/logs/middleware/adp
> ll
total 89868
-rw-rw-r-- 1 admin admin    16395 Nov 29 15:04 adp.log
-rw-rw-r-- 1 admin admin 91995264 Dec  5 15:40 local-trace.log

查看adp.log :

01 2021-11-24 19:27:47.349 INFO [main :FinTraceMainLogger] QUICK-MOCK ENABLE Aspect MODEL
01 2021-11-24 19:27:48.995 INFO [main :FinTraceMainLogger] ============== ADP_TRACE_STARTER LOADING.... ===========
01 2021-11-24 19:27:48.997 INFO [main :FinTraceMainLogger] Version:1.0.4.1  AppName:sgth-audit  IP:33.4.132.237
01 2021-11-24 19:27:48.997 INFO [main :FinTraceMainLogger] ADP平台 链路查询:http://ali-dp.alibaba-inc.com/traceQuery?appName=sgth-audit&ip=33.4.132.237&env=online
01 2021-11-24 19:27:48.997 INFO [main :FinTraceMainLogger] ADP平台 钉钉答疑:31928176 Doc:https://yuque.antfin-inc.com/youjun.myj/hm1fyg/ohf1la
01 2021-11-24 19:27:49.001 INFO [main :FinTraceMainLogger] current env is online
01 2021-11-24 19:27:49.001 INFO [main :FinTraceMainLogger] ADP-TRACE-STARTER enable true
01 2021-11-24 19:27:49.016 INFO [main :FinTraceMainLogger] ADP-TRACE-STARTER print type: LOG
01 2021-11-24 19:27:49.016 INFO [main :FinTraceMainLogger] ADP-TRACE-STARTER start success !!! enableServer:true enableClient:true metaqEnable:true tddlEnable:false httpEnable:false dpTraceEnable:true dtsEnable:false mockEnable:false version:1.0.4.1
01 2021-11-24 19:27:49.020 INFO [main :FinTraceMainLogger] [INFO] ADP-TRACE-STARTER start PushController...
01 2021-11-24 19:27:49.023 INFO [main :FinTraceMainLogger] AutoLoad DP_TRACE_CONTROL_HSF START...sgth-audit
01 2021-11-24 19:27:49.034 INFO [main :FinTraceMainLogger] AutoLoad DP_TRACE_CONTROL_HSF SUCCESS.
01 2021-11-24 19:27:49.211 INFO [main :FinTraceMainLogger] ADP-TRACE-STARTER metaq enable success.
01 2021-11-24 19:27:49.215 INFO [main :FinTraceMainLogger] 加载情况 {"commonExclude":"","dptraceEnable":true,"dptraceFrequency":1,"dtsEnable":false,"dtsFrequency":1,"hsfClient":true,"hsfExclude":"","hsfExcludeOutput":"","hsfFrequency":1,"hsfServer":true,"httpEnable":false,"httpFrequency":1,"httpPushBatchSize":20,"httpPushMaxCapacity":200,"httpPushSendPeriod":2,"issEnable":false,"logEnable":false,"metaqEnable":true,"metaqFrequency":1,"mockEnable":false,"notifyEnable":false,"printType":"LOG","tairEnable":false,"tddlEnable":false,"tddlFrequency":1}

如何启动

spring-boot会自动扫描并加载debug-platform-startercom.alibaba.adp.TraceConfiguration:
startup.png
TraceConfiguration的操作如下:

加载TraceProperties

TraceProperties的许多参数我们在前面配置过了。

// com.alibaba.adp.TraceConfiguration
    @Bean
    public TraceProperties dpTraceProperties() {
        return new TraceProperties();
    }
public class TraceProperties {

    private Boolean blackServer;

    /**
     * Trace信息堆积最大数据量
     */
    private Integer traceContentMapMaxSize ;

    /**
     * Log信息堆积最大数据量
     */
    private Integer logContentMapMaxSize ;

    /**
     * 是否开启Log跟踪
     */
    private Boolean logEnable;

    /**
     * 日志采集级别 DEBUG,INFO,WARN,ERROR ,默认采集级别为WARN
     */
    private String logLevel;

    /**
     * 是否开启Notify跟踪
     */
    private Boolean notifyEnable;

    /**
     * 是否开启Log跟踪
     */
    private Boolean tairEnable;
    /**
     * 是否开启ISS跟踪
     */
    private Boolean issEnable;

    /**
     * HTTP
     */

    /**
     * 是否开启sql跟踪
     */
    @Value("${dev.trace.http.enable:#{null}}")
    private Boolean httpEnable;

    /**
     * 采样频率
     * <p>计算公式为 1/frequency
     * <p>设置1为 1/1 =100%
     * <p>全部采集 设置 100 为 1/100 = 1% 采集
     */
    @Value("${dev.trace.http.frequency:1}")
    private Integer httpFrequency = 1;

    /**
     * DPTRACE
     */

    /**
     * 是否开启
     */
    @Value("${dev.trace.dptrace.enable:#{null}}")
    private Boolean dptraceEnable;

    /**
     * 采样频率
     * <p>计算公式为 1/frequency
     * <p>设置1为 1/1 =100%
     * <p>全部采集 设置 100 为 1/100 = 1% 采集
     */
    @Value("${dev.trace.dptrace.frequency:1}")
    private Integer dptraceFrequency = 1;

    /**
     * TDDL
     */

    /**
     * 是否开启sql跟踪
     */
    @Value("${dev.trace.tddl.enable:#{null}}")
    private Boolean tddlEnable;

    /**
     * 采样频率
     * <p>计算公式为 1/frequency
     * <p>设置1为 1/1 =100%
     * <p>全部采集 设置 100 为 1/100 = 1% 采集
     */
    @Value("${dev.trace.tddl.frequency:1}")
    private Integer tddlFrequency = 1;

    /**
     * 不打日志
     */
    @Value("${dev.trace.tddl.excludeType:#{null}}")
    private String tddlExcludeType;

    /**
     * METAQ
     */

    /**
     * 是否开启接收消息跟踪
     */
    @Value("${dev.trace.metaq.enable:#{null}}")
    private Boolean metaqEnable;

    /**
     * 采样频率
     * <p>计算公式为 1/frequency
     * <p>设置1为 1/1 =100%
     * <p>全部采集 设置 100 为 1/100 = 1% 采集
     */
    @Value("${dev.trace.metaq.frequency:1}")
    private Integer metaqFrequency = 1;

    /**
     * DTS
     */

    /**
     * 是否开启接收消息跟踪
     */
    @Value("${dev.trace.dts.enable:#{null}}")
    private Boolean dtsEnable;

    /**
     * 采样频率
     * <p>计算公式为 1/frequency
     * <p>设置1为 1/1 =100%
     * <p>全部采集 设置 100 为 1/100 = 1% 采集
     */
    @Value("${dev.trace.dts.frequency:1}")
    private Integer dtsFrequency = 1;

    /**
     * HSF
     */

    /**
     * 是否开启ServerFilter 默认值true
     */
    @Value("${dev.trace.hsf.server:#{null}}")
    private Boolean hsfServer;

    /**
     * 是否开启ClientFilter 默认值false
     */
    @Value("${dev.trace.hsf.client:#{null}}")
    private Boolean hsfClient;

    /**
     * 采样频率
     * <p>计算公式为 1/frequency
     * <p>设置1为 1/1 =100%
     * <p>全部采集 设置 100 为 1/100 = 1% 采集
     */
    @Value("${dev.trace.hsf.frequency:1}")
    private Integer hsfFrequency = 1;

    /**
     * 不打日志
     */
    @Value("${dev.trace.hsf.exclude:#{null}}")
    private String hsfExclude;
    /**
     * 接口输出结果隐藏
     */
    @Value("${dev.trace.hsf.excludeOutput:#{null}}")
    private String hsfExcludeOutput;

    /**
     * 通用
     */
    @Value("${dev.trace.printType:#{null}}")
    private PrintType printType;

    @Value("${dev.trace.dailyEnable:#{null}}")
    private Boolean dailyEnable;

    @Value("${dev.trace.preEnable:#{null}}")
    private Boolean preEnable;

    @Value("${dev.trace.onlineEnable:#{null}}")
    private Boolean onlineEnable;

    /**
     * 排除接口 interfaceName#MethodName or interfaceName
     */
    @Value("${dev.trace.common.exclude:#{null}}")
    private String commonExclude;

    /**
     * http推送batchSize
     */
    @Value("${dev.trace.http.push.batchSize:#{null}}")
    private Integer httpPushBatchSize;

    /**
     * http推送batchSize
     */
    @Value("${dev.trace.http.push.sendPeriod:#{null}}")
    private Integer httpPushSendPeriod;

    /**
     * http推送batchSize
     */
    @Value("${dev.trace.http.push.maxCapacity:#{null}}")
    private Integer httpPushMaxCapacity;

    @Value("${quick.mock.enable:#{null}}")
    private Boolean mockEnable;
}

init()初始化操作

// com.alibaba.adp.TraceConfiguration

@PostConstruct
public void init() {
        APP_NAME = AppUtil.appName(null);

        logger.info("============== ADP_TRACE_STARTER LOADING.... ===========");
        logger.info(String.format("Version:%s  AppName:%s  IP:%s", CURRENT_VERSION, APP_NAME, LOCAL_IP));
        String hostUrl = String.format(ADP_HOST_URL, APP_NAME, LOCAL_IP, EnvUtil.getCurrentEnv());
        logger.info(String.format("ADP平台 链路查询:%s ", hostUrl));
        logger.info(String.format("ADP平台 钉钉答疑:31928176 Doc:https://yuque.antfin-inc.com/youjun.myj/hm1fyg/ohf1la"));

        EnvUtil.setActiveProfiles(DiamondProfiles.load(false));

        // 先获取当前环境的策略
        globalEnable = enableByConfigAndEnv();
        logger.info("ADP-TRACE-STARTER enable {}", globalEnable);

        // 全局开关未开启,退出
        if (!globalEnable) {
            TraceStrategyManager.closeAll();
            return;
        }

        if (EnvUtil.isDaily()) {
            //日常环境默认开启
            TraceStrategyManager.openAll();
        } else {
            //非日常环境默认关闭,等待远程控制
            TraceStrategyManager.closeAll();
        }
        try {
            TraceStrategyManager.adjustStrategy(traceProperties);
        } catch (Throwable t) {
            logger.error("ADP-TRACE-STARTER adjustStrategy fail!!");
        }

        if (StringUtils.equals(APP_NAME,"unknow")) {
            logger.error("APP-ERROR",
                "ADP-TRACE-STARTER start missing appName.Please config project.name or spring.application.name");
        }
        PushController.start();
        initHsfControlClient();

        ControlEventSender.sendControlEvent(EventType.START);
        logger.info(String.format("加载情况 %s ", JSON.toJSONString(TraceStrategyManager.currentTraceProperties())));
        logger.info("==========================================================");
        // 启动心跳发送线程
        new Thread(metaqHeartThread).start();
    }

全局开关

// com.alibaba.adp.TraceConfiguration

// 先获取当前环境的策略
globalEnable = enableByConfigAndEnv();
logger.info("ADP-TRACE-STARTER enable {}", globalEnable);

// 全局开关未开启,退出
if (!globalEnable) {
     TraceStrategyManager.closeAll();
      return;
}

默认trace策略

  1. 日常环境默认开启所有trace策略, 非日常环境默认关闭所有trace策略

    // com.alibaba.adp.TraceConfiguration
    if (EnvUtil.isDaily()) {
      //日常环境默认开启
      TraceStrategyManager.openAll();
    } else {
      //非日常环境默认关闭,等待远程控制
      TraceStrategyManager.closeAll();
    }
    
```java
// com.alibaba.fin.infrastructure.trace.processor.TraceStrategyManager
public static void closeAll() {
        HsfTraceStrategy.setEnableServer(false);
        HsfTraceStrategy.setEnableClient(false);
        MetaqTraceStrategy.setEnable(false);
        TddlTraceStrategy.setEnable(false);
        HttpTraceStrategy.setEnable(false);
        DptraceTraceStrategy.setEnable(false);
        DtsTraceStrategy.setEnable(false);
        MockStrategy.setEnable(false);
        IssTraceStrategy.setEnable(false);
        LogTraceStrategy.setEnable(false);
        NotifyTraceStrategy.setEnable(false);
    }

    public static void openAll() {
        HsfTraceStrategy.setEnableServer(true);
        HsfTraceStrategy.setEnableClient(true);
        MetaqTraceStrategy.setEnable(true);
        TddlTraceStrategy.setEnable(true);
        HttpTraceStrategy.setEnable(true);
        DptraceTraceStrategy.setEnable(true);
        DtsTraceStrategy.setEnable(true);
        MockStrategy.setEnable(true);
        IssTraceStrategy.setEnable(true);
        LogTraceStrategy.setEnable(true);
        NotifyTraceStrategy.setEnable(true);
    }

配置策略

根据traceProperties设置trace策略:

// com.alibaba.adp.TraceConfiguration

try {
     TraceStrategyManager.adjustStrategy(traceProperties);
 } catch (Throwable t) {
   logger.error("ADP-TRACE-STARTER adjustStrategy fail!!");
}
//com.alibaba.fin.infrastructure.trace.processor.TraceStrategyManager

public static void adjustStrategy(TraceProperties traceProperties) {

        settingExclude(traceProperties);

        settingFrequency(traceProperties);

        settingEnable(traceProperties);

        if (Objects.nonNull(traceProperties.getPrintType())) {
            PrintTypeStrategy.setPrintType(traceProperties.getPrintType());
        }

        settingPushProperties(traceProperties);

        settingMock(traceProperties);

        logger.info(String.format("ADP-TRACE-STARTER print type: %s", PrintTypeStrategy.getPrintType().name()));

        logger.info("ADP-TRACE-STARTER start success !!! "
                + "enableServer:{} enableClient:{} metaqEnable:{} tddlEnable:{}" +
                " httpEnable:{} dpTraceEnable:{} dtsEnable:{} mockEnable:{} version:{}",
            HsfTraceStrategy.isEnableServer(), HsfTraceStrategy.isEnableClient(),
            MetaqTraceStrategy.isEnable(), TddlTraceStrategy.isEnable(),
            HttpTraceStrategy.isEnable(), DptraceTraceStrategy.isEnable(),
            DtsTraceStrategy.isEnable(), MockStrategy.isEnable(), CURRENT_VERSION);
    }

尤其要注意settingMock(traceProperties)会关闭线上环境的HttpTraceStrategy, 所以线上环境是查不到Http记录的。

// com.alibaba.fin.infrastructure.trace.processor.TraceStrategyManager
private static void settingMock(TraceProperties traceProperties) {
        boolean mockEnable = false;
        // 日常,及预发环境默认开启无需关闭
        if (EnvUtil.isProd()) {
            // 线上环境默认关闭
            mockEnable = false;

            // 线上环境强制关闭HTTP,相对而言http的应用广度不是那么多,避免因为特设兼容问题,导致线上HTTP异常。安全性还未达到线上使用的程度
            HttpTraceStrategy.setEnable(false);

            // 线上环境只支持本地文件的方式
            PrintTypeStrategy.setPrintType(PrintType.LOG);
        } else if (Objects.nonNull(traceProperties.getMockEnable())) {
            mockEnable = traceProperties.getMockEnable();
        } else {
            mockEnable = EnvUtil.isDaily();
        }

        MockStrategy.setEnable(mockEnable);
    }

启动日志推送

// com.alibaba.adp.TraceConfiguration
PushController.start();

PushController会启动线程池,定时向ADP服务器推送PushController.logRecords中的日志:

//com.alibaba.fin.infrastructure.trace.push.PushController
public static void start() {
        logger.info("[INFO] ADP-TRACE-STARTER start PushController...");
        SEND_THREAD_POOL.scheduleAtFixedRate(new IntervalPushTask(), 0, PushStrategy.getSendPeriod(), TimeUnit.SECONDS);
        FUSE_THREAD_POOL.scheduleAtFixedRate(new ClearErrorTask(), 0, 1, TimeUnit.HOURS);
        SEND_THREAD_POOL.submit(new LoopPushTask());
    }
//com.alibaba.fin.infrastructure.trace.push.PushController 
public static synchronized void sendMessage() throws IOException {
        Set<LogRecord> readyPush = new HashSet<>(50);
        try {
            if (PushController.logRecords.size() > 0) {
                HttpMessage message = new HttpMessage();
                message.setHost(ADP_TRACE_HOST);
                message.setPort(ADP_TRACE_PORT);

                //写锁是为了在清理过程中阻止元素刚写完被误清掉,所以短暂禁止写入,这个并发点比争队尾或并发写的概率低很多
                Lock writeLock = lock.writeLock();
                writeLock.lock();
                try {
                    Set<LogRecord> values = PushController.logRecords.values();
                    readyPush.addAll(values);
                    LogRecord logRecord = values.stream().findFirst().get();
                    logger.info("send Message {} traceId:{} {}", readyPush.size(),
                        logRecord.getTraceId(), logRecord.getServiceType());
                    PushController.logRecords.clear();
                } finally {
                    writeLock.unlock();
                }
                message.setContent(JSON.toJSONString(readyPush));
                message.setUrlPath(ADP_TRACE_URL_PATH);

                HttpSendUtils.sendHttpRequest(message);
            }
        } catch (Throwable e) {
            if (ERROR_COUNT.addAndGet(1) < MAX_ERROR) {
                logger.error("ERROR ,SendMessage_ERROR {}", e.getMessage());
            }else{
                logger.warn("ERROR ,SendMessage_ERROR {}", e.getMessage());
            }
            if (readyPush.size() > 0) {
                readyPush.stream().parallel().forEach(LogRecord::logByMetaq);
            }

        }
    }

hsf mock客户端和心跳检测

Filter产生日志

PushController会定时向adp服务器发送日志(PushController.logRecords),这些日志又是从如何生成的呢?

  • 处理http请求和hsf请求的时候。
  • 通过filter来拦截请求生成log,保存到PushController.logRecords

http filter

ServletTraceFilter会拦截http请求:

//com.alibaba.fin.infrastructure.trace.processor.controller.ServletTraceFilter 

@Override
public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {

        if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) {
            chain.doFilter(req, res);
            return;
        }
        if (!HttpTraceStrategy.checkPrint()) {
            chain.doFilter(req, res);
            return;
        }

        // multipart/form-data 暂时不支持的类型,文件上传存在兼容性问题待修复
        if (StringUtils.contains(req.getContentType(), "multipart/form-data")) {
            chain.doFilter(req, res);
            return;
        }

        HttpServletResponse response = (HttpServletResponse)res;
        long startTime = System.currentTimeMillis();
        HttpServletResponseCopier responseWrapper = new HttpServletResponseCopier(response);
        LogRecord record = new LogRecord();
        HttpInputContext context = new HttpInputContext();
        initLogRecord((HttpServletRequest)req, response, record, context);

        if (TraceStrategyManager.decideExcludeService(record.getServiceId(), "")) {
            chain.doFilter(req, res);
            return;
        }

        try {
            chain.doFilter(req, responseWrapper);
            buildParamOutput(startTime, responseWrapper, record, context);
        } catch (Throwable e) {
            buildParamOutputExp(startTime, responseWrapper, record, context, e);
            throw e;
        } finally {
            if (!TraceStrategyManager.decideExcludeService(record.getServiceId(), "")) {
                record.log();
                EagleEyeHttpResponse eagleEyeHttpResponse = convertHttpResponse(response, record.getResponseCode());
                EagleEyeRequestTracer.endTrace(eagleEyeHttpResponse);
            }
        }

    }

检查http trace是否启动

一般线上环境http trace关闭,走到这里就结束了。

//com.alibaba.fin.infrastructure.trace.processor.controller.ServletTraceFilter 

if (!HttpTraceStrategy.checkPrint()) {
     chain.doFilter(req, res);
     return;
 }

初始化日志

//com.alibaba.fin.infrastructure.trace.processor.controller.ServletTraceFilter 

LogRecord record = new LogRecord();
HttpInputContext context = new HttpInputContext();
initLogRecord((HttpServletRequest)req, response, record, context);

获取traceid,并开启新的trace

//com.alibaba.fin.infrastructure.trace.processor.controller.ServletTraceFilter
private void buildBaseParam(HttpServletRequest request, LogRecord record, HttpInputContext httpInput) {

     // ...
     if (StringUtils.isBlank(record.getTraceId())) {
         // 尝试重新构建traceId信息
         EagleEyeHttpRequest eagleEyeHttpRequest = convertHttpRequest(request);
         String traceId = EagleEyeRequestTracer.getTraceId(eagleEyeHttpRequest,
         eagleEyeHttpRequest.getRemoteIp(), false);
         //开启追踪
         EagleEye.startTrace(traceId, EagleEye.ROOT_RPC_ID, eagleEyeHttpRequest.getUrl(), EagleEye.TYPE_TRACE);
                record.reSetTraceId();
     }

     //...
    }
//com.taobao.eagleeye.EagleEyeRequestTracer#getTraceId

/**
     * 获取 TraceId。根据以下步骤:
     * <ol>
     * <li>从 ThreadLocal 获取</li>
     * <li>从 URL 参数中 获取</li>
     * <li>从 header 中 获取</li>
     * <li>如果上述都没有,则自动生成,如果 ip 不为 <code>null</code>,则基于指定的 ip,
     *     否则,使用本机 ip
     * </li>
     * </ol>
     * @param httpRequest
     * @param ip
     * @return
     */
public static final String getTraceId(EagleEyeHttpRequest eagleEyeHttpRequest, String ip, boolean ignoreCurrentThreadLocal) {
        String traceId = getThreadLocalTraceId();
        if (!EagleEyeCoreUtils.isBlank(traceId) && !ignoreCurrentThreadLocal) {
            return traceId;
        }

        try {
            // 检查 url 中的调用链配置,这种配置是很罕见的,因此要尽量保证性能
            String queryString = eagleEyeHttpRequest.getQueryString();
            traceId = getParamFromUrl(queryString, EAGLEEYE_TRACEID_PARAM_KEY);
            if (EagleEyeCoreUtils.isValidTraceId(traceId)) {
                return traceId;
            }

            // 检查 header 中的调用链配置
            traceId = EagleEyeCoreUtils.trim(eagleEyeHttpRequest.getHeader(EAGLEEYE_TRACEID_HEADER_KEY));
            if (EagleEyeCoreUtils.isValidTraceId(traceId)) {
                return traceId;
            }
        } catch (Throwable t) {
            // quietly
        }

        return TraceIdGenerator.generate(ip);
    }

设置response header

private HttpInputContext initLogRecord(HttpServletRequest req, HttpServletResponse response, LogRecord record,
                                           HttpInputContext context) {
        try {
            buildBaseParam(req, record, context);
            response.addHeader(EagleEyeRequestTracer.EAGLEEYE_TRACEID_HEADER_KEY, EagleEye.getTraceId());
            response.addHeader(EagleEyeRequestTracer.EAGLEEYE_RPCID_HEADER_KEY, EagleEye.getRpcId());
            final RpcContext_inner rpcContext = EagleEye.getRpcContext();
            if(Objects.nonNull(rpcContext)) {
                response.addHeader(EagleEyeRequestTracer.EAGLEEYE_USERDATA_HEADER_KEY,
                    rpcContext.exportPrintableUserData());
            }
            response.addHeader("DP", String.format(DP_URL, EagleEye.getTraceId(), getCurrentEnv()));
            return context;
        } catch (Exception e) {
            if (errorLogIndex < 10000) {
                logger.warn("initLogRecord Exception", e);
            } else {
                errorLogIndex++;
                logger.warn("initLogRecord Exception");
            }

            return new HttpInputContext();
        }
    }

保存日志并关闭trace

日志保存:

  • 保存一份在本地:logger.debug
  • 加入PushController的record,等待定时推送。
 if (!TraceStrategyManager.decideExcludeService(record.getServiceId(), "")) {
    record.log();
    EagleEyeHttpResponse eagleEyeHttpResponse = convertHttpResponse(response, record.getResponseCode());
    EagleEyeRequestTracer.endTrace(eagleEyeHttpResponse);
}
// com.alibaba.fin.infrastructure.trace.LogRecord
 public void log() {
        try {
            this.time = String.valueOf(System.currentTimeMillis());
            logger.debug("print trace log {} ", this);
            prune();
            // 时间再打印的时候再设置
            PrintFactory.getPrinter().print(this);
        } catch (Exception e) {
            logger.debug("log exception {}  {}", toString(), e.getMessage());
        }
}


// com.alibaba.fin.infrastructure.trace.print.HttpPrintStrategy
public class HttpPrintStrategy implements PrintStrategy {
    @Override
    public boolean print(LogRecord logRecord) {
        return PushController.putRecord(logRecord);
    }
}

rpc filter

采用SPI的方式提供RPCFilter实现类:
RPCFilter.png

HsfServerTraceFilterHsfClientTraceFilter都继承AbstractHsfTraceFilter抽象类(模板)

//com.alibaba.fin.infrastructure.trace.processor.hsf.AbstractHsfTraceFilter
    public ListenableFuture<RPCResult> invoke(InvocationHandler nextHandler, Invocation invocation) throws Throwable {
        try {
            return nextHandler.invoke(invocation);
        } catch (Exception exception) {
            printLog(invocation, exception, true);
            throw exception;
        }
    }

    public void onResponse(Invocation invocation, RPCResult rpcResult) {
        Object responseObject = null;
        if (rpcResult != null) {
            responseObject = rpcResult.getAppResponse();
        }
        printLog(invocation, responseObject, false);
    }

两者的区别在于HsfTraceStrategy策略的参数不同:

//com.alibaba.fin.infrastructure.trace.processor.hsf.HsfClientTraceFilter   
@Override
protected boolean isPrintTrace(String serviceName, String methodName) {
        String logStrategy = HsfTraceStrategy.get(serviceName, methodName);
        if (HsfTraceStrategy.isEnableClient() && !HsfTraceStrategy.EXCLUDE.equals(logStrategy)) {
            return true;
        }
        return false;
    }

//com.alibaba.fin.infrastructure.trace.processor.hsf.HsfServerTraceFilter
@Override
protected boolean isPrintTrace(String serviceName, String methodName) {
        String logStrategy = HsfTraceStrategy.get(serviceName, methodName);
        if (HsfTraceStrategy.isEnableServer() && !HsfTraceStrategy.EXCLUDE.equals(logStrategy)) {
            return true;
        }
        return false;
    }