如何实现业务链路监控
trace的一个demo版本,可以查看接口调用的整个链路,各种常规指标的统计,比如接口耗时,接口异常等。在此基础上手机日志后,可以做一些简单的logview展示。在实际工程中,根据业务需要,记录相关监控指标数据,可以在此基础上进行扩展,本demo展示的是trace链路原理上的处理方案。
1.注解类:对所有需要进行拦截的类添加该注解后,就会被扫描到做切面操作。
@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface TraceAnno { String desc() default ""; }
2.trace拦截类:对于注解类切面后的操作,记录程序入口和接口整个链路
@Aspect public class TraceApsect { private final static Logger logger = LoggerFactory.getLogger(TraceApsect.class); @Pointcut("@annotation(com.demo.it.trace.TraceAnno) " + "|| @within(com.demo.it.trace.TraceAnno)") public void pointCut() { } @Around("pointCut()") public Object invoke(final ProceedingJoinPoint joinPoint) throws Throwable { if (!TraceSwitch.getInstance().isOpenProfilerTree()) { return joinPoint.proceed(); } String methodName = this.getClassAndMethodName(joinPoint); if (null == methodName) { return joinPoint.proceed(); } try { if (TraceProfiler.getEntry() == null) { TraceProfiler.start(methodName); } else { TraceProfiler.enter(methodName); } return joinPoint.proceed(); } catch (Throwable e) { // 异常通知 logger.error("The method " + methodName + " occurs expection : " + e); TraceProfiler.error(e); return e.getMessage(); } finally { TraceProfiler.release(); // 当root entry为状态为release的时候,打印信息,并做reset操作 TraceProfiler.Entry rootEntry = TraceProfiler.getEntry(); if (rootEntry != null) { if (rootEntry.isReleased()) { long duration = rootEntry.getDuration(); if (duration > TraceSwitch.getInstance().getInvokeTimeout()) { logger.warn(TraceProfiler.dump()); } else { logger.info(TraceProfiler.dump()); } TraceProfiler.reset(); } } } } private String getClassAndMethodName(ProceedingJoinPoint joinPoint) { try { MethodSignature sign = (MethodSignature) joinPoint.getSignature(); String clazzName = joinPoint.getTarget().toString(); StringBuilder sb = new StringBuilder(); sb.append(TraceProfiler.split(clazzName, "@")[0]); sb.append(":").append(sign.getMethod().getName()); sb.append("(param:").append(sign.getMethod().getParameterTypes().length); sb.append(")"); return sb.toString(); } catch (Exception e) { e.printStackTrace(); return null; } }
3.trace 需要统计的内容:错误日志,耗时,ip, 接口调用连等
public final class TraceProfiler { /** 构建实体的存储缓存体 */ private static final ThreadLocal<Entry> entryStack = new ThreadLocal<Entry>(); /** 开始计时 */ public static void start() { start((String) null); } /** 开始计时,创建一个Entry的实体对象 */ public static void start(String message) { entryStack.set(new Entry(message, null, null)); } /** threadLocal缓存清理,由于现在大多是线程池的设置,所以要做一个清理 */ public static void reset() { entryStack.set(null); } /** 由于Entry自身是树状结构,所以如果是进入非Root的节点,那就需要enter来搞 */ public static void enter(String message) { Entry currentEntry = getCurrentEntry(); if (currentEntry != null) { currentEntry.enterSubEntry(message); } } /** 保存异常信息 */ public static void error(Throwable e){ Entry currentEntry = getCurrentEntry(); if (currentEntry != null) { currentEntry.setErr(e); } } /** 方法运行结束之后,把当前的Entry的endTime来设置成当前时间 */ public static void release() { Entry currentEntry = getCurrentEntry(); if (currentEntry != null) { currentEntry.release(); } } /** 获取start和end的时间差 */ public static long getDuration() { Entry entry = (Entry) entryStack.get(); if (entry != null) { return entry.getDuration(); } else { return -1; } } /** 把Entry的信息dump出来,可以打印到日志中去 */ public static String dump() { Entry entry = (Entry) entryStack.get(); if (entry != null) { String str=JSONObject.toJSONString(entry); return str; } return ""; } /** 获取Entry信息 */ public static Entry getEntry() { return (Entry) entryStack.get(); } /** entry中含有subentry,如此这样来进行循环来保持树状的结构 */ private static Entry getCurrentEntry() { Entry subEntry = (Entry) entryStack.get(); Entry entry = null; if (subEntry != null) { do { entry = subEntry; subEntry = entry.getUnreleasedEntry(); } while (subEntry != null); } return entry; } public static final class CONSTANT { public final static String DATE_FORMAT="yyyy-MM-dd hh:mm:ss SSS"; } /** * 代表一个计时单元。 */ public static final class Entry { // subEntries来表示树状的子节点 private final List<Entry> subEntries = new ArrayList<Entry>(); private final Object message; private final Entry firstEntry; private final long startTime; private long endTime; private Throwable err; private Entry(Object message/* 描述信息 */, Entry parentEntry/* 父节点信息 */, Entry firstEntry/* 第一个节点 */) { this.message = message; this.startTime = TraceSwitch.getInstance().isOpenProfilerNanoTime() == true ? System .nanoTime() : System.currentTimeMillis(); this.firstEntry = (Entry) defaultIfNull(firstEntry, this); } /** * 取得entry的信息。 */ public String getMessage() { return defaultIfEmpty((String) message, null); } public static String defaultIfEmpty(String str, String defaultStr) { return ((str == null) || (str.length() == 0)) ? defaultStr : str; } public static Object defaultIfNull(Object object, Object defaultValue) { return (object != null) ? object : defaultValue; } /** 获取当前节点的开始时间 */ public String getStartTime() { SimpleDateFormat fmt=new SimpleDateFormat(CONSTANT.DATE_FORMAT); return fmt.format(startTime) ; } /** 获取当前节点的结束时间 */ public String getEndTime() { SimpleDateFormat fmt=new SimpleDateFormat(CONSTANT.DATE_FORMAT); return fmt.format(endTime) ; } /** 获取持续时间 */ public long getDuration() { if (endTime < startTime) { return -1; } else { return endTime - startTime; } } /** 取得entry自身所用的时间,即总时间减去所有子entry所用的时间。 */ public long getDurationOfSelf() { long duration = getDuration(); if (duration < 0) { return -1; } else if (subEntries.isEmpty()) { return duration; } else { for (int i = 0; i < subEntries.size(); i++) { Entry subEntry = (Entry) subEntries.get(i); duration -= subEntry.getDuration(); } if (duration < 0) { return -1; } else { return duration; } } } /** 取得所有子entries。 */ public List<Entry> getSubEntries() { return Collections.unmodifiableList(subEntries); } /** 结束当前entry,并记录结束时间。 */ private void release() { endTime = TraceSwitch.getInstance().isOpenProfilerNanoTime() == true ? System .nanoTime() : System.currentTimeMillis(); } /** 判断当前entry是否结束。 */ public boolean isReleased() { return endTime > 0; } /** 创建一个新的子entry */ private void enterSubEntry(Object message) { Entry subEntry = new Entry(message, this, firstEntry); subEntries.add(subEntry); } /** 添加异常信息 */ public String getError() { return err==null?"":err.getMessage(); } /** 设置异常信息 */ public void setErr(Throwable e){ this.err=e; } /** 取得未结束的子entry,链表中的最后一个元素 */ private Entry getUnreleasedEntry() { Entry subEntry = null; if (!subEntries.isEmpty()) { subEntry = (Entry) subEntries.get(subEntries.size() - 1); if (subEntry.isReleased()) { subEntry = null; } } return subEntry; } } public static String[] split(String str, String separatorChars) { return split(str, separatorChars, -1); } private static String[] split(String str, String separatorChars, int max) { if (str == null) { return null; } int length = str.length(); if (length == 0) { return new String[0]; } List<String> list = new LinkedList<String>(); int sizePlus1 = 1; int i = 0; int start = 0; boolean match = false; if (separatorChars == null) { // null表示使用空白作为分隔符 while (i < length) { if (Character.isWhitespace(str.charAt(i))) { if (match) { if (sizePlus1++ == max) { i = length; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } match = true; i++; } } else if (separatorChars.length() == 1) { char sep = separatorChars.charAt(0); while (i < length) { if (str.charAt(i) == sep) { if (match) { if (sizePlus1++ == max) { i = length; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } match = true; i++; } } else { // 一般情形 while (i < length) { if (separatorChars.indexOf(str.charAt(i)) >= 0) { if (match) { if (sizePlus1++ == max) { i = length; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } match = true; i++; } } if (match) { list.add(str.substring(start, i)); } return list.toArray(new String[list.size()]); } }
4.开关,各种指标监控
public class TraceSwitch { private static TraceSwitch instance = new TraceSwitch();public static TraceSwitch getInstance(){ return instance; } /** * 是否打开打印日志的开关 */ private boolean openProfilerTree =true;/** * 超时时间 */ private long invokeTimeout =500;/** * 是否打印纳秒 * @return */ private boolean openProfilerNanoTime = false;public boolean isOpenProfilerTree() { return openProfilerTree; } public void setOpenProfilerTree(boolean openProfilerTree) { this.openProfilerTree = openProfilerTree; } public long getInvokeTimeout() { return invokeTimeout; } public void setInvokeTimeout(long invokeTimeout) { this.invokeTimeout = invokeTimeout; } public boolean isOpenProfilerNanoTime() { return openProfilerNanoTime; } public void setOpenProfilerNanoTime(boolean openProfilerNanoTime) { this.openProfilerNanoTime = openProfilerNanoTime; } }
do good things。