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

Spring?Aop+Redis實(shí)現(xiàn)優(yōu)雅記錄接口調(diào)用情況

 更新時(shí)間:2023年06月13日 16:19:37   作者:Lxlxxx  
通常情況下,開(kāi)發(fā)完一個(gè)接口,無(wú)論是在測(cè)試階段還是生產(chǎn)上線,我們都需要對(duì)接口的執(zhí)行情況做一個(gè)監(jiān)控,所以本文為大家整理了Spring統(tǒng)計(jì)接口調(diào)用的多種方法,希望對(duì)大家有所幫助

記錄接口調(diào)用情況的訴求

通常情況下,開(kāi)發(fā)完一個(gè)接口,無(wú)論是在測(cè)試階段還是生產(chǎn)上線,我們都需要對(duì)接口的執(zhí)行情況做一個(gè)監(jiān)控,比如記錄接口的調(diào)用次數(shù)、失敗的次數(shù)、調(diào)用時(shí)間、包括對(duì)接口進(jìn)行限流,這些都需要我們開(kāi)發(fā)人員進(jìn)行把控的,以便提高整體服務(wù)的運(yùn)行質(zhì)量,也能方便我們分析接口的執(zhí)行瓶頸,可以更好的對(duì)接口進(jìn)行優(yōu)化。

常見(jiàn)監(jiān)測(cè)服務(wù)的工具

通過(guò)一些常見(jiàn)第三方的工具,比如:Sentinel、Arthas、Prometheus等都可以進(jìn)行服務(wù)的監(jiān)控、報(bào)警、服務(wù)治理、qps并發(fā)情況,基本大多數(shù)都支持Dodcker、Kubernetes,也相對(duì)比較好部署,相對(duì)來(lái)說(shuō)比較適應(yīng)于大型業(yè)務(wù)系統(tǒng),服務(wù)比較多、并發(fā)量比較大、需要更好的服務(wù)治理,從而更加方便對(duì)服務(wù)進(jìn)行管理,但是一般小型的業(yè)務(wù)系統(tǒng)其實(shí)也沒(méi)太必要引入這些服務(wù),畢竟需要花時(shí)間和人力去搭建和運(yùn)維。

Spring實(shí)現(xiàn)接口調(diào)用統(tǒng)計(jì)

引入依賴 Spring boot、redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
  <version>2.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

思路就是通過(guò)AOP切面,在controller方法執(zhí)行前進(jìn)行切面處理,記錄接口名、方法、接口調(diào)用次數(shù)、調(diào)用情況、調(diào)用ip、并且寫(xiě)入redis緩存,提供查詢接口,可以查看調(diào)用情況。

RequestApiAdvice切面處理

package com.example.system.aspect;
import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class RequestApiAdvice {
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 前置處理,記錄接口在調(diào)用剛開(kāi)始的時(shí)候,每次調(diào)用+1
     *
     * @param joinPoint
     */
    @Before("execution(* com.example.system.controller.*.*(..))")
    public void before(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //獲取請(qǐng)求的request
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURI();
        String ip = getRequestIp(request);
        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        log.info("請(qǐng)求接口的類名:{}", className);
        log.info("請(qǐng)求的方法名:{}", methodName);
        //redis key由 url+類名+方法名+日期
        String apiKey = ip + "_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        //判斷是否存在key
        if (!redisTemplate.hasKey(apiKey)) {
            int count = Integer.parseInt(redisTemplate.boundValueOps(ip).get().toString());
            //訪問(wèn)次數(shù)大于20次就進(jìn)行接口熔斷
            if (count > 20) {
                throw new RuntimeException("已超過(guò)允許失敗訪問(wèn)次數(shù),不允許再次訪問(wèn)");
            }
            redisTemplate.opsForValue().increment(apiKey, 1);
        } else {
            redisTemplate.opsForValue().set(apiKey, "1", 1L, TimeUnit.DAYS);
        }
    }
    /**
     * 后置處理,接口在調(diào)用結(jié)束后,有返回結(jié)果,對(duì)接口調(diào)用成功后進(jìn)行記錄。
     */
    @After("execution(* com.example.system.controller.*.*(..))")
    public void after() {
        // 接收到請(qǐng)求,記錄請(qǐng)求內(nèi)容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //獲取請(qǐng)求的request
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURI();
        log.info("調(diào)用完成手的url:{}", url);
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        if (redisTemplate.hasKey(url)) {
            redisTemplate.boundHashOps(url).increment(date, 1);
        } else {
            redisTemplate.boundHashOps(url).put(date, "1");
        }
    }
    @AfterThrowing(value = "execution(* com.example.system.controller.*.*(..))", throwing = "e")
    public void throwing(Exception e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURI() + "_exception";
        //精確到時(shí)分秒
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        String date = format.format(new Date());
        //異常報(bào)錯(cuò)
        String exception = e.getMessage();
        redisTemplate.boundHashOps(url).put(date, exception);
    }
    private String getRequestIp(HttpServletRequest request) {
        //獲取ip
        String ip = request.getHeader("x-forwarded-for");
        Assert.notBlank(ip, "請(qǐng)求接口ip不能為空!");
        return ip;
    }
}

