/// <summary> /// evaluate lua script /// </summary> /// <param name="luaScript"></param> /// <param name="keys"></param> /// <param name="values"></param> /// <returns></returns> protected RedisResult EvaluateScript(RedisLuaScript luaScript, RedisKey[] keys, RedisValue[] values) { byte[] sha1 = luaScript.Load(); IDatabase dataBase = _redisClient.GetDatabase(); return(dataBase.ScriptEvaluate(sha1, keys, values)); }
/// <summary> /// async evaluate lua script /// </summary> /// <param name="luaScript"></param> /// <param name="keys"></param> /// <param name="values"></param> /// <returns></returns> protected async Task <RedisResult> EvaluateScriptAsync(RedisLuaScript luaScript, RedisKey[] keys, RedisValue[] values) { byte[] sha1 = await luaScript.LoadAsync(); IDatabase dataBase = _redisClient.GetDatabase(); return(await dataBase.ScriptEvaluateAsync(sha1, keys, values)); }
/// <summary> /// create a new instance /// </summary> /// <param name="rules">The rate limit rules</param> /// <param name="redisClient">The redis client</param> /// <param name="timeProvider">The time provider</param> /// <param name="updatable">If rules can be updated</param> public RedisFixedWindowAlgorithm(IEnumerable <FixedWindowRule> rules, ConnectionMultiplexer redisClient = null, ITimeProvider timeProvider = null, bool updatable = false) : base(rules, redisClient, timeProvider, updatable) { _fixedWindowIncrementLuaScript = new RedisLuaScript(_redisClient, "Src-IncrWithExpireSec", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 return ret; end ret[1]=0 local amount=tonumber(ARGV[1]) local limit_number=tonumber(ARGV[3]) local lock_seconds=tonumber(ARGV[4]) local check_result=false local current=redis.call('get',KEYS[1]) if current~=false then current = tonumber(current) if(limit_number>=0 and current>=limit_number) then check_result=true else redis.call('incrby',KEYS[1],amount) current=current+amount end else redis.call('set',KEYS[1],amount,'PX',ARGV[2]) current=amount end ret[2]=current if check_result then ret[1]=1 if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end end return ret"); }
/// <summary> /// /// </summary> /// <param name="rules"></param> /// <param name="redisClient"></param> /// <param name="timeProvider"></param> /// <param name="updatable"></param> public RedisSlidingWindowAlgorithm(IEnumerable <SlidingWindowRule> rules, ConnectionMultiplexer redisClient = null, ITimeProvider timeProvider = null, bool updatable = false) : base(rules, redisClient, timeProvider, updatable) { _slidingWindowIncrementLuaScript = new RedisLuaScript(_redisClient, "Src-IncrWithExpireSec", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 return ret; end ret[1]=0 local st_key=KEYS[1] .. '-st' local amount=tonumber(ARGV[1]) local period_expire_ms=tonumber(ARGV[2]) local period_ms=tonumber(ARGV[3]) local period_number=tonumber(ARGV[4]) local current_time=tonumber(ARGV[5]) local cal_start_time=tonumber(ARGV[6]) local limit_number=tonumber(ARGV[7]) local lock_seconds=tonumber(ARGV[8]) local current_period local current_period_key local start_time=redis.call('get',st_key) if(start_time==false) then start_time=cal_start_time current_period=start_time+period_ms-1 current_period_key=KEYS[1] .. '-' .. current_period redis.call('set',st_key,start_time) redis.call('set',current_period_key,amount,'PX',period_expire_ms) ret[2]=amount return ret end start_time=tonumber(start_time) local past_ms=current_time-start_time local past_period_number=past_ms/period_ms local past_period_number_floor=math.floor(past_period_number) local past_period_number_ceil=math.ceil(past_period_number) local past_period_number_fixed=past_period_number_floor if (past_period_number_ceil > past_period_number_floor) then past_period_number_fixed = past_period_number_ceil end if past_period_number_fixed==0 then past_period_number_fixed=1 end current_period=start_time + past_period_number_fixed * period_ms - 1 current_period_key=KEYS[1] .. '-' .. current_period local periods={current_period_key} for i=1,period_number-1,1 do periods[i+1] = KEYS[1] .. '-' .. (current_period - period_ms * i) end local periods_amount=0 local periods_amount_array=redis.call('mget',unpack(periods)) for key,value in ipairs(periods_amount_array) do if(value~=false) then periods_amount=periods_amount+value end end ret[2]=amount+periods_amount if (limit_number>=0 and ret[2]>limit_number) then if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end ret[1]=1 ret[2]=periods_amount return ret end local current_amount current_amount = redis.call('incrby',current_period_key,amount) current_amount = tonumber(current_amount) if current_amount == amount then redis.call('PEXPIRE',current_period_key,period_expire_ms) end return ret"); }
/// <summary> /// create a new instance /// </summary> /// <param name="rules">The rate limit rules</param> /// <param name="redisClient">The redis client</param> /// <param name="timeProvider">The time provider</param> /// <param name="updatable">If rules can be updated</param> public RedisTokenBucketAlgorithm(IEnumerable <TokenBucketRule> rules, ConnectionMultiplexer redisClient = null, ITimeProvider timeProvider = null, bool updatable = false) : base(rules, redisClient, timeProvider, updatable) { _tokenBucketDecrementLuaScript = new RedisLuaScript(_redisClient, "Src-DecrWithTokenBucket", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 return ret; end ret[1]=0 local st_key= KEYS[1] .. '-st' local amount=tonumber(ARGV[1]) local capacity=tonumber(ARGV[2]) local inflow_unit=tonumber(ARGV[3]) local inflow_quantity_per_unit=tonumber(ARGV[4]) local current_time=tonumber(ARGV[5]) local start_time=tonumber(ARGV[6]) local lock_seconds=tonumber(ARGV[7]) local bucket_amount=0 local last_time=redis.call('get',st_key) if(last_time==false) then bucket_amount = capacity - amount; redis.call('mset',KEYS[1],bucket_amount,st_key,start_time) ret[2]=bucket_amount return ret end local current_value = redis.call('get',KEYS[1]) current_value = tonumber(current_value) last_time=tonumber(last_time) local last_time_changed=0 local past_time=current_time-last_time if(past_time<inflow_unit) then bucket_amount=current_value-amount else local past_inflow_unit_quantity = past_time/inflow_unit past_inflow_unit_quantity=math.floor(past_inflow_unit_quantity) last_time=last_time+past_inflow_unit_quantity*inflow_unit last_time_changed=1 local past_inflow_quantity=past_inflow_unit_quantity*inflow_quantity_per_unit bucket_amount=current_value+past_inflow_quantity-amount end if(bucket_amount>=capacity) then bucket_amount=capacity-amount end ret[2]=bucket_amount if(bucket_amount<0) then if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end ret[1]=1 return ret end if last_time_changed==1 then redis.call('mset',KEYS[1],bucket_amount,st_key,last_time) else redis.call('set',KEYS[1],bucket_amount) end return ret"); }
/// <summary> /// create a new instance /// </summary> /// <param name="rules">The rate limit rules</param> /// <param name="redisClient">The redis client</param> /// <param name="timeProvider">The time provider</param> /// <param name="updatable">If rules can be updated</param> public RedisLeakyBucketAlgorithm(IEnumerable <LeakyBucketRule> rules, ConnectionMultiplexer redisClient = null, ITimeProvider timeProvider = null, bool updatable = false) : base(rules, redisClient, timeProvider, updatable) { _leakyBucketIncrementLuaScript = new RedisLuaScript(_redisClient, "Src-IncrWithLeakyBucket", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 ret[3]=-1 return ret; end ret[1]=0 local st_key= KEYS[1] .. '-st' local amount=tonumber(ARGV[1]) local capacity=tonumber(ARGV[2]) local outflow_unit=tonumber(ARGV[3]) local outflow_quantity_per_unit=tonumber(ARGV[4]) local current_time=tonumber(ARGV[5]) local start_time=tonumber(ARGV[6]) local lock_seconds=tonumber(ARGV[7]) local last_time=redis.call('get',st_key) if(last_time==false) then redis.call('mset',KEYS[1],amount,st_key,start_time) ret[2]=0 ret[3]=0 return ret end local current_value = redis.call('get',KEYS[1]) current_value = tonumber(current_value) last_time=tonumber(last_time) local past_time=current_time-last_time local last_time_changed=0 local wait=0 if(past_time<outflow_unit) then current_value=current_value+amount if(current_value<=capacity+outflow_quantity_per_unit) then local current_unit_rest_time = outflow_unit - past_time if(current_value>outflow_quantity_per_unit) then local batch_number = math.ceil(current_value/outflow_quantity_per_unit) - 1 if (batch_number == 1) then wait = current_unit_rest_time; else wait = outflow_unit * (batch_number - 1) + current_unit_rest_time; end end else if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end ret[1]=1 ret[2]=capacity ret[3]=-1 return ret end else local past_outflow_unit_quantity = math.floor(past_time/outflow_unit) last_time=last_time+past_outflow_unit_quantity*outflow_unit last_time_changed=1 if (current_value < outflow_quantity_per_unit) then current_value = amount wait = 0 else local past_outflow_quantity=past_outflow_unit_quantity*outflow_quantity_per_unit local new_value=current_value-past_outflow_quantity+amount if(new_value<=0) then current_value=amount else current_value=new_value end local current_unit_rest_time = outflow_unit - (current_time - last_time) if(current_value>outflow_quantity_per_unit) then local batch_number = math.ceil(current_value/outflow_quantity_per_unit) - 1 if (batch_number == 1) then wait = current_unit_rest_time; else wait = outflow_unit * (batch_number - 1) + current_unit_rest_time; end end end end if last_time_changed==1 then redis.call('mset',KEYS[1],current_value,st_key,last_time) else redis.call('set',KEYS[1],current_value) end local view_count = current_value - outflow_quantity_per_unit; if(view_count<0) then view_count=0 end ret[2]=view_count ret[3]=wait return ret"); }
/// <summary> /// Create a new instance /// </summary> /// <param name="redisClient"></param> public RedisStorage(ConnectionMultiplexer redisClient) { _redisClient = redisClient; _fixedWindowIncrementLuaScript = new RedisLuaScript(_redisClient, "Src-IncrWithExpireSec", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 return ret; end ret[1]=0 local amount=tonumber(ARGV[1]) local limit_number=tonumber(ARGV[3]) local lock_seconds=tonumber(ARGV[4]) local check_result=false local current current = redis.call('get',KEYS[1]) if current~=false then current = tonumber(current) if current>=limit_number then check_result=true else redis.call('incrby',KEYS[1],amount) end else redis.call('set',KEYS[1],amount,'PX',ARGV[2]) current=amount end ret[2]=current if check_result then ret[1]=1 if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end end return ret"); _slidingWindowIncrementLuaScript = new RedisLuaScript(_redisClient, "Src-IncrWithExpireSec", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 return ret; end ret[1]=0 local st_key=KEYS[1] .. '-st' local amount=tonumber(ARGV[1]) local period_expire_ms=tonumber(ARGV[2])*2 local period_ms=tonumber(ARGV[3]) local period_number=tonumber(ARGV[4]) local current_time=tonumber(ARGV[5]) local limit_number=tonumber(ARGV[6]) local lock_seconds=tonumber(ARGV[7]) local current_period local current_period_key local start_time=redis.call('get',st_key) if(start_time==false) then start_time=current_time current_period=start_time+period_ms-1 current_period_key=KEYS[1] .. '-' .. current_period redis.call('set',st_key,start_time) redis.call('set',current_period_key,amount,'PX',period_expire_ms) ret[2]=amount return ret end start_time=tonumber(start_time) local past_ms=current_time-start_time local past_period_number=past_ms/period_ms local past_period_number_floor=math.floor(past_period_number) local past_period_number_ceiling=math.ceil(past_period_number) local past_period_number_fixed=past_period_number_floor if (past_period_number_ceiling > past_period_number_floor) then past_period_number_fixed = past_period_number_ceiling end if past_period_number_fixed==0 then past_period_number_fixed=1 end current_period=start_time + past_period_number_fixed * period_ms - 1 current_period_key=KEYS[1] .. '-' .. current_period local periods={current_period_key} for i=1,period_number-1,1 do periods[i+1] = KEYS[1] .. '-' .. (current_period - period_ms * i) end local periods_amount=0 local periods_amount_array=redis.call('mget',unpack(periods)) for key,value in ipairs(periods_amount_array) do if(value~=false) then periods_amount=periods_amount+value end end ret[2]=amount+periods_amount if ret[2] > limit_number then if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end ret[1]=1 return ret end local current_amount current_amount = redis.call('incrby',current_period_key,amount) current_amount = tonumber(current_amount) if current_amount == amount then redis.call('PEXPIRE',current_period_key,period_expire_ms) end return ret"); _leakyBucketIncrementLuaScript = new RedisLuaScript(_redisClient, "Src-IncrWithLeakyBucket", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 return ret; end ret[1]=0 local st_key= KEYS[1] .. '-st' local amount=tonumber(ARGV[1]) local capacity=tonumber(ARGV[2]) local outflow_unit=tonumber(ARGV[3]) local outflow_quantity_per_unit=tonumber(ARGV[4]) local current_time=tonumber(ARGV[5]) local lock_seconds=tonumber(ARGV[6]) local last_time last_time=redis.call('get',st_key) if(last_time==false) then redis.call('mset',KEYS[1],amount,st_key,current_time) ret[2]=amount return ret end local current_value = redis.call('get',KEYS[1]) current_value = tonumber(current_value) last_time=tonumber(last_time) local past_time=current_time-last_time local last_time_changed=0 if(past_time<outflow_unit) then current_value=current_value+amount else local past_outflow_unit_quantity = past_time/outflow_unit past_outflow_unit_quantity=math.floor(past_outflow_unit_quantity) last_time=last_time+past_outflow_unit_quantity*outflow_unit last_time_changed=1 local past_outflow_quantity=past_outflow_unit_quantity*outflow_quantity_per_unit local new_value=current_value-past_outflow_quantity+amount if(new_value<=0) then current_value=amount else current_value=new_value end end ret[2]=current_value if(current_value>capacity) then if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end ret[1]=1 return ret end if last_time_changed==1 then redis.call('mset',KEYS[1],current_value,st_key,last_time) else redis.call('set',KEYS[1],current_value) end return ret"); _tokenBucketDecrementLuaScript = new RedisLuaScript(_redisClient, "Src-DecrWithTokenBucket", @"local ret={} local lock_key=KEYS[1] .. '-lock' local lock_val=redis.call('get',lock_key) if lock_val == '1' then ret[1]=1 ret[2]=-1 return ret; end ret[1]=0 local st_key= KEYS[1] .. '-st' local amount=tonumber(ARGV[1]) local capacity=tonumber(ARGV[2]) local inflow_unit=tonumber(ARGV[3]) local inflow_quantity_per_unit=tonumber(ARGV[4]) local current_time=tonumber(ARGV[5]) local lock_seconds=tonumber(ARGV[6]) local bucket_amount=0 local last_time=redis.call('get',st_key) if(last_time==false) then bucket_amount = capacity - amount; redis.call('mset',KEYS[1],bucket_amount,st_key,current_time) ret[2]=bucket_amount return ret end local current_value = redis.call('get',KEYS[1]) current_value = tonumber(current_value) last_time=tonumber(last_time) local last_time_changed=0 local past_time=current_time-last_time if(past_time<inflow_unit) then bucket_amount=current_value-amount else local past_inflow_unit_quantity = past_time/inflow_unit past_inflow_unit_quantity=math.floor(past_inflow_unit_quantity) last_time=last_time+past_inflow_unit_quantity*inflow_unit last_time_changed=1 local past_inflow_quantity=past_inflow_unit_quantity*inflow_quantity_per_unit bucket_amount=current_value+past_inflow_quantity-amount end if(bucket_amount>=capacity) then bucket_amount=capacity-amount end ret[2]=bucket_amount if(bucket_amount<0) then if lock_seconds>0 then redis.call('set',lock_key,'1','EX',lock_seconds,'NX') end ret[1]=1 return ret end if last_time_changed==1 then redis.call('mset',KEYS[1],bucket_amount,st_key,last_time) else redis.call('set',KEYS[1],bucket_amount) end return ret"); }