亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn)

 更新時(shí)間:2021年12月29日 09:46:14   作者:The_SHY  
本文將結(jié)合實(shí)例代碼,介紹SpringBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

一個(gè)基于Redis實(shí)現(xiàn)的接口限流方案,先說要實(shí)現(xiàn)的功能

  • 可以限制指定的接口,在一定時(shí)間內(nèi),只能被請(qǐng)求N次,超過次數(shù)就返回異常信息
  • 可以通過配置文件,或者管理后臺(tái),動(dòng)態(tài)的修改限流配置

實(shí)現(xiàn)的思路

使用 Hash 存儲(chǔ)接口的限流配置

request_limit_config    "/api2" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}

hash中的key就是請(qǐng)求的uri路徑,value是一個(gè)對(duì)象。通過3個(gè)屬性,描述限制策略

  • limit 最多請(qǐng)求次數(shù)
  • time 時(shí)間
  • timeUnit 時(shí)間單位

使用普通kv,存儲(chǔ)api的請(qǐng)求次數(shù)

request_limit:/api  1

處理請(qǐng)求的時(shí)候,通過increment對(duì)該key進(jìn)行 +1 操作,如果返回1,則表示是第一次請(qǐng)求,此時(shí)設(shè)置它的過期時(shí)間。為限制策略中定義時(shí)間限制信息。再通過命名的返回值,判斷是否超出了限制。

increment 指令是線程安全的,不用擔(dān)心并發(fā)的問題。

使用SpringBoot實(shí)現(xiàn)

創(chuàng)建SpringBoot工程,添加

spring-boot-starter-data-redis依賴,并且給出正確的配置。

這里不做工程的創(chuàng)建,配置,以及其他額外代碼的演示,僅僅給出關(guān)鍵的代碼。

RedisKeys

定義兩個(gè)Key,限流用到的2個(gè)Key

public interface RedisKeys {
    /**
     * api的限制配置,hash key
     */
    String REQUEST_LIMIT_CONFIG = "request_limit_config";

    /**
     * api的請(qǐng)求的次數(shù)
     */
    String REQUEST_LIMIT = "request_limit";
}

ObjectRedisTemplate

為了提高h(yuǎn)ash value的序列化效率,自定義一個(gè)RedisTemplate的實(shí)現(xiàn)。使用jdk的序列化,而不是json。

import org.springframework.data.redis.core.RedisTemplate;

public class ObjectRedisTemplate extends RedisTemplate<String, Object> {

}

RedisConfigration

把自定義的ObjectRedisTemplate配置到IOC

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;

import io.springboot.jwt.redis.ObjectRedisTemplate;

@Configuration
public class RedisConfiguration {
    @Bean
    public ObjectRedisTemplate objectRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) {

        ObjectRedisTemplate objectRedisTemplate = new ObjectRedisTemplate();
        objectRedisTemplate.setConnectionFactory(redisConnectionFactory);

        objectRedisTemplate.setKeySerializer(RedisSerializer.string());
        objectRedisTemplate.setValueSerializer(RedisSerializer.java());

        // hash的key使用String序列化
        objectRedisTemplate.setHashKeySerializer(RedisSerializer.string());
        // hash的value使用jdk的序列化
        objectRedisTemplate.setHashValueSerializer(RedisSerializer.java());
        return objectRedisTemplate;
    }
}

RequestLimitConfig

用于描述限制策略的對(duì)象。

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

public class RequestLimitConfig implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1101875328323558092L;

    // 最大請(qǐng)求次數(shù)
    private long limit;
    // 時(shí)間
    private long time;
    // 時(shí)間單位
    private TimeUnit timeUnit;
    public RequestLimitConfig() {
        super();
    }
    public RequestLimitConfig(long limit, long time, TimeUnit timeUnit) {
        super();
        this.limit = limit;
        this.time = time;
        this.timeUnit = timeUnit;
    }
    public long getLimit() {
        return limit;
    }
    public void setLimit(long limit) {
        this.limit = limit;
    }
    public long getTime() {
        return time;
    }
    public void setTime(long time) {
        this.time = time;
    }
    public TimeUnit getTimeUnit() {
        return timeUnit;
    }
    public void setTimeUnit(TimeUnit timeUnit) {
        this.timeUnit = timeUnit;
    }
    @Override
    public String toString() {
        return "RequestLimitConfig [limit=" + limit + ", time=" + time + ", timeUnit=" + timeUnit + "]";
    }
}

RequestLimitInterceptor

通過攔截器,來完成限流的實(shí)現(xiàn)。

