设计方案-API接口访问信息设计

1. 背景

有时候为了对系统进行优化,我们需要找出系统中访问时间长的那些方法,本文就来演示下,如何实现这个功能。最终实现的效果是访问一个url,列出当前系统中所有api接口的访问信息,包括:接口的调用次数、正常调用次数、异常调用次数、接口的平均访问时间、最大访问时间、最小访问。

2. 思路

  1. 定义一个拦截器,记录方法的开始时间和结束时间
  2. 定义一个全局的异常处理器,记录防范访问发生异常
  3. 定义ThreadLocal,保存方法的访问信息。
  4. 为了访问多线程并发访问影响记录的准确性,用队列把计算串行化。
  5. 定义结果输出界面

3. 设计

3.1 设计拦截器

@Service
public class AccessInterceptor implements HandlerInterceptor  {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod){
            HandlerMethod hm = (HandlerMethod)handler;
            AccessInfo mi = new AccessInfo();
            mi.setHm(hm);
            mi.setStartAt(System.currentTimeMillis());
            mi.setUri(request.getRequestURI());
            AccessHolder.accessStart(mi);//记录方法开始
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView model)
            throws Exception {
        //可以向view中写入数据
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
            throws Exception {
        if(handler instanceof HandlerMethod){
            AccessHolder.accessEnd();//记录方法结束
        }
    }
}

3.2 设计全局异常Handler

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
    @ExceptionHandler(value=Exception.class)  
    public Result<String> allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception{  
        AccessHolder.accessError();//记录方法异常
        if(exception instanceof BizException){
            exception.printStackTrace();
            BizException biz = (BizException)exception;
            return Result.error(biz.getCodeMsg());
        }else {
            exception.printStackTrace();
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }  
}

3.3 设计ThreadLocal对象

@Service
public class AccessHolder {
    
    private static ThreadLocal<AccessInfo> accessHolder = new ThreadLocal<AccessInfo>();//记录单次访问信息

    private static ConcurrentHashMap<Class<?>, ControllerAccessInfo> map = new ConcurrentHashMap<Class<?>, ControllerAccessInfo>();//记录所有的访问信息

    private static WorkingService<AccessRequest> workingService = new WorkingService<AccessRequest>();//工作队列,串行化
    
    static {
        workingService.start();
    }
    
    public static void accessStart(AccessInfo mai) {
        accessHolder.set(mai);
    }
    
    public static AccessInfo getAccessInfo() {
        return accessHolder.get();
    }
    
    public static void accessError() {
        AccessInfo ai = getAccessInfo() ;
        if(ai == null) {
            return;
        }
        ai.setOccurError(true);
    }
    
    public static void accessEnd() {
        AccessInfo ai = getAccessInfo();
        if(ai == null) {
            return;
        }
        ai.setEndAt(System.currentTimeMillis());
        accessHolder.set(null);
        workingService.execute(new AccessRequest(ai), new LazyExecutable<AccessRequest>() {
            @Override
            public void lazyExecute(AccessRequest request) {
                addAccessInfo(request.getAi());
            }
        });
    }
    
    private static void addAccessInfo(AccessInfo ai) {
        HandlerMethod hm = ai.getHm();
        Class<?> controllerClazz = hm.getBeanType();
        if(!AccessAble.class.isAssignableFrom(controllerClazz)) {
            return;
        }
        Method method = hm.getMethod();
        long startAt = ai.getStartAt();
        long endAt = ai.getEndAt();
        boolean occurError = ai.isOccurError();
        long useTime = endAt - startAt;
        String uri = ai.getUri();
        ControllerAccessInfo cai = map.get(controllerClazz);
        if(cai == null) {
            cai = new ControllerAccessInfo();
            cai.setControllerClazz(controllerClazz);
            map.put(controllerClazz, cai);
        }
        List<MethodAccessInfo> mais = cai.getMethodInfos();
        if(mais == null) {
            mais = new ArrayList<MethodAccessInfo>();
            cai.setMethodInfos(mais);
        }
        MethodAccessInfo mai = getMethodAccessInfo(mais, method);
        if(mai == null) {
            mai = new MethodAccessInfo();
            mai.setMethod(method.getName());
            mai.setUri(uri);
            mai.setInvokeCount(1);
            if(occurError) {
                mai.setErrorCount(1);
            }else {
                mai.setSuccessCount(1);
            }
            mai.setMinMillSecond(useTime);
            mai.setMaxMillSecond(useTime);
            mai.setAvgMillSecond(useTime);
            mais.add(mai);
        }else {
            if(useTime < mai.getMinMillSecond()) {
                mai.setMinMillSecond(useTime);
            }
            if(useTime > mai.getMaxMillSecond()) {
                mai.setMaxMillSecond(useTime);
            }
            mai.setInvokeCount(mai.getInvokeCount() + 1);
            if(occurError) {
                mai.setErrorCount(mai.getErrorCount()+1);
            }else {
                mai.setSuccessCount(mai.getSuccessCount()+1);
            }
            mai.setAvgMillSecond((mai.getAvgMillSecond()+useTime)/2);
        }
    }
    
    private static MethodAccessInfo getMethodAccessInfo(List<MethodAccessInfo> mais, Method method) {
        for(MethodAccessInfo mai : mais) {
            if(method.getName().equals(mai.getMethod())) {
                return mai;
            }
        }
        return null;
    }
    
    public static Map<String, Object> getAllAccessInfo() {
        Map<String, Object> result = new HashMap<String, Object>();
        for(Map.Entry<Class<?>, ControllerAccessInfo> entry : map.entrySet()) {
            ControllerAccessInfo cai = entry.getValue();
            result.put(cai.getControllerClazz().getSimpleName(), cai.getMethodInfos());
        }
        return result;
    }
}

3.4 实现EndPoint接口

public class AccessEndPoint implements Endpoint<Map<String, Object>> {

    public String getId() {
        return "access";
    }
    
    public boolean isEnabled() {
        return true;
    }
    
    public boolean isSensitive() {
        return false;
    }
    
    public Map<String, Object> invoke() {
        return AccessHolder.getAllAccessInfo();
    }
}

3.5 配置configuration

@Configuration
public class EndPointAutoConfig {
    @Bean
    public AccessEndPoint myEndPoint() {
        return new AccessEndPoint();
    }
}

4. 总结

实现对接口的信息记录方案其实很多,有的时候可以手写AOP,然后基于切面proceed后的对象进行操作,也可以直接用拦截器进行。说到底,一切切面型的工作,都需要深入理解AOP的思想,善于总结和归纳。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,014评论 11 349
  • 人潮涌动 被墨镜染色的天空 独自失控 于无边黑夜尽情放纵 我曾想象过人们口中的霓虹 也试图寻找记忆中的迷宫 希望带...
    岁临海阅读 334评论 3 7
  • 最后一天的作业是写《刻意练习》的读书笔记。可是憋了一天硬是没想好要写什么。越是着急越是写不出半个字。最后索性随便写...
    喜之郎碎碎念阅读 655评论 0 1
  • 昆明的夜是寒冷的,也是温暖的, 昆明的冷,冷在皮肤的表层, 昆明的暖暖在人心 在寒冷的晚上总能看到幸福的笑容 在寒...
    杨晓2019阅读 416评论 0 0
  • 我又来分享了。 遇到喜欢的勇敢点,女人花点心思追求自己的幸福。
    橙子的世界你不懂阅读 575评论 0 2