利用Jmx统计spring-mvc所有controller的调用

简介:        一、spring-mvc添加拦截器配置: 对所有/下的访问都做拦截     二、 定义VisitCounterStatInterceptor    1. 这里我们使用了guava的中的AtomicLongMap, 它的底层是ConcurrentHashMap,可以用来记录每个key的counter, 可以作为一种很高效的计数器。

 

      一、spring-mvc添加拦截器配置: 对所有/下的访问都做拦截

    
        
        
    

 

   二、 定义VisitCounterStatInterceptor

  

  1. 这里我们使用了guava的中的AtomicLongMap, 它的底层是ConcurrentHashMap,可以用来记录每个key的counter, 可以作为一种很高效的计数器。

  2. 这里我们用定义了两个AtomicLongMap, 分别记录每个controller-uri对应的访问次数以及慢查询次数(例如超过100秒)。 

  3. 耗时是通过ThreadLocal来记录,在preHandle记录开始时间,在afterCompletion计算整个controller的耗时,但是这里强调一下finally中一定要调用costThreadLocal.remove();

package com.sohu.tv.mobil.web.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.google.common.util.concurrent.AtomicLongMap;

/**
 * 访问相关统计拦截器
 * 
 * @author leifu
 * @Date 2015年10月30日
 * @Time 上午9:36:16
 */
public class VisitCounterStatInterceptor extends HandlerInterceptorAdapter {
    private Logger logger = LoggerFactory.getLogger(VisitCounterStatInterceptor.class);

    /**
     * 记录接口访问
     */
    public static final AtomicLongMap VISIT_COUNT_MAP = AtomicLongMap.create();

    /**
     * 记录接口慢查询
     */
    public static final AtomicLongMap VISIT_SLOW_COST_MAP = AtomicLongMap.create();

    /**
     * 耗时
     */
    private ThreadLocal costThreadLocal = new ThreadLocal();

    /**
     * 最大接受的耗时
     */
    private final static long MAX_ACCEPT_TIME = 100;

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        if (StringUtils.isNotBlank(uri)) {
            VISIT_COUNT_MAP.incrementAndGet(uri);
            // 记录startTime
            costThreadLocal.set(System.currentTimeMillis());
        }
        return true;
    }

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

    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        try {
            String uri = request.getRequestURI();
            if (StringUtils.isNotBlank(uri)) {
                long startTime = costThreadLocal.get();
                long costTime = System.currentTimeMillis() - startTime;
                if (costTime > MAX_ACCEPT_TIME) {
                    VISIT_SLOW_COST_MAP.incrementAndGet(uri);
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            costThreadLocal.remove();
        }
    }
}

 

  三、利用jmx记录controller-uri的调用统计

 

  1. 定义MBean:

package com.sohu.tv.mobil.common.jmx;

import java.util.Map;

public interface CounterMapMBean {

    void clear();

    Map getCounterMap();

}

 

  2.定义MBean的实现:

package com.sohu.tv.mobil.common.jmx.impl;

import com.google.common.util.concurrent.AtomicLongMap;
import com.sohu.tv.mobil.common.jmx.CounterMapMBean;

import java.util.*;

public class CounterMapImpl implements CounterMapMBean {

    public final AtomicLongMap counterMap;

    public CounterMapImpl(AtomicLongMap counterMap) {
        this.counterMap = counterMap;
    }

    @Override
    public void clear() {
        counterMap.clear();
    }

