背景
在上面介绍了CAT如何做跨进程追踪的。公司项目采用业务垂直切分的方式开发、前后端也是分离方式。那么实时监控埋点的地方有如下几处:
- 前端和后端交互
- 后端业务逻辑
- 数据库存储
- 远程PRC调用
- 消息队列
RPC框架
公司选用的RPC框架是apache dubbo ,版本是2.7.5 。dubbo是一款非常优秀的RPC框架。对于框架我就不做过多的介绍了。cat 集成 dubbo 首要考虑的就是让业务系统无感。那么优秀的框架一定有扩展点。在dubbo中可以使用Filter 来进行扩展。
CAT集成dubbo
dubbo集成的时候需要区分是生产者还是消费者,生产者和消费者监控的数据大致上都是一样的,有少量数据不一样。并且需要处理异常的情况。由于Filter需要对调用前和调用后进行监控。并且流程上固定的。所以这里我采用了模板的方式。代码的类图如下:

使用AbstractCatTransaction 中进行流程的定义,并且将公共代码都放到抽象类中。定义抽象想法让子类去实现。代码详情如下:
AbstractCatTransaction 代码:
/**
* CAT-DUBBO 监控抽象类。采用模板设计把固定的流程固化。
* @author tengx
*/
public abstract class AbstractCatTransaction implements Filter {
/**
* 拦截rpc 请求,设计3个扩展口子。
* 1.创建 CAT 事务
* 2.在rpc调用前执行扩展方法
* 3.在rpc调用后执行扩展方法
* 4.记录rpc花费的时间
* 5.对于异常的处理
*
* @param invoker 调用者
* @param invocation 调用
* @return 返回结果
* @throws RpcException RPC异常
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result invokeResult=null;
Transaction t=createTransaction(invoker,invocation);
try{
beforeInvoke(invoker,invocation,t);
long startTime=System.currentTimeMillis();
//rpc 返回的结果
invokeResult=invoker.invoke(invocation);
long endTime=System.currentTimeMillis();
afterInvoke(invoker,invocation,t);
//记录rpc花费的时间
long spendTime=endTime-startTime;
t.setDurationInMillis(spendTime);
//记录rpc花费的时间
Cat.logEvent(CatConstants.DUBBO_SPEND_TIME,spendTime+"ms");
boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);
//异步的不能判断是否有异常,这样会阻塞住接口(<AsyncRpcResult>hasException->getRpcResult->resultFuture.get()
if (isAsync) {
t.setStatus(Transaction.SUCCESS);
return invokeResult;
}
//没有异常直接返回结果
if (!invokeResult.hasException()){
t.setStatus(Transaction.SUCCESS);
return invokeResult;
}
handlerRpcException(invokeResult,t);
}catch (Exception e){
t.setStatus(e);
Cat.logError(e);
}finally {
t.complete();
}
return invokeResult;
}
/**
* 创建CAT监控事务模型
* @param invoker 调用人
* @param invocation 调用
* @return 事务模型
*/
protected abstract Transaction createTransaction(Invoker<?> invoker, Invocation invocation);
/**
* 在rpc方法执行前进行监控操作
* @param invoker 调用人
* @param invocation 调用
* @param t CAT事务模型
*/
protected abstract void beforeInvoke(Invoker<?> invoker, Invocation invocation,Transaction t);
/**
* 在rpc方法执行后进行监控操作
* @param invoker 调用人
* @param invocation 调用
* @param t CAT事务模型
*/
protected abstract void afterInvoke(Invoker<?> invoker, Invocation invocation,Transaction t);
/**
* 对于RPC异常情况的处理
* @param result rpc返回的结果
* @param t cat的事务
*/
protected void handlerRpcException(Result result, Transaction t){
if (!result.hasException()){
return;
}
Throwable throwable=result.getException();
if(RpcException.class==throwable.getClass()){
Throwable caseBy = throwable.getCause();
if(caseBy != null&&caseBy.getClass()== TimeoutException.class){
Cat.logError(CatConstants.DUBBO_TIMEOUT_ERROR,throwable);
}else{
Cat.logError(CatConstants.DUBBO_REMOTING_ERROR,throwable);
}
}else if(RemotingException.class.isAssignableFrom(throwable.getClass())){
Cat.logError(CatConstants.DUBBO_REMOTING_ERROR,throwable);
}else{
Cat.logError(CatConstants.SERVER_BIZ_ERROR,throwable);
}
t.setStatus(throwable);
}
}
CatConsumerTransaction 代码
/**
* CAT-DUBBO 消费端监控
* @author tengx
*/
@Activate(group = {CommonConstants.CONSUMER})
@Slf4j
public class CatConsumerTransaction extends AbstractCatTransaction {
/**
* 通过url获取应用名称,然后创建事务模型
* @param invoker 调用人
* @param invocation 调用
* @return 事务模型
*/
@Override
protected Transaction createTransaction(Invoker<?> invoker, Invocation invocation) {
URL url=invoker.getUrl();
String applicationName=url.getParameter(CommonConstants.APPLICATION_KEY);
//创建Transaction
return Cat.newTransaction(CatConstants.DUBBO_CONSUMER_NAME,applicationName);
}
/**
* 拦截rpc消费请求,使用CAT 记录以下信息:
* 1.消费端的名称
* 2.记录消费端的IP
* 3.记录消费端调用的方法和参数
* 4.记录消费端调用的持续时间
* @param invoker 调用者
* @param invocation 调用
* @param t 事务模型
*/
@Override
protected void beforeInvoke(Invoker<?> invoker, Invocation invocation,Transaction t) {
URL url=invoker.getUrl();
String applicationName=url.getParameter(CommonConstants.APPLICATION_KEY);
Context ctx = new CatContext();
Cat.logRemoteCallClient(ctx,applicationName);
//消费端传递RPC参数
consumerSetAttachment(url, applicationName, ctx);
Cat.logEvent(CatConstants.DUBBO_CONSUMER_IP,url.getIp());
Cat.logEvent(CatConstants.DUBBO_CONSUMER_METHOD,invocation.getMethodName());
Cat.logEvent(CatConstants.DUBBO_CONSUMER_METHOD_Arguments, JsonUtils.toJSONString(invocation.getArguments()));
Cat.logEvent(CatConstants.DUBBO_CONSUMER_DATETIME, DateUtils.format(new Date(),DateUtils.DATE_HH_MM_SS));
}
/**
* 空方法,rpc调用后没有逻辑
* @param invoker 调用人
* @param invocation 调用
* @param t CAT事务模型
*/
@Override
protected void afterInvoke(Invoker<?> invoker, Invocation invocation,Transaction t) {
}
/**
* 向RPC Context中设置CAT监控跨进程需要的参数
* @param url dubbo的url
* @param applicationName 应用名称
* @param ctx CAT上下文
*/
private void consumerSetAttachment(URL url, String applicationName, Context ctx) {
//向rpc context 中放入追踪的参数
RpcContext.getContext().setAttachment(Context.ROOT, ctx.getProperty(Context.ROOT));
RpcContext.getContext().setAttachment(Context.CHILD, ctx.getProperty(Context.CHILD));
RpcContext.getContext().setAttachment(Context.PARENT, ctx.getProperty(Context.PARENT));
RpcContext.getContext().setAttachment(CatConstants.DUBBO_CONSUMER_NAME, applicationName);
RpcContext.getContext().setAttachment(CatConstants.DUBBO_CONSUMER_IP, url.getIp());
}
}
这里需要注意的是,消费者会把CAT做追踪的参数、应用名称、消费端IP放到 RpcContext中。这样生产者就能获取到这样参数。在监控中做对应。在生产环境中,系统会有多个节点。所以这里把消费者的IP传入的RpcContext 然后生产者那边接受这个参数并记录。目的是对应上服务器。
CatProviderTransaction的代码
/**
* CAT-DUBBO 生产者记录
* @author tengx
*/
@Activate(group = {CommonConstants.PROVIDER})
@Slf4j
public class CatProviderTransaction extends AbstractCatTransaction {
@Override
protected Transaction createTransaction(Invoker<?> invoker, Invocation invocation) {
URL url=invoker.getUrl();
String applicationName=url.getParameter(CommonConstants.APPLICATION_KEY);
//创建Transaction
return Cat.newTransaction(CatConstants.DUBBO_PROVIDER_NAME,applicationName);
}
@Override
protected void beforeInvoke(Invoker<?> invoker, Invocation invocation, Transaction t) {
URL url=invoker.getUrl();
Context ctx = new CatContext();
ctx.addProperty(Context.ROOT,RpcContext.getContext().getAttachment(Context.ROOT));
ctx.addProperty(Context.CHILD,RpcContext.getContext().getAttachment(Context.CHILD));
ctx.addProperty(Context.PARENT,RpcContext.getContext().getAttachment(Context.PARENT));
Cat.logRemoteCallServer(ctx);
String str=RpcContext.getContext().getAttachment(CatConstants.DUBBO_CONSUMER_NAME)+
":"+RpcContext.getContext().getAttachment(CatConstants.DUBBO_CONSUMER_IP);
Cat.logEvent(CatConstants.DUBBO_CONSUMER_NAME,str);
Cat.logEvent(CatConstants.DUBBO_PROVIDER_IP,url.getIp());
Cat.logEvent(CatConstants.DUBBO_PROVIDER_METHOD,invocation.getMethodName());
Cat.logEvent(CatConstants.DUBBO_PROVIDER_METHOD_Arguments, JsonUtils.toJSONString(invocation.getArguments()));
Cat.logEvent(CatConstants.DUBBO_PROVIDER_DATETIME, DateUtils.format(new Date(),DateUtils.DATE_HH_MM_SS));
}
@Override
protected void afterInvoke(Invoker<?> invoker, Invocation invocation, Transaction t) {
}
}
最后在使用dubbo Filter的时候需要在resources 下MEAT-INF/dubbo/创建Filter文件。Filter文件名称是:org.apache.dubbo.rpc.Filter
总结:
- 优秀的框架都会留有扩展的接口。所以在做集成的时候先看看框架扩展接口,然后再思考是否自己写扩展方式。
- 对于公用的代码可以使用模板的方式进行抽取。
- dubbo框架可以使用filter接口对调用进行扩展。
2360

被折叠的 条评论
为什么被折叠?