RedisSerialize序列化處理

這邊需要對(duì)redis的序列化方式進(jìn)行簡(jiǎn)單配置,要不然在進(jìn)行set key的操作的時(shí)候,由于key和value是字符串類型,如果不進(jìn)行反序化配置,redis通過(guò)key獲取value的時(shí)候,會(huì)出現(xiàn)null值。

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes", "deprecation" })
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(connectionFactory);
        // 定義value的序列化方式
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setKeySerializer(new StringRedisSerializer(Charset.forName("UTF-8")));
        //save hash use StringRedisSerializer as serial method
        template.setHashKeySerializer(new StringRedisSerializer(Charset.forName("UTF-8")));
        template.setHashValueSerializer(new StringRedisSerializer(Charset.forName("UTF-8")));
        return template;
    }
}

RedisTestController查詢r(jià)edis緩存接口

@RestController
@Slf4j
@RequestMapping("/api/redis")
public class RedisTestController {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @GetMapping("/getApiRequestCount")
    public List<String> getApiRequestCount() {
        List list =new ArrayList();
        Set<String> keys = stringRedisTemplate.keys("/api/*");
        for (int i = 0; i < keys.size(); i++) {
            Map<Object, Object> m = null;
            try {
                m = stringRedisTemplate.opsForHash().entries((String) keys.toArray()[i]);
            } catch (Exception e) {
                e.printStackTrace();
            }
            List result = new ArrayList();
            for (Object key : m.keySet()) {
                //將字符串反序列化為list
                String value = (String) m.get(key);
                result.add(String.format("%s: %s", key, value));
            }
            list.addAll(result);
        }
        return list;
    }
    @GetMapping("/{methodName}")
    public String getCount(@PathVariable String methodName) {
        List<Object> values = stringRedisTemplate.boundHashOps("/api/" + methodName).values();
        return String.format("%s: %s", methodName, values);
    }
}

redis緩存存儲(chǔ)情況 請(qǐng)求次數(shù)

異常key

查詢緩存結(jié)果

可以看到,統(tǒng)計(jì)到了接口請(qǐng)求的時(shí)間以及異常信息,還有接口的請(qǐng)求次數(shù)。

總結(jié)

某些場(chǎng)景下還是需要用到接口請(qǐng)求統(tǒng)計(jì)的,包括也可以做限流操作,大部分中間件的底層做監(jiān)控,底層實(shí)現(xiàn)方式也差不了多少, 記得很多年前有道面試題,還被問(wèn)到如何做接口的請(qǐng)求次數(shù)統(tǒng)計(jì),以及限流策略。