import java.nio.charset.StandardCharsets;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import io.springboot.jwt.redis.ObjectRedisTemplate;
import io.springboot.jwt.redis.RedisKeys;
import io.springboot.jwt.web.RequestLimitConfig;

public class RequestLimitInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(RequestLimitInterceptor.class);

    @Autowired
    private ObjectRedisTemplate objectRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        /**
         * 獲取到請(qǐng)求的URI
         */
        String contentPath = request.getContextPath();
        String uri = request.getRequestURI().toString();
        if (!StringUtils.isEmpty(contentPath) && !contentPath.equals("/")) {
            uri =  uri.substring(uri.indexOf(contentPath) + contentPath.length());
        }
        LOGGER.info("uri={}",  uri);

        /**
         * 嘗試從hash中讀取得到當(dāng)前接口的限流配置
         */
        RequestLimitConfig requestLimitConfig = (RequestLimitConfig) this.objectRedisTemplate.opsForHash().get(RedisKeys.REQUEST_LIMIT_CONFIG, uri);
        if (requestLimitConfig == null) {
            LOGGER.info("該uri={}沒有限流配置", uri);
            return true;
        }

        String limitKey = RedisKeys.REQUEST_LIMIT + ":" + uri;

        /**
         * 當(dāng)前接口的訪問次數(shù) +1
         */
        long count = this.objectRedisTemplate.opsForValue().increment(limitKey);
        if (count == 1) {
            /**
             * 第一次請(qǐng)求,設(shè)置key的過期時(shí)間
             */
            this.objectRedisTemplate.expire(limitKey, requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit());
            LOGGER.info("設(shè)置過期時(shí)間:time={}, timeUnit={}", requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit());
        }

        LOGGER.info("請(qǐng)求限制。limit={}, count={}", requestLimitConfig.getLimit(), count);

        if (count > requestLimitConfig.getLimit()) {
            /**
             * 限定時(shí)間內(nèi),請(qǐng)求超出限制,響應(yīng)客戶端錯(cuò)誤信息。
             */
            response.setContentType(MediaType.TEXT_PLAIN_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.getWriter().write("服務(wù)器繁忙,稍后再試");
            return false;
        }
        return true;
    }
}

Controller

一個(gè)用于測試的接口類

import java.util.Collections;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping
    public Object test () {
        return Collections.singletonMap("success", true);
    }
}

WebMvcConfigration

攔截器的配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import io.springboot.jwt.web.interceptor.RequestLimitInterceptor;

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.requestLimitInterceptor())
            .addPathPatterns("/test");
    }

    @Bean
    public RequestLimitInterceptor requestLimitInterceptor() {
        return new RequestLimitInterceptor();
    }
}

通過@Test測試,初始化一個(gè)限流配置

@Autowired
private ObjectRedisTemplate objectRedisTemplate;

@Test
public void test () {
    // 3秒內(nèi),只能請(qǐng)求2次
    RequestLimitConfig requestLimitConfig = new RequestLimitConfig(2, 3, TimeUnit.SECONDS);
    // 限制的uri是 /test
    this.objectRedisTemplate.opsForHash().put(RedisKeys.REQUEST_LIMIT_CONFIG, "/test", requestLimitConfig);
}

使用瀏覽器演示

最后一些問題

怎么靈活的配置

都寫到這個(gè)份兒上了,如果熟悉Redis以及客戶端,我想提供一個(gè)“限流管理”接口的并不是難事兒。

針對(duì)指定的用戶限流

這里演示的方法是,針對(duì)接口的限流。有時(shí)候,也有一些特殊的需求,需要“針對(duì)不同”的用戶來做限流。打個(gè)比方。針對(duì)A用戶,允許有他1分鐘請(qǐng)求20次接口,針對(duì)B用戶,允許他1分鐘請(qǐng)求10次接口。 這個(gè)其實(shí)也簡單,只需要修改一下上面的兩個(gè)限制key,在key中添加用戶的唯一標(biāo)識(shí)(例如:ID)

request_limit_config    "/api2:{userId}" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}

request_limit:{userId}:/api  1

在攔截器中獲取到用戶的ID,加上用戶ID進(jìn)行檢索和判斷,就可以完成針對(duì)用戶的限流。

Restful 接口的問題

@GetMapping("/user/{id}")  // restful的檢索接口,往往把ID信息放在了URI中

