最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringMVC攔截器實現(xiàn)接口耗時監(jiān)控功能

 更新時間:2024年02月03日 08:48:02   作者:CIT  
本文呢主要介紹了基于SpringMVC攔截器實現(xiàn)的接口耗時監(jiān)控功能,統(tǒng)計接口的耗時情況屬于一個可以復(fù)用的功能點,因此這里直接使用 SpringMVC的HandlerInterceptor攔截器來實現(xiàn),需要的朋友可以參考下

前言

在日常的項目開發(fā)過程中,后端開發(fā)人員應(yīng)該主動去關(guān)心自己的接口性能。這種關(guān)心需要量化,而量化的直接方式就是對接口的響應(yīng)時間進行監(jiān)控,以了解系統(tǒng)性能,幫助判斷性能瓶頸。本文基于已有的全鏈路日志系統(tǒng)進一步補充了接口耗時的方案。已有的全鏈路日志系統(tǒng)是圍繞ELK+Jaeger構(gòu)建起來的,在Spring Cloud微服務(wù)架構(gòu)中,可以實現(xiàn)跨服務(wù)的請求日志追蹤 ,幫助我們進行線上問題排查。

服務(wù)告警部分則是通過Frostmourne平臺來實現(xiàn)了,該平臺可以接入Elasticsearch,配置相關(guān)的項目監(jiān)控與告警。當監(jiān)控到接口超時以后,可以通過接口超時日志中的traceId,在Jaeger平臺上查看整個請求鏈路的耗時分布,快速明確問題發(fā)生的位置,提升問題發(fā)現(xiàn)與響應(yīng)的速度。

實現(xiàn)

基本介紹

統(tǒng)計接口的耗時情況屬于一個可以復(fù)用的功能點,因此這里直接使用 SpringMVC的HandlerInterceptor攔截器來實現(xiàn),后續(xù)抽取成一個公共組件,方便復(fù)用。

攔截器接口 HandlerInterceptor 提供了三個方法來實現(xiàn)對請求前、請求后,響應(yīng)后進行自定義處理,并且攔截器的前置處理和后置處理是具體關(guān)聯(lián)性的。

  • preHandle() :在 Controller 方法執(zhí)行之前執(zhí)行。即在 HandlerMapping 確定適當?shù)奶幚沓绦驅(qū)ο笾笳{(diào)用,但在HandlerAdapter 調(diào)用處理程序之前調(diào)用。
  • postHandle() :在 Controller 方法執(zhí)行之后執(zhí)行。即在 HandlerAdapter 實際調(diào)用處理程序之后,但在DispatcherServlet 呈現(xiàn)視圖之前調(diào)用。
  • afterCompletion() :完成請求處理后(即渲染視圖之后)的回調(diào)。 將在處理程序執(zhí)行的任何結(jié)果上被調(diào)用,從而允許適當?shù)馁Y源清理。

實現(xiàn)思路

要統(tǒng)計接口處理請求的時長,可以在攔截器的 preHandle() 方法記錄請求開始時間(startTime),在 afterCompletion() 方法中記錄請求處理完后的結(jié)束時間(endTime),請求處理時間(響應(yīng)時間) = 結(jié)束時間 - 開始時間。

實現(xiàn)過程

  • 定義一個攔截器
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
?
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
?
/**
 * 攔截器,統(tǒng)計接口耗時
 */
@Slf4j
public class TimeConsumingInterceptor implements HandlerInterceptor {
?
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 記錄請求開始時間
        request.setAttribute("_startTime", System.currentTimeMillis());
        return true;
    }
?
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        // no need to override
    }
?
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        // 請求結(jié)束時間
        Long endTime = System.currentTimeMillis();
        try {
            // 從HttpServletRequest獲取開始時間
            Long startTime = (long) request.getAttribute("_startTime");
            String clientIP = ServletUtil.getClientIP(request, "");
            String fullUrl = getFullUrl(request);
            Long cost = endTime - startTime;
            MDC.put("cost_time", cost.toString());
            MDC.put("request_url", fullUrl);
            MDC.put("client_ip", clientIP);
            // 打印接口信息及耗時
            log.info("client IP {}, url {}, cost {}ms", clientIP, fullUrl, cost);
        } catch (Exception e) {
            log.error("fail to calculate time cost", e);
        } finally {
            MDC.remove("cost_time");
            MDC.remove("request_url");
            MDC.remove("client_ip");
        }
    }