以上就是Spring Aop+Redis實(shí)現(xiàn)優(yōu)雅記錄接口調(diào)用情況的詳細(xì)內(nèi)容,更多關(guān)于Spring Redis接口調(diào)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java 中的字符串常量池詳解

    Java 中的字符串常量池詳解

    本文主要介紹Java中的字符串常量池的知識(shí),這里整理了相關(guān)資料及簡(jiǎn)單示例代碼幫助大家學(xué)習(xí)理解此部分的知識(shí),有需要的小伙伴可以參考下
    2016-09-09
  • Java實(shí)戰(zhàn)之網(wǎng)上書(shū)店管理系統(tǒng)的實(shí)現(xiàn)

    Java實(shí)戰(zhàn)之網(wǎng)上書(shū)店管理系統(tǒng)的實(shí)現(xiàn)

    本文將利用Java語(yǔ)言實(shí)現(xiàn)網(wǎng)上書(shū)店管理系統(tǒng)。其功能一般包括:圖書(shū)信息管理、用戶信息管理、圖書(shū)購(gòu)買、圖書(shū)訂單查看、圖書(shū)添加、圖書(shū)維護(hù)等等,感興趣的可以了解一下
    2022-06-06
  • 在java中用Scanner類讀入單個(gè)字符的方法

    在java中用Scanner類讀入單個(gè)字符的方法

    今天小編就為大家分享一篇在java中用Scanner類讀入單個(gè)字符的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • Java中常用加密/解密方法詳解

    Java中常用加密/解密方法詳解

    本文主要介紹了Java中常用加密/解密方法。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • Java系統(tǒng)的高并發(fā)解決方法詳解

    Java系統(tǒng)的高并發(fā)解決方法詳解

    這篇文章主要介紹了Java系統(tǒng)的高并發(fā)解決方法,內(nèi)容十分豐富,在這里分享給大家,需要的朋友可以參考。
    2017-09-09
  • 關(guān)于String.format()格式化輸出方式

    關(guān)于String.format()格式化輸出方式

    String.format()是Java的格式化輸出方法,支持多種數(shù)據(jù)類型和格式化選項(xiàng),它在格式化和拼接字符串時(shí)具有較高的靈活性,但效率相對(duì)較低,特別是在處理大量數(shù)據(jù)時(shí),在實(shí)際編程中,應(yīng)根據(jù)具體需求選擇合適的字符串拼接方式
    2024-12-12
  • idea 默認(rèn)路徑修改從C盤更改到D盤

    idea 默認(rèn)路徑修改從C盤更改到D盤

    本文主要介紹了idea 默認(rèn)路徑修改從C盤更改到D盤,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • 聊一聊concurrenthashmap的size方法原理

    聊一聊concurrenthashmap的size方法原理

    這篇文章主要介紹了concurrenthashmap的size方法原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java基礎(chǔ)知識(shí)總結(jié)之繼承

    Java基礎(chǔ)知識(shí)總結(jié)之繼承

    這一篇我們來(lái)學(xué)習(xí)面向?qū)ο蟮牡诙€(gè)特征——繼承,文中有非常詳細(xì)的基礎(chǔ)知識(shí)總結(jié),對(duì)正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-06-06
  • 詳解springboot+atomikos+druid?數(shù)據(jù)庫(kù)連接失效分析

    詳解springboot+atomikos+druid?數(shù)據(jù)庫(kù)連接失效分析

    本文主要介紹了springboot+atomikos+druid?數(shù)據(jù)庫(kù)連接失效分析,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02

最新評(píng)論

天等县| 高碑店市| 西乌珠穆沁旗| 武宣县| 通州区| 昌图县| 东阳市| 延川县| 大荔县| 博白县| 玛纳斯县| 疏附县| 磴口县| 深水埗区| 资中县| 商洛市| 乡宁县| 肥西县| 义乌市| 辽阳县| 辽阳县| 化隆| 临猗县| 句容市| 福贡县| 长阳| 康定县| 巴里| 邵阳市| 临桂县| 苍山县| 贵德县| 岗巴县| 上蔡县| 百色市| 浦县| 邯郸县| 馆陶县| 奈曼旗| 拜城县| 通州区|