上一篇:公共日志服务系统设计
简介:本篇文章将详细介绍公共日志服务系统中的基础信息获取
背景:众所周知,我们现在系统服务类型纷繁复杂,但从大的方面来说,主要分为一下几类。第一,http 服务,主要提供给前端或者其他的公司的同学调用;第二,RPC服务,类似于dubbo ,Spring cloud 等提供的服务。因为笔者的公司主要是使用的dubbo 所以这次文中所提及的RPC服务都指dubbo 服务;第三,就是一些定时的任务系统了;那么针对于这几种服务类型,我们应该用什么样的方式来收集基础的用户和应用信息呢?下面将分别介绍
基础信息获取
1、http服务类型,这种类型的服务提供给别人调用时,比如我们提供给前端的数据调用接口。对于这种接口笔者的公司会由网关加入SSO验证,所以通过网关能够拿到用户的信息。所以就有必要在具体的数据操作之前,通过拦截器获取到用户的基础信息。见代码:
// 实现Spring 拦截器 HandlerInterceptor 将请求拦截下来进行处理
public class UserInfoCollectInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(UserInfoCollectInterceptor.class);
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
StringBuffer url = request.getRequestURL();
getAuthUser(url.toString());
}catch (Exception e){
}
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
下面再看看getAuthUser 方法
/**
* 收集应用信息
* @param
*/
protected void getAuthUser(String url) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
ThreadLocal<Map<String, String>> threadLocalMap = ThreadLocalCarrier.threadLocalMap;
Map<String, String> stringMap = new HashMap<>(16);
//缓存内容传递至下一节点
stringMap.put(CommonLogConstant.VALUE_ENV, MySpringContextHelper.getProperty(CommonLogConstant.VALUE_ENV));
stringMap.put(CommonLogConstant.VALUE_APPID, MySpringContextHelper.getProperty(CommonLogConstant.VALUE_COMMONLOG_APPID));
stringMap.put(CommonLogConstant.VALUE_APPNAME, MySpringContextHelper.getProperty(CommonLogConstant.VALUE_COMMONLOG_APPNAME));
stringMap.put(CommonLogConstant.VALUE_DESCRIBE, MySpringContextHelper.getProperty(CommonLogConstant.VALUE_COMMONLOG_DESCRIBE));
stringMap.put(CommonLogConstant.VALUE_SERVICE_URL, url);
}
}catch (Exception e){
}finally {
threadLocalMap.set(stringMap);
}
}
只能贴出部分源码,大概讲一下,定义了一个ThreadLocal,再定义一个Map 然后将能够收集到的基础信息都放到ThreadLocal 里面,然后请求继续往下走,如果这个请求设计到数据更新的操作,那么这个请求会进入到mybatis 拦截器。这个是http 请求获取用户基础信息的方式。
dubbo 请求收集基础信息的方式
刚开始的时候我们在 Consumer端通过 RpcContext 注入服务调用方的一些基础信息,类似于这样:
RpcContext.getContext().setAttachment("userKey", "userValue");
不过RpcContext 存在一个问题,RpcContext是一个ThreadLocal的临时状态记录器,当接收到RPC请求,或发起RPC请求,RpcContext的状态都会发生变化。比如,A ---->B, B 能够拿到A的RpcContext里面的信息,B---->C C能够拿到B的RpcContext 的信息。不过 有个问题,加入 A先调B,完了以后,A再调C,那么C就取不到A 调用B之前注入的RpcContext 里面的信息了,因为调用完B以后,这个RpcContext 的信息就被清掉了。由于我们的后端很多的dubbo服务都是存在多重调用的,所以就会造成早在我们真正需要RpcContext 的服务提供方拿不到这个信息。查了些资料,找到了如下的方式解决,通过dubbo 的拦截器来注入所需要的服务信息。
代码如下:
public abstract class DubboProfilerFilterTemplate implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
consumerInvoke(ThreadLocalCarrier.getThreadLocalMap());
RpcContext rpcContext = RpcContext.getContext();
providerInvoke(rpcContext);
return invoker.invoke(invocation);
}
/**
* @title 消费调用端拦截器
* @description
* @param threadLocalMap
* @return
* @throws
*/
abstract void consumerInvoke(Map<String, String> threadLocalMap);
/**
* @title 生产服务端拦截器
* @description
* @param
* @return
* @throws
*/
abstract void providerInvoke(RpcContext rpcContext);
}
这样就能在服务消费端注入应用的基本信息,在服务的提供方拿到信息,为后序的进一步处理做好铺垫。
后序设计
拿到完整的应用信息以后,进一步做拦截处理。解析sql ---> 信息重组----> 发送mq---->服务消费写入ES----->提供个性化查询接口----> 前端展示。
本文旨在提供一个日志服务系统的设计思路,贴出的源码仅供参考,大部分还是需要自己设计的。