?
    /**
     * 獲取完整的URL路徑
     *
     * @param request 請求對象{@link HttpServletRequest}
     * @return 完整的URL路徑
     */
    private String getFullUrl(HttpServletRequest request) {
        //記錄請求參數(shù)
        StringBuilder sb = new StringBuilder();
        String method = request.getMethod();
        sb.append(method).append(" ");
        sb.append(request.getRequestURL().toString());
        if (RequestMethod.POST.name().equals(method)) {
            //獲取參數(shù)
            Map<String, String[]> pm = request.getParameterMap();
            Set<Map.Entry<String, String[]>> es = pm.entrySet();
            Iterator<Map.Entry<String, String[]>> iterator = es.iterator();
            appendPathVariable(iterator, sb);
        }
        return sb.toString();
    }
?
    private void appendPathVariable(Iterator<Map.Entry<String, String[]>> iterator, StringBuilder sb) {
        int pointer = 0;
        while (iterator.hasNext()) {
            if (pointer == 0) {
                sb.append("?");
            } else {
                sb.append("&");
            }
            Map.Entry<String, String[]> next = iterator.next();
            String key = next.getKey();
            String[] value = next.getValue();
            for (int i = 0; i < value.length; i++) {
                if (i != 0) {
                    sb.append("&");
                }
                if (value[i].length() <= 20) {
                    sb.append(key).append("=").append(value[i]);
                } else {
                    sb.append(key).append("=").append(CharSequenceUtil.subPre(value[i], 20)).append("…");
                }
            }
            pointer++;
        }
    }
}
  • 配置攔截器使其生效
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
?
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
?
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TimeConsumingInterceptor())
                // 需攔截的URI配置
                .addPathPatterns("/**")
                // 不需攔截的URI配置
                .excludePathPatterns("/swagger/**", "/static/**", "/resource/**");
        log.info("***************** ADD TIME CONSUMING INTERCEPTOR  ******************");
    }
}
  • 添加logback配置,在開發(fā)和測試環(huán)境由于流量小,可以通過TCP監(jiān)聽的方式直接將接口的耗時日志傳輸至logstash,生產(chǎn)環(huán)境最好還是通過filebeat監(jiān)聽日志文件的方式去實現(xiàn)。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <contextName>log</contextName>
?
    <property name="logback.logDir" value="${LOG_PATH}"/>
    <springProperty name="logback.appName" scope="context" source="spring.application.name"/>
    <springProperty name="logback.elastic" scope="context" source="logback.elastic"/>
    <springProperty name="env" scope="context" source="spring.profiles.active"/>
    <springProperty name="serverIP" scope="context" source="spring.cloud.client.ip-address" defaultValue="0.0.0.0"/>
    <property name="commonLayoutPattern"
              value="[${serverIP}] %d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} [%mdc{trace_id:-N/A}] ${LOG_LEVEL_PATTERN:-%p} ${PID:- } --- [%t] %logger{39}.%method[%line] : %m%n"/>
?
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <!--展示格式 layout -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${commonLayoutPattern}</pattern>
        </layout>
    </appender>
?
    <appender name="logStash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <!--可以訪問的logstash日志收集端口-->
        <destination>192.168.xxx.xxx:4560</destination>
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>Asia/Shanghai</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "appName": "${logback.appName}-${env}",
                        "serverIP": "${serverIP}",
                        "traceId":"%mdc{trace_id:-N/A}",
                        "requestUrl":"%mdc{request_url:-N/A}",
                        "clientIP":"%mdc{client_ip:-N/A}",
                        "costTime": "%mdc{cost_time:-N/A}"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>
?
    <springProfile name="dev,pre">
        <logger name="com.xxx.xxx.log.autoconfigure.TimeConsumingInterceptor" additivity="false">
            <appender-ref ref="logStash"/>
            <appender-ref ref="consoleLog"/>
            <appender-ref ref="fileRequestLog"/>
        </logger>
    </springProfile>
?
    <springProfile name="prod">
        <logger name="com.xxx.xxx.log.autoconfigure.TimeConsumingInterceptor" additivity="false">
            <appender-ref ref="fileRequestLog"/>
        </logger>
    </springProfile>
?
    <appender name="asyncRequestLog" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>1024</queueSize>
        <appender-ref ref="fileRequestLog"/>
    </appender>
</configuration>
  • 配置開發(fā)、測試環(huán)境的logstash傳輸耗時日志
