使用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.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.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/
客户端日志:
服务端日志:
服务端和客户端日志中都有相同的reqId。
示例源码:https://github.com/zhaozhou11/dubbo-demo.git