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

Redis實現(xiàn)IP限流的2種方式舉例詳解

 更新時間:2024年08月13日 08:57:48   作者:@猿程序  
通俗的說限流就是限制一段時間內(nèi)用戶訪問資源的次數(shù),減輕服務(wù)器壓力,這篇文章主要給大家介紹了關(guān)于Redis實現(xiàn)IP限流的2種方式,文中通過圖文介紹的非常詳細,需要的朋友可以參考下

通過reids實現(xiàn)

  • 限流的流程圖

  • 在配置文件配置限流參數(shù)

    blackIP:
      # ip 連續(xù)請求的次數(shù)
      continue-counts: ${counts:3}
      # ip 判斷的時間間隔,單位:秒
      time-interval: ${interval:20}
      # 限制的時間,單位:秒
      limit-time: ${time:30}
    
  • 編寫全局過濾器類

    package com.ajie.gateway.filter;
    
    import com.ajie.common.enums.ResponseStatusEnum;
    import com.ajie.common.result.GraceJSONResult;
    import com.ajie.common.utils.CollUtils;
    import com.ajie.common.utils.IPUtil;
    import com.ajie.common.utils.JsonUtils;
    import com.ajie.common.utils.RedisUtil;
    import io.netty.handler.codec.http.HttpHeaderNames;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.util.AntPathMatcher;
    import org.springframework.util.MimeTypeUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.nio.charset.StandardCharsets;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Description:
     * @Author: ajie
     */
    @Slf4j
    @Component
    public class IpLimitFilterJwt implements GlobalFilter, Ordered {
    
        @Autowired
        private UrlPathProperties urlPathProperties;
        @Value("${blackIP.continue-counts}")
        private Integer continueCounts;
        @Value("${blackIP.time-interval}")
        private Integer timeInterval;
        @Value("${blackIP.limit-time}")
        private Integer limitTime;
        private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1.獲取當(dāng)前的請求路徑
            String path = exchange.getRequest().getURI().getPath();
    
            // 2.獲得所有的需要限流的url
            List<String> ipLimitUrls = urlPathProperties.getIpLimitUrls();
            // 3.校驗并且排除excludeList
            if (CollUtils.isNotEmpty(ipLimitUrls)) {
                for (String url : ipLimitUrls) {
                    if (antPathMatcher.matchStart(url, path)) {
                        log.warn("IpLimitFilterJwt--url={}", path);
                        // 進行ip限流
                        return doLimit(exchange, chain);
                    }
                }
            }
            // 默認直接放行
            return chain.filter(exchange);
        }
    
        private Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 獲取真實ip
            ServerHttpRequest request = exchange.getRequest();
            String ip = IPUtil.getIP(request);
    
            /**
             * 需求:
             * 判斷ip在20秒內(nèi)請求的次數(shù)是否超過3次
             * 如果超過,則限制訪問30秒
             * 等待30秒以后,才能夠恢復(fù)訪問
             */
            // 正常ip
            String ipRedisKey = "gateway_ip:" + ip;
            // 被攔截的黑名單,如果存在,則表示該ip已經(jīng)被限制訪問
            String ipRedisLimitedKey = "gateway_ip:limit:" + ip;
            long limitLeftTime = RedisUtil.KeyOps.getExpire(ipRedisLimitedKey);
            if (limitLeftTime > 0) {
                return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
            }
            // 在redis中獲得ip的累加次數(shù)
            long requestTimes = RedisUtil.StringOps.incrBy(ipRedisKey, 1);
            // 如果訪問次數(shù)為1,則表明是第一次訪問,在redis設(shè)置倒計時
            if (requestTimes == 1) {
                RedisUtil.KeyOps.expire(ipRedisKey, timeInterval, TimeUnit.SECONDS);
            }
    
            // 如果訪問次數(shù)超過限制的次數(shù),直接將該ip存入限制的redis key,并設(shè)置限制訪問時間
            if (requestTimes > continueCounts) {
                // 設(shè)置該ip需要被限流的時間
                RedisUtil.StringOps.setEx(ipRedisLimitedKey, ip, limitTime, TimeUnit.SECONDS);
                return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
            }
            return chain.filter(exchange);
        }
    
        public Mono<Void> renderErrorMsg(ServerWebExchange exchange, ResponseStatusEnum statusEnum) {
            // 1.獲得response
            ServerHttpResponse response = exchange.getResponse();
            // 2.構(gòu)建jsonResult
            GraceJSONResult jsonResult = GraceJSONResult.exception(statusEnum);
            // 3.修改response的code為500
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            // 4.設(shè)定header類型
            if (!response.getHeaders().containsKey("Content-Type")) {
                response.getHeaders().add(HttpHeaderNames.CONTENT_TYPE.toString(), MimeTypeUtils.APPLICATION_JSON_VALUE);
            }
            // 5.轉(zhuǎn)換json并且向response寫入數(shù)據(jù)
            String jsonStr = JsonUtils.toJsonStr(jsonResult);
            DataBuffer dataBuffer = response.bufferFactory()
                    .wrap(jsonStr.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(dataBuffer));
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }
    

通過Lua+Redis實現(xiàn)

業(yè)務(wù)流程還是和上圖差不多,只不過gateway網(wǎng)關(guān)不用再頻繁和redis進行交互。整個限流邏輯放在redis層,通過Lua代碼嵌套

  • Lua實現(xiàn)限流的代碼

    --[[
    ipRedisLimitedKey:限流的redis key
    ipRedisKey:未被限流的redis key,通過此key計算訪問次數(shù)
    timeInterval:訪問時間間隔,在此時間內(nèi),訪問到指定次數(shù)進行限流
    limitTime:限流的時長
    ]]
    -- 判斷當(dāng)前ip是否已經(jīng)被限流
    if redis.call("ttl", ipRedisLimitedKey) > 0 then
        return 1
    end
    
    -- 如果沒有被限流,就讓當(dāng)前ip在redis中的值累計1
    local requestTimes = redis.call("incrby", ipRedisKey, 1)
    -- 判斷累加后的值
    if requestTimes == 1 then
        -- 如果累加后的值是1,說明是第一次請求,設(shè)置一個時間間隔
        redis.call("expire", ipRedisKey, timeInterval)
        return 0
    elseif requestTimes > continueCounts then
        --  如果累加后的值超過了設(shè)定的閾值,就對當(dāng)前ip進行限流
        redis.call("setex", ipRedisLimitedKey, limitTime, ip)
        return 1
    end
    
  • java代碼實現(xiàn)Lua和redis的整合

    package com.ajie.gateway.filter;
    
    import com.ajie.common.enums.ResponseStatusEnum;
    import com.ajie.common.result.GraceJSONResult;
    import com.ajie.common.utils.CollUtils;
    import com.ajie.common.utils.IPUtil;
    import com.ajie.common.utils.JsonUtils;
    import com.ajie.common.utils.RedisUtil;
    import com.google.common.collect.Lists;
    import io.netty.handler.codec.http.HttpHeaderNames;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.util.AntPathMatcher;
    import org.springframework.util.MimeTypeUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.nio.charset.StandardCharsets;
    import java.util.List;
    
    /**
     * @Description:
     * @Author: ajie
     */
    @Slf4j
    @Component
    public class IpLuaLimitFilterJwt implements GlobalFilter, Ordered {
    
        @Autowired
        private UrlPathProperties urlPathProperties;
        @Value("${blackIP.continue-counts}")
        private Integer continueCounts;
        @Value("${blackIP.time-interval}")
        private Integer timeInterval;
        @Value("${blackIP.limit-time}")
        private Integer limitTime;
        private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1.獲取當(dāng)前的請求路徑
            String path = exchange.getRequest().getURI().getPath();
    
            // 2.獲得所有的需要限流的url
            List<String> ipLimitUrls = urlPathProperties.getIpLimitUrls();
            // 3.校驗并且排除excludeList
            if (CollUtils.isNotEmpty(ipLimitUrls)) {
                for (String url : ipLimitUrls) {
                    if (antPathMatcher.matchStart(url, path)) {
                        log.warn("IpLimitFilterJwt--url={}", path);
                        // 進行ip限流
                        return doLimit(exchange, chain);
                    }
                }
            }
            // 默認直接放行
            return chain.filter(exchange);
        }
    
        private Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 獲取真實ip
            ServerHttpRequest request = exchange.getRequest();
            String ip = IPUtil.getIP(request);
    
            /**
             * 需求:
             * 判斷ip在20秒內(nèi)請求的次數(shù)是否超過3次
             * 如果超過,則限制訪問30秒
             * 等待30秒以后,才能夠恢復(fù)訪問
             */
            // 正常ip
            String ipRedisKey = "gateway_ip:" + ip;
            // 被攔截的黑名單,如果存在,則表示該ip已經(jīng)被限制訪問
            String ipRedisLimitedKey = "gateway_ip:limit:" + ip;
            // 通過redis執(zhí)行l(wèi)ua腳本。返回1代表限流了,返回0代表沒有限流
            String script = "if tonumber(redis.call('ttl', KEYS[2])) > 0 then return 1 end local" +
                    " requestTimes = redis.call('incrby', KEYS[1], 1) if tonumber(requestTimes) == 1 then" +
                    " redis.call('expire', KEYS[1], ARGV[2]) return 0 elseif tonumber(requestTimes)" +
                    " > tonumber(ARGV[1]) then redis.call('setex', KEYS[2], ARGV[3], ARGV[4])" +
                    " return 1 else return 0 end";
            Long result = RedisUtil.Helper.execute(script, Long.class,
                    Lists.newArrayList(ipRedisKey, ipRedisLimitedKey),
                    continueCounts, timeInterval, limitTime, ip);
            if(result == 1){
                return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
            }
            return chain.filter(exchange);
        }
    
        public Mono<Void> renderErrorMsg(ServerWebExchange exchange, ResponseStatusEnum statusEnum) {
            // 1.獲得response
            ServerHttpResponse response = exchange.getResponse();
            // 2.構(gòu)建jsonResult
            GraceJSONResult jsonResult = GraceJSONResult.exception(statusEnum);
            // 3.修改response的code為500
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            // 4.設(shè)定header類型
            if (!response.getHeaders().containsKey("Content-Type")) {
                response.getHeaders().add(HttpHeaderNames.CONTENT_TYPE.toString(), MimeTypeUtils.APPLICATION_JSON_VALUE);
            }
            // 5.轉(zhuǎn)換json并且向response寫入數(shù)據(jù)
            String jsonStr = JsonUtils.toJsonStr(jsonResult);
            DataBuffer dataBuffer = response.bufferFactory()
                    .wrap(jsonStr.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(dataBuffer));
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }
    

注意事項

  • 在編寫lua腳本的時候最好不要一次性寫完去試,因為無法進行調(diào)試,最好進行拆解。

  • 在進行數(shù)字比較時建議加上tonumber()。如果是通過方法傳參進來的一定要加,因為redisTemplate默認會把參數(shù)當(dāng)做字符串傳入

    如果不轉(zhuǎn)數(shù)字就會出現(xiàn)上面的錯誤

  • 最后也是最重要的,lua代碼邏輯一定要對,否則得不到自己想要的結(jié)果需要排查很久

總結(jié) 

到此這篇關(guān)于Redis實現(xiàn)IP限流的2種方式的文章就介紹到這了,更多相關(guān)Redis實現(xiàn)IP限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis數(shù)據(jù)類型之散列類型hash命令學(xué)習(xí)

    Redis數(shù)據(jù)類型之散列類型hash命令學(xué)習(xí)

    這篇文章主要為大家介紹了Redis數(shù)據(jù)類型之散列類型hash命令學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • Redis?使用?List?實現(xiàn)消息隊列的優(yōu)缺點

    Redis?使用?List?實現(xiàn)消息隊列的優(yōu)缺點

    這篇文章主要介紹了Redis?使用?List?實現(xiàn)消息隊列有哪些利弊,小編結(jié)合消息隊列的特點一步步帶大家分析使用?Redis?的?List?作為消息隊列的實現(xiàn)原理,并分享如何把?SpringBoot?與?Redission?整合運用到項目中,需要的朋友可以參考下
    2022-01-01
  • 淺談Redis阻塞的9種情況

    淺談Redis阻塞的9種情況

    本文主要介紹了淺談Redis阻塞的9種情況,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Redis中管道操作pipeline的實現(xiàn)

    Redis中管道操作pipeline的實現(xiàn)

    RedisPipeline是一種優(yōu)化客戶端與服務(wù)器通信的技術(shù),通過批量發(fā)送和接收命令減少網(wǎng)絡(luò)往返次數(shù),提高命令執(zhí)行效率,本文就來介紹一下Redis中管道操作pipeline的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2025-03-03
  • Redis精確去重計數(shù)方法(咆哮位圖)

    Redis精確去重計數(shù)方法(咆哮位圖)

    這篇文章主要給大家介紹了關(guān)于Redis精確去重計數(shù)方法(咆哮位圖)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Win10下通過Ubuntu安裝Redis的過程

    Win10下通過Ubuntu安裝Redis的過程

    這篇文章主要介紹了Win10下通過Ubuntu安裝Redis,在安裝Ubuntu需要先打開Windows功能,接著創(chuàng)建一個用戶及密碼,本文給大家介紹的非常詳細,需要的朋友可以參考下
    2022-04-04
  • Redis開啟鍵空間通知實現(xiàn)超時通知的步驟詳解

    Redis開啟鍵空間通知實現(xiàn)超時通知的步驟詳解

    這篇文章主要介紹了Redis開啟鍵空間通知實現(xiàn)超時通知的步驟,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • redis鍵值出現(xiàn)\xac\xed\x00\x05t\x00&的問題及解決

    redis鍵值出現(xiàn)\xac\xed\x00\x05t\x00&的問題及解決

    這篇文章主要介紹了redis鍵值出現(xiàn)\xac\xed\x00\x05t\x00&的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • redis適合場景八點總結(jié)

    redis適合場景八點總結(jié)

    在本篇文章中我們給大家整理了關(guān)于redis適合什么場景的8點知識點內(nèi)容,需要的朋友們參考下。
    2019-06-06
  • Redis中的慢日志

    Redis中的慢日志

    這篇文章主要介紹了Redis中的慢日志,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12

最新評論