input {
  tcp {
    host => "192.168.xxx.xxx"
    port => 4560
    codec => json_lines
  }
}
?
?
filter {
  mutate {
    convert => {
      "costTime" => "integer"
    }
  }
}
?
output {
  elasticsearch {
    hosts => ["http://192.168.xxx.xxx:9200"]
    index => "request-%{[appName]}-%{+YYYY.MM.dd}"
    }
}
  • 查看耗時結(jié)果

  • 配置耗時監(jiān)控與結(jié)果驗證

以上就是基于SpringMVC攔截器實現(xiàn)的接口耗時監(jiān)控功能的詳細內(nèi)容,更多關(guān)于SpringMVC接口耗時監(jiān)控的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring Boot整合mybatis使用注解實現(xiàn)動態(tài)Sql、參數(shù)傳遞等常用操作(實現(xiàn)方法)

    Spring Boot整合mybatis使用注解實現(xiàn)動態(tài)Sql、參數(shù)傳遞等常用操作(實現(xiàn)方法)

    這篇文章主要介紹了Spring Boot整合mybatis使用注解實現(xiàn)動態(tài)Sql、參數(shù)傳遞等常用操作(實現(xiàn)方法),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • 詳解Java停止線程的四種方法

    詳解Java停止線程的四種方法

    如何停止java的線程一直是一個困惱我們開發(fā)多線程程序的一個問題嗎,本文主要介紹了詳解Java停止線程的四種方法,感興趣的可以了解一下
    2021-05-05
  • 單例模式垃圾回收_動力節(jié)點Java學(xué)院整理

    單例模式垃圾回收_動力節(jié)點Java學(xué)院整理

    這篇文章主要為大家詳細介紹了單例模式垃圾回收的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • SpringBoot3.3.X整合Mybatis-Plus的實現(xiàn)示例

    SpringBoot3.3.X整合Mybatis-Plus的實現(xiàn)示例

    本文介紹了在Spring Boot 3.3.2中整合MyBatis-Plus 3.5.7,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-03-03
  • MyBatis SQL執(zhí)行模塊的使用及解讀

    MyBatis SQL執(zhí)行模塊的使用及解讀

    本文詳細介紹了MyBatis的SQL執(zhí)行模塊,涵蓋了Executor執(zhí)行器體系、緩存機制、事務(wù)管理和批處理原理等內(nèi)容,幫助讀者全面理解MyBatis在執(zhí)行SQL時的各個關(guān)鍵點
    2026-01-01
  • JVM虛擬機查找類文件的順序方法

    JVM虛擬機查找類文件的順序方法

    下面小編就為大家分享一篇JVM虛擬機查找類文件的順序方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • 通過實例解析Socket套接字通信原理

    通過實例解析Socket套接字通信原理

    這篇文章主要介紹了通過實例解析Socket套接字通信原理,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-12-12
  • JFinal 調(diào)用存儲過程的步驟

    JFinal 調(diào)用存儲過程的步驟

    這篇文章主要介紹了JFinal 調(diào)用存儲過程的步驟,幫助大家更好的理解和學(xué)習(xí)使用JFinal,感興趣的朋友可以了解下
    2021-03-03
  • Spring AI 入門學(xué)習(xí)指南

    Spring AI 入門學(xué)習(xí)指南

    本文介紹了如何使用SpringAI和Ollama進行AI開發(fā),包括如何配置和使用聊天、圖像、語音轉(zhuǎn)文字和文字轉(zhuǎn)語音模型,以及如何在Java項目中集成Ollama模型,感興趣的朋友一起看看吧
    2024-11-11
  • SpringSecurity 手機號登錄功能實現(xiàn)

    SpringSecurity 手機號登錄功能實現(xiàn)

    這篇文章主要介紹了SpringSecurity 手機號登錄功能實現(xiàn),本文通過實例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧
    2023-12-12

最新評論

陆丰市| 紫金县| 阿图什市| 平定县| 桦甸市| 甘德县| 安多县| 西吉县| 青海省| 舒城县| 灵宝市| 河曲县| 泾川县| 日土县| 嵊州市| 永泰县| 色达县| 庆元县| 和硕县| 信宜市| 永泰县| 偃师市| 二连浩特市| 潼南县| 白玉县| 麻城市| 潞城市| 佳木斯市| 古浪县| 黄山市| 雷波县| 扶风县| 华安县| 玉溪市| 江城| 清流县| 九龙县| 池州市| 锡林郭勒盟| 蕉岭县| 来安县|