    @Override
    public Map getCounterMap() {
        List> entryList = new ArrayList<>(counterMap.asMap().entrySet());
        Collections.sort(entryList, new Comparator>() {
            @Override
            public int compare(Map.Entry o1, Map.Entry o2) {
                Long v1 = o1.getValue();
                Long v2 = o2.getValue();
                if (v1 > v2) {
                    return -1;
                } else if (v1 < v2) {
                    return 1;
                } else {
                    return o1.getKey().compareTo(o2.getKey());
                }
            }
        });
        Map resultMap = new LinkedHashMap();
        for (Map.Entry entry : entryList) {
            resultMap.put(entry.getKey(), entry.getValue());
        }
        return resultMap;
    }

}

 

  3. 在spring中定义jmx:

    
        
    

    
        
            
        
    
    
    
        
            
        
    

    
        
            
                
                
            
        
        
        
    

    
        
            
                com.sohu.tv.mobil.common.jmx.CounterMapMBean
            
        
    


  

  4. 上线后,就可以在jvisualvm或者jconsole中看到MBean的调用统计:

  

  5. 我们在后台,调用jmx,并做成界面:

  

   

  四、 存在的几个问题

 

  1. request.getRequestURI()可能会存在问题,比如如果使用了如下配置,可能会撑爆AtomicLongMap, 造成内存溢出,解决方法还要进一步观察(但是暂时我们的系统没有使用这种调用方式)

    @RequestMapping(value = "/drama/{pid}", produces = "text/javascript; charset=UTF-8")
  

  2. 使用了后,出现了很多奇怪的uri, 比如下图中的情况,具体原因还要查询。

   

 

  3. jmx是非持久化的,只能查询实时数据,如果需要的话可以定期统计jmx到mysql或者其他存储,方便查询历史数据,帮助有效定位问题

  

  4. jmx的数据可以结合nagios或者ganglia来使用,不需要单独开发后台界面。

 

 附图一张:

 

 

相关文章
|
前端开发 Java 数据安全/隐私保护
深入理解 Spring MVC Controller —— 请求参数获取
前言 接上篇《深入理解 Spring MVC Controller —— 请求映射》,上篇主要介绍了处理器方法及请求映射的定义。有了处理器方法 Spring MVC 就可以对请求进行处理,有了请求映射 Spring MVC 就能知道哪些请求应该由哪些处理器方法来处理。
1233 0
深入理解 Spring MVC Controller —— 请求参数获取
spring-mvc里Controller中model.addAttribute报红(错误记录日志)
spring-mvc里Controller中model.addAttribute报红(错误记录日志)
|
JSON 前端开发 Java
Spring 注解之@RestController与@Controller的区别
Spring 注解之@RestController与@Controller的区别
513 0
|
安全 Java Spring
spring的controller是单例还是多例,怎么保证并发的安全
spring的controller是单例还是多例,怎么保证并发的安全
264 0
|
XML JSON Java
图文并茂:解析Spring Boot Controller返回图片的三种方式
图文并茂:解析Spring Boot Controller返回图片的三种方式
1600 0
|
安全 Java Spring
Spring 的 Controller 是单例还是多例?怎么保证并发的安全
Spring 的 Controller 是单例还是多例?怎么保证并发的安全
87 0
|
安全 Java Spring
Spring 的 Controller 是单例还是多例?怎么保证并发的安全
Spring 的 Controller 是单例还是多例?怎么保证并发的安全
143 0
|
JSON 前端开发 Java
Spring Boot之Controller控制器:实现Web请求处理与业务逻辑分离
本篇详细介绍了Spring Boot中的Controller控制器的作用、用法和最佳实践。Controller是负责处理Web请求和响应的核心组件,能够将前端的HTTP请求映射到相应的业务逻辑处理,并返回适当的响应。通过一个简单的示例,展示了如何创建和配置Controller,并使用不同类型的注解来映射请求。还介绍了如何处理请求参数、路径变量,并展示了Controller方法返回不同类型的数据和视图的方式。通过学习本文内容,读者可以更好地理解和使用Spring Boot中的Controller,从而构建出灵活、高效的Web应用,提供优质的用户体验。
2985 2
|
JSON 缓存 前端开发
Spring Controller 基本认识及应用
Controller 在 spring 中代表的是控制层,是将访问者请求进行分发调用不同函数,来控制获取请求参数以及返回业务层处理完的数据给访问者的层面。它在 spring 中必须在 Controll
364 0
Spring Controller 基本认识及应用
|
前端开发 Java Spring
Spring Boot 中的 @Controller 注解:原理、用法与示例
Spring Boot 中的 @Controller 注解:原理、用法与示例

热门文章

最新文章