這就會(huì)導(dǎo)致上面的代碼有問題,因?yàn)檫@里采用的是根據(jù)URI來完成的限流操作。檢索不同ID的用戶,會(huì)導(dǎo)致URI不同。 解決辦法我認(rèn)為也很簡單。那就不要使用URI,可以通過 自定義注解,方式,不同的接口,定義不同的唯一標(biāo)識(shí)。在攔截器中獲取到注解,讀取到唯一的編碼,代替原來的URI,即可。

到此這篇關(guān)于SpingBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpingBoot Redis接口限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring boot 總結(jié)之跨域處理cors的方法

    Spring boot 總結(jié)之跨域處理cors的方法

    本篇文章主要介紹了Spring boot 總結(jié)之跨域處理cors的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-02-02
  • 詳解Java豆瓣電影爬蟲——小爬蟲成長記(附源碼)

    詳解Java豆瓣電影爬蟲——小爬蟲成長記(附源碼)

    這篇文章主要介紹了詳解Java豆瓣電影爬蟲——小爬蟲成長記(附源碼) ,具有一定的參考價(jià)值,有需要的可以了解一下。
    2016-12-12
  • 詳解Spring Boot 集成Shiro和CAS

    詳解Spring Boot 集成Shiro和CAS

    這篇文章主要介紹了詳解Spring Boot 集成Shiro和CAS,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-05-05
  • 在SpringBoot接口中正確地序列化時(shí)間字段的方法

    在SpringBoot接口中正確地序列化時(shí)間字段的方法

    文章主要介紹在 Spring Boot 接口中正確序列化時(shí)間字段的方法,包括 Java 中Date和LocalDateTime類型的區(qū)別,JSON 序列化和請(qǐng)求參數(shù)中時(shí)間字段的處理,如時(shí)間字符串的格式配置、時(shí)間戳的使用及相關(guān)配置,還提到了在 Swagger UI 中的類型設(shè)置,需要的朋友可以參考下
    2024-11-11
  • 淺談為什么重寫equals()就要重寫hashCode()

    淺談為什么重寫equals()就要重寫hashCode()

    困擾我很久的問題,一直不明白為什么重寫equals()方法的時(shí)候要重寫hashCode()方法,這次總算弄明白了,作此分享,感興趣的可以了解一下
    2021-10-10
  • Java 生成圖片驗(yàn)證碼3種方法(字母、加減乘除、中文)

    Java 生成圖片驗(yàn)證碼3種方法(字母、加減乘除、中文)

    這篇文章主要介紹了Java 生成圖片驗(yàn)證碼3種方法(字母、加減乘除、中文),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • Java中的紙老虎之泛型

    Java中的紙老虎之泛型

    泛型在java中有很重要的地位,在面向?qū)ο缶幊碳案鞣N設(shè)計(jì)模式中有非常廣泛的應(yīng)用。對(duì)java的泛型特性的了解僅限于表面的淺淺一層,直到在學(xué)習(xí)設(shè)計(jì)模式時(shí)發(fā)現(xiàn)有不了解的用法,才想起詳細(xì)的記錄一下。
    2021-09-09
  • Spring?Boot?中的?@HystrixCommand?注解原理及使用方法

    Spring?Boot?中的?@HystrixCommand?注解原理及使用方法

    通過使用 @HystrixCommand 注解,我們可以輕松地實(shí)現(xiàn)對(duì)方法的隔離和監(jiān)控,從而提高系統(tǒng)的可靠性和穩(wěn)定性,本文介紹了Spring Boot 中的@HystrixCommand注解是什么,其原理以及如何使用,感興趣的朋友跟隨小編一起看看吧
    2023-07-07
  • Java實(shí)現(xiàn)warcraft?java版游戲的示例代碼

    Java實(shí)現(xiàn)warcraft?java版游戲的示例代碼

    致敬經(jīng)典的warcraft,《warcraft?java版》是一款即時(shí)戰(zhàn)略題材單機(jī)游戲,采用魔獸原味風(fēng)格和機(jī)制。本文將用java語言實(shí)現(xiàn),采用了swing技術(shù)進(jìn)行了界面化處理,感興趣的可以了解一下
    2022-09-09
  • 使用MyBatis-Plus實(shí)現(xiàn)聯(lián)表查詢分頁的示例代碼

    使用MyBatis-Plus實(shí)現(xiàn)聯(lián)表查詢分頁的示例代碼

    本文主要講述了如何在SpringBoot項(xiàng)目中使用MyBatis-Plus的分頁插件,通過這個(gè)示例,可以學(xué)會(huì)如何利用MyBatis-Plus進(jìn)行高效的分頁查詢,感興趣的可以了解一下
    2024-10-10

最新評(píng)論