dubbo应用笔记(2)基于Filter、RpcContext隐藏参数及SLF的MDC的服务调用链日志跟踪

使用dubbo做分布式服务,当查看日志时,需要在多个应用中对日志进行查询;若一个接口被多个客户端同时调用,则会出现日志查找辨别非常困难,无法及时定位错误。

本示例基于MVC拦截器、Dubbo的Filter及SLF的MDC实现,原理为在客户端调用http接口时,利用MVC拦截器,在MDC中放置一个reqId,并在logback日志中对此reqId进行输出;当此次请求进行RPC请求时,Filter会获取MDC中的reqId,并将此reqId以隐藏参数的形式传递给服务提供者;服务提供者的Filter会再将隐藏参数中的reqId放到服务端调用的MDC中,从而实现整个调用流程使用同一个reqId;

1、服务端实现

1.1、源码目录

dubbo应用笔记(2)基于Filter、RpcContext隐藏参数及SLF的MDC的服务调用链日志跟踪_第1张图片
服务端源码目录.png

目录说明:
dubbo.trace.server.api包:此包为接口及接口实现;
dubbo.trace.server.config包:此包配置加载dubbo的配置;
dubbo.trace.server.filter包:服务端Filter实现;
resource/config目录:logback日志配置及dubbo配置;
resource/config/META-INF/dubbo目录:dubbo的SPI扩展目录,本处扩展了com.alibaba.dubbo.rpc.Filter接口;

1.2、接口及实现

接口:

public interface DubboTraceApi {
    public String echoTest(String msg);
}

实现:

public class DubboTraceApiImpl implements DubboTraceApi {

    private static Logger logger = LoggerFactory.getLogger(DubboTraceApiImpl.class);

    @Override
    public String echoTest(String msg){
        logger.info("echoTest:{}", msg);
        return msg;
    }
}

1.3、Filter实现

public class ServerTraceFilter implements Filter {

    private static Logger logger = LoggerFactory.getLogger(ServerTraceFilter.class);

    @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
        logger.info("####className:{}", invocation.getClass().getName());
        logger.info("####methodName:{}", invocation.getMethodName());

        //获取appCode及secretKey
        String appCode = RpcContext.getContext().getAttachment("appCode");
        String secretKey = RpcContext.getContext().getAttachment("secretKey");

        if(Strings.isNullOrEmpty(appCode) || Strings.isNullOrEmpty(secretKey)){
            throw new RpcException("Sorry, your access is denied!");
        }

        logger.info("appCode:{}", appCode);

        //获取reqId,若没有,则通过UUID生成一个;然后将reqId放到MDC中,便于日志中打印
        String reqId = RpcContext.getContext().getAttachment("reqId");
        reqId = !Strings.isNullOrEmpty(reqId) ? reqId : UUID.randomUUID().toString();
        MDC.put("appCode", appCode);
        MDC.put("reqId", reqId);

        logger.info("reqId:{}", reqId);
        if(!appCode.equals("zhaozhou11") || !secretKey.equals("666666")){
            throw new RpcException("your appCode or secretKey is error!");
        }

        return invoker.invoke(invocation);
    }
}

本处主要获取appCode、secretKey及reqId,对appCode及secretKey进行验证,将appCode及reqId放到MDC中;

1.4、dubbo服务端配置

dubbo-rpc-provider.xml:




    
    

    
    

    
    


    

    
    

    
    

主要配置注册中心、暴露协议、服务提供者等,重点是设置provider的Filter为traceFilter,此处的traceFilter是在META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件中进行设置的;

设置如下:

traceFilter=dubbo.trace.server.filter.ServerTraceFilter

1.5、logback日志配置

logback日志配置就不全贴出来了,只贴出日志输出格式的设置,如下:


格式中变量appCode及reqId即为在Filter中设置的MDC值。

2、客户端实现

2.1、源码目录

dubbo应用笔记(2)基于Filter、RpcContext隐藏参数及SLF的MDC的服务调用链日志跟踪_第2张图片
客户端源码目录.png

目录说明:
dubbo.trace.client.config包:客户端的bean配置加载,包括dubbo及接口拦截器;
dubbo.trace.client.controller包:controller类;
dubbo.trace.client.filter包:客户端的Filter实现;
resource/config目录:dubbo客户端配置及logback配置;
resource/META-INF/dubbo目录:dubbo的SPI扩展配置;
resource/templates目录:页面资源目录

2.2、拦截器实现

public class TraceClientInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        MDC.put("userName", "zhaozhou11");
        MDC.put("reqId", UUID.randomUUID().toString());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        MDC.remove("userName");
        MDC.remove("reqId");
    }
}

拦截器主要是在接口调用之前设置MDC值,此处设置userName及reqId,并在调用完成后清除;

2.2、Filter实现

public class ConsumerRpcFilter implements Filter {

    private static Logger logger = LoggerFactory.getLogger(ConsumerRpcFilter.class);

    @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
        logger.info("###className:{}", invocation.getClass().getName());
        logger.info("###methodName:{}", invocation.getMethodName());
        String appCode = invoker.getUrl().getParameter("appCode");
        String secretKey = invoker.getUrl().getParameter("secretKey");

        String reqId = MDC.get("reqId");
        reqId = Strings.isNotEmpty(reqId) ? reqId : UUID.randomUUID().toString();

        RpcContext.getContext().setAttachment("appCode",appCode);
        RpcContext.getContext().setAttachment("secretKey", secretKey);
        RpcContext.getContext().setAttachment("reqId", reqId);
        return invoker.invoke(invocation);
    }
}

本处主要是获取appCode、secretKey及reqId,并将这三个参数放到RpcContext的Attachment中,而服务端就可以从其对应的Attachment中获取这些参数。

2.3、Controller实现

@Controller
@RequestMapping(value = "")
public class MainController {

    @Autowired
    private DubboTraceApi traceApi;

    @RequestMapping(value = "/")
    public String gotoIndexPage(Model model){
        String ret = traceApi.echoTest("this is test!");
        model.addAttribute("ret", ret);
        return "index";
    }
}

3、测试输出

确保zookeeper是启动的,并启动服务端和客户端,浏览器访问:http://localhost:8080/

客户端日志:

客户端日志.png

服务端日志:

服务端日志.png

服务端和客户端日志中都有相同的reqId。

示例源码:https://github.com/zhaozhou11/dubbo-demo.git

你可能感兴趣的:(dubbo应用笔记(2)基于Filter、RpcContext隐藏参数及SLF的MDC的服务调用链日志跟踪)