使用redis-list類型 限制用戶1分鍾內訪問次數為100次


 

1、實現邏輯

記錄用戶每次的訪問時間,因此對於每個用戶,用列表類型的鍵記錄他最近100次訪問的時間。
如果鍵中的元素超過100個,就判斷時間最早的元素距離現在的時間是否小於1分鍾,如果是,則表示用戶最近1分鍾的訪問次數超過100次,如果不是就將當前時間加入列表中,同時把最早的元素刪除

 

2、LUA腳本

使用lua腳本實現,保證多個操作的“原子性”

參數說明:

KEYS[1] 傳入表示用戶唯一標識的鍵

ARGV[1] 傳入限制的訪問次數

lua腳本如下:

 

local len = redis.call('llen',KEYS[1]);
redis.replicate_commands(); -- 防止隨機寫入
local now = redis.call('TIME')[1]; -- 當前系統時間,單位是秒
if len < tonumber(ARGV[1]) then 
   redis.call('lpush',KEYS[1],now);
   return 0;
else
 local lasttime = redis.call('lindex',KEYS[1],-1); -- 取最后一個元素
 if now - lasttime < 60 then  -- 訪問頻率超過限制 ,這里的60是指60秒
   return -1; 
 else -- 訪問頻率未超出限制
  redis.call('lpush',KEYS[1],now); 
  redis.call('ltrim',KEYS[1],0,tonumber(ARGV[1])-1); -- 刪除索引在[0,訪問次數-1]以外的元素
  return 0;
 end;
end;

 

list記錄了用戶的訪問時間,list長度為訪問次數。使用此方式進行次數限制,不適用於訪問次數較大的場景,會占用較多內存

 

通過eval命令執行以上腳本

 redis-cli -p 7001 -a 123456 -c  --eval "speed.lua" "harara" , 3

 speed.lua是lua腳本路徑,"harara"是參數keys ,3是參數argv

 注意:參數key和參數argv中間的逗號兩邊都要有空格!!!

 如下截圖:限制次數為3次,當在一分鍾之內連續執行3次命令之后,執行第四次返回 -1 , 表示超出訪問次數限制

 

 

 

3、代碼實現(java)

調用controlSpeed方法實現控制訪問次數,返回true表示未超出次數限制

package com.harara.redis.rate; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * 使用redis-list類型 限制用戶1分鍾內訪問次數為100次 * @author : harara * @version : 1.0 * @date : 2021/2/26 9:58 */ @Service @Slf4j public class ListControlSpeed { @Autowired private RedisTemplate redisTemplate; /** * 控速處理 * @param account 用戶賬號 * @param limit 限制次數 * @return true表示未超出限制可繼續推送 false表示超出限制不可繼續推送 */
    public boolean controlSpeed(String account,int limit){ String keyParam = "LIMIT:"+account; // 指定 lua 腳本
        String luaScript = "local len = redis.call('llen',KEYS[1]);" +
                "redis.replicate_commands();" +
                "local now = redis.call('time')[1];" +
                "if len < tonumber(ARGV[1]) then" +
                "   redis.call('lpush',KEYS[1],now);" +
                "   return 0;" +
                "else" +
                " local lasttime = redis.call('lindex',KEYS[1],-1);" +
                " if now - lasttime < 60 then" +
                "   return -1;" +
                " else" +
                "  redis.call('lpush',KEYS[1],now);" +
                "  redis.call('ltrim',KEYS[1],0,tonumber(ARGV[1])-1);" +
                "  return 0;" +
                " end;" +
                "end;"; try{ // 參數一:redisScript,參數二:key列表,參數三:arg(1、限制條數)
            Object result = executeLua(luaScript,Collections.singletonList(keyParam),String.valueOf(limit)); //返回0表示未超過限制的條數 可繼續發送
            if((long)result == 0) { return true; } }catch (Exception e){ log.error("對用戶賬號{}進行控速處理出現異常",account,e); return false; } return false; } /** * 執行lua腳本 * @param luaScript lua腳本 * @param keyParams lua腳本中KEYS參數 * @param argvParams lua腳本中ARGV參數 */
    public Object executeLua(String luaScript, List<String> keyParams, String... argvParams){ DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript,Long.class); // 參數一:redisScript,參數二:key列表,參數三:arg(可多個) // 注釋掉,spring自帶的執行腳本方法中,集群模式直接拋出不支持執行腳本異常(報錯EvalSha is not supported in cluster environment),只支持單節點不支持集群 //Object result = redisTemplate.execute(redisScript, keyParams,argvParams); //spring自帶的執行腳本方法中,集群模式直接拋出不支持執行腳本異常,此處拿到原redis的connection執行腳本
        Object result = redisTemplate.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { Object nativeConnection = connection.getNativeConnection(); // 集群模式和單點模式雖然執行腳本的方法一樣,但是沒有共同的接口,所以只能分開執行 // 集群
                if (nativeConnection instanceof JedisCluster) { return ((JedisCluster) nativeConnection).eval(luaScript, keyParams, Arrays.asList(argvParams)); } // 單點
                else if (nativeConnection instanceof Jedis) { return ((Jedis) nativeConnection).eval(luaScript, keyParams,Arrays.asList(argvParams)); } return null; } }); return result; } }

 

 

 

參考地址:

1、Redis實現訪問控制頻率

2、RedisTemplate執行lua腳本,集群模式下報錯解決

3、redis獲取當前時間精確到微秒

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM