欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡(jiǎn)單的限流器

 更新時(shí)間:2023年08月15日 11:08:57   作者:冰點(diǎn).  
在Spring?Boot中使用Redis和過(guò)濾器實(shí)現(xiàn)請(qǐng)求限流,過(guò)濾器將在每個(gè)請(qǐng)求到達(dá)時(shí)檢查請(qǐng)求頻率,并根據(jù)設(shè)定的閾值進(jìn)行限制,這樣可以保護(hù)您的應(yīng)用程序免受惡意請(qǐng)求或高并發(fā)請(qǐng)求的影響,本文我們通過(guò)Spring?Boot?+Redis?實(shí)現(xiàn)一個(gè)輕量級(jí)的消息隊(duì)列,需要的朋友可以參考下

1.基礎(chǔ)介紹

1.1. 限流場(chǎng)景

假設(shè)我們有一個(gè)API接口,需要限制每個(gè)用戶(hù)在一段時(shí)間內(nèi)的請(qǐng)求頻率。比如每秒只允許請(qǐng)求100次等等的業(yè)務(wù)需求。

1.2. 實(shí)現(xiàn)限流邏輯:

使用Redis的計(jì)數(shù)器功能可以實(shí)現(xiàn)基于時(shí)間窗口的限流算法。通過(guò)在Redis中存儲(chǔ)請(qǐng)求計(jì)數(shù)器和過(guò)期時(shí)間,可以控制單位時(shí)間內(nèi)的請(qǐng)求頻率。在需要進(jìn)行限流的接口或方法中,使用Redis的原子操作(如INCR和EXPIRE)來(lái)增加計(jì)數(shù)器并設(shè)置過(guò)期時(shí)間。

在每個(gè)請(qǐng)求到達(dá)時(shí),檢查計(jì)數(shù)器的值是否超過(guò)設(shè)定的閾值,如果超過(guò)則拒絕請(qǐng)求,否則允許請(qǐng)求繼續(xù)執(zhí)行。

2.步驟

2.1. 引入依賴(lài)

<dependencies>
    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

2.2. 配置文件

# Redis連接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=your_password
spring.redis.database=0
# Redis連接池配置
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-wait=-1

在上面的配置中,您可以根據(jù)實(shí)際情況修改以下屬性:

  • spring.redis.host:Redis服務(wù)器的主機(jī)名或IP地址。
  • spring.redis.port:Redis服務(wù)器的端口號(hào)。
  • spring.redis.password:Redis服務(wù)器的密碼(如果有的話(huà))。
  • spring.redis.database:Redis數(shù)據(jù)庫(kù)的索引,默認(rèn)為0。

另外,您還可以配置Redis連接池的屬性,以控制連接池的行為。在示例配置中,設(shè)置了以下連接池屬性:

  • spring.redis.jedis.pool.max-active:連接池中的最大活動(dòng)連接數(shù)。
  • spring.redis.jedis.pool.max-idle:連接池中的最大空閑連接數(shù)。
  • spring.redis.jedis.pool.min-idle:連接池中的最小空閑連接數(shù)。
  • spring.redis.jedis.pool.max-wait:從連接池獲取連接的最大等待時(shí)間(毫秒),-1表示無(wú)限等待。

如果 使用的是YAML格式的配置文件(application.yml),可以將上述配置轉(zhuǎn)換為相應(yīng)的格式:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: your_password
    database: 0
  redis.jedis.pool:
    max-active: 50
    max-idle: 10
    min-idle: 5
    max-wait: -1

請(qǐng)根據(jù)您的實(shí)際Redis服務(wù)器配置進(jìn)行調(diào)整,并根據(jù)需要添加其他相關(guān)配置,如超時(shí)設(shè)置、SSL配置等。

2.3. 核心源碼

  • 實(shí)現(xiàn)請(qǐng)求限流過(guò)濾器

    創(chuàng)建一個(gè)實(shí)現(xiàn)javax.servlet.Filter接口的請(qǐng)求限流過(guò)濾器。在過(guò)濾器中,使用Redis的計(jì)數(shù)器功能來(lái)實(shí)現(xiàn)請(qǐng)求限流邏輯。

    示例中,RequestLimitFilter是一個(gè)實(shí)現(xiàn)了javax.servlet.Filter接口的請(qǐng)求限流過(guò)濾器。它使用Redis的計(jì)數(shù)器功能來(lái)實(shí)現(xiàn)請(qǐng)求限流邏輯。每個(gè)請(qǐng)求到達(dá)時(shí),根據(jù)客戶(hù)端的IP地址作為Redis的鍵,增加計(jì)數(shù)器的值并設(shè)置過(guò)期時(shí)間為指定的時(shí)間窗口。如果計(jì)數(shù)器超過(guò)了設(shè)定的閾值(這里是100),則返回HTTP 429 Too Many Requests響應(yīng)

示例中使用的是RedisTemplate<String, String>來(lái)操作Redis, 可以根據(jù)需要調(diào)整為適合您的數(shù)據(jù)類(lèi)型和操作方式的RedisTemplate。

@Component
public class RequestLimitFilter implements Filter {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String REQUEST_LIMIT_PREFIX = "requestLimit:";
    private static final long REQUEST_LIMIT = 100; // 請(qǐng)求限制數(shù)量
    private static final long TIME_WINDOW = 60; // 時(shí)間窗口(單位:秒)
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ipAddress = getClientIpAddress(httpRequest);
        String key = REQUEST_LIMIT_PREFIX + ipAddress;
        Long counter = redisTemplate.opsForValue().increment(key, 1);
        if (counter == 1) {
            redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS);
        }
        if (counter > REQUEST_LIMIT) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            httpResponse.getWriter().write("請(qǐng)求頻率超過(guò)限制,請(qǐng)稍后再試!");
            return;
        }
        chain.doFilter(request, response);
    }
  private String getClientIpAddress(HttpServletRequest request) {
    String ipAddress = request.getHeader("X-Forwarded-For");
    if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getHeader("Proxy-Client-IP");
    }
    if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getHeader("WL-Proxy-Client-IP");
    }
    if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getRemoteAddr();
    }
    return ipAddress;
}
}

優(yōu)化后

public class RequestLimitFilter implements Filter {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String REQUEST_LIMIT_PREFIX = "requestLimit:";
    private static final long REQUEST_LIMIT = 100; // 請(qǐng)求限制數(shù)量
    private static final long TIME_WINDOW = 60; // 時(shí)間窗口(單位:秒)
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ipAddress = getClientIpAddress(httpRequest);
        String key = REQUEST_LIMIT_PREFIX + ipAddress;
        Long counter = redisTemplate.opsForValue().increment(key, 1);
        if (counter == 1) {
            redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS);
        }
        if (counter > REQUEST_LIMIT) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            try (PrintWriter writer = httpResponse.getWriter()) {
                writer.write("請(qǐng)求頻率超過(guò)限制,請(qǐng)稍后再試!");
            }
            return;
        }
        chain.doFilter(request, response);
    }
    private String getClientIpAddress(HttpServletRequest request) {
      ...
        return ipAddress;
    }
}

再優(yōu)化一下加入布隆過(guò)濾器

使用布隆過(guò)濾器減少對(duì)Redis的訪問(wèn):布隆過(guò)濾器是一種高效的概率數(shù)據(jù)結(jié)構(gòu),可以用于快速判斷元素是否存在于集合中。在限制請(qǐng)求頻率時(shí),可以使用布隆過(guò)濾器來(lái)減少對(duì)Redis的訪問(wèn)。只有在布隆過(guò)濾器判斷請(qǐng)求不是重復(fù)請(qǐng)求時(shí),才進(jìn)行Redis操作。

public class RequestLimitFilter implements Filter {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String REQUEST_LIMIT_PREFIX = "requestLimit:";
    private static final long REQUEST_LIMIT = 100; // 請(qǐng)求限制數(shù)量
    private static final long TIME_WINDOW = 60; // 時(shí)間窗口(單位:秒)
    private BloomFilter<String> bloomFilter;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化布隆過(guò)濾器
        int expectedInsertions = 1000; // 預(yù)期插入數(shù)量
        double falsePositiveProbability = 0.01; // 誤判率
        bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, falsePositiveProbability);
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ipAddress = getClientIpAddress(httpRequest);
        if (bloomFilter.mightContain(ipAddress)) {
            // 布隆過(guò)濾器判斷可能是重復(fù)請(qǐng)求,直接放行
            chain.doFilter(request, response);
            return;
        }
        String key = REQUEST_LIMIT_PREFIX + ipAddress;
        Long counter;
        boolean isNewKey = false;
        try {
            counter = redisTemplate.opsForValue().increment(key, 1);
            if (counter == 1) {
                redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS);
                isNewKey = true;
            }
        } catch (Exception e) {
            // 處理Redis操作異常
            // 可以選擇記錄日志或采取適當(dāng)?shù)奶幚泶胧?
            e.printStackTrace();
            chain.doFilter(request, response);
            return;
        }
        if (counter > REQUEST_LIMIT) {
            if (isNewKey) {
                // 刪除新創(chuàng)建的鍵,避免無(wú)限增長(zhǎng)
                redisTemplate.delete(key);
            }
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            try (PrintWriter writer = httpResponse.getWriter()) {
                writer.write("請(qǐng)求頻率超過(guò)限制,請(qǐng)稍后再試!");
            }
            return;
        }
        bloomFilter.put(ipAddress); // 將IP地址添加到布隆過(guò)濾器
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
        // 清理資源,如關(guān)閉Redis連接等
    }
    private String getClientIpAddress(HttpServletRequest request) {
        // 獲取客戶(hù)端IP地址的邏輯
        // ...
    }
}

在上述代碼中,我們引入了布隆過(guò)濾器來(lái)減少對(duì)Redis的訪問(wèn)。如果布隆過(guò)濾器判斷請(qǐng)求可能是重復(fù)請(qǐng)求,則直接放行,無(wú)需進(jìn)行Redis操作。同時(shí),我們還添加了對(duì)Redis操作異常的處理,并在限流超過(guò)閾值時(shí)刪除新創(chuàng)建的鍵,以避免無(wú)限增長(zhǎng)。請(qǐng)根據(jù)實(shí)際情況進(jìn)行適當(dāng)調(diào)整和完善。

  • 注冊(cè)過(guò)濾器
    在Spring Boot應(yīng)用程序的配置類(lèi)中注冊(cè)過(guò)濾器,以便它能夠在請(qǐng)求處理過(guò)程中生效。
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private RequestLimitFilter requestLimitFilter;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(requestLimitFilter);
    }
}

通過(guò)將過(guò)濾器添加到addInterceptors方法中,它將被注冊(cè)為Spring Boot應(yīng)用程序的全局過(guò)濾器,并在請(qǐng)求到達(dá)時(shí)執(zhí)行限流邏輯。

3.總結(jié)

其實(shí)上面我們寫(xiě)完的還是有問(wèn)題的

  • 如果系統(tǒng)部署在多個(gè)節(jié)點(diǎn)上,可以考慮使用分布式限流算法,如令牌桶算法或漏桶算法。這些算法可以在分布式環(huán)境中平衡請(qǐng)求的處理,并保證全局的請(qǐng)求限制。
  • 將請(qǐng)求限流的參數(shù),如請(qǐng)求限制數(shù)量和時(shí)間窗口,配置為可動(dòng)態(tài)調(diào)整的參數(shù)。可以使用注解或配置文件來(lái)管理這些參數(shù),以便在運(yùn)行時(shí)進(jìn)行調(diào)整,而無(wú)需重新編譯代碼。

到此這篇關(guān)于基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡(jiǎn)單的限流器的文章就介紹到這了,更多相關(guān)SpringBoot+Redis實(shí)現(xiàn)限流器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論