LUA脚本语言是C开发的,类似存储过程,是为了实现完整的原子性操作,可以用来补充redis弱事务的缺点.
1、LUA脚本的好处
2、Lua脚本限流实战
支持分布式
java
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
/**
* 分布式限流的服务类
*/
@Service
public class IsAcquire {
//引入一个Redis的Lua脚本的支持
private DefaultRedisScript<Long> getRedisScript;
//判断限流方法---类似于RateLimiter
public boolean acquire(String limitKey,int limit,int expire) throws Exception{
//连接Redis
Jedis jedis = new Jedis("127.0.0.1",6379);
getRedisScript =new DefaultRedisScript<>();
getRedisScript.setResultType(Long.class);//脚本执行返回值 long
getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
Long result = (Long)jedis.eval(getRedisScript.getScriptAsString(),
1,limitKey,String.valueOf(limit),String.valueOf(expire));
if(result ==0){
return false;
}
return true;
}
}
java
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 类说明:接口类,抢购接口
* http请求控制类 Contoller
*/
@RestController
public class Controller {
@Autowired
IsAcquire isAcquire;//手下的分布式限流
//final RateLimiter rateLimiter = RateLimiter.create(5); //guava引入的令牌桶限流(非分布式,单机)
//秒杀接口
@RequestMapping("/order")
public String killProduct(@RequestParam(required = true) String name) throws Exception{
//rateLimiter.tryAcquire(1); //调用
if(isAcquire.acquire("iphone",10,60)){//60秒只能进行10次
System.out.println("业务成功!");
return "恭喜("+name+"),抢到iphone!";
}else{
System.out.println("-----------业务被限流");
return "对不起,你被限流了!";
}
}
}
rateLimiter.lua脚本如下:
java
--java端送入三个参数(1个key,2个param )string
--limitKey(redi中key的值)
local key =KEYS[1];
--limit(次数)
local times = ARGV[1];
--expire(秒S)
local expire = ARGV[2];
--对key-value中的 value +1的操作 返回一个结果
local afterval= redis.call('incr',key);
if afterval ==1 then --第一次
redis.call('expire',key,tonumber(expire) ) --失效时间(1S) TLL 1S
return 1; --第一次不会进行限制
end
--不是第一次,进行判断
if afterval > tonumber(times) then
--限制了
return 0;
end
return 1;
以上简单的计数器的方式,就是一种固定窗口的算法,可以实现对单个接口的限流.
3、限流算法
固定窗口算法的问题
解决方案:改为滑动窗口,避免固定窗口的临界问题
滑动窗口演示地址:
java
https://media.pearsoncmg.com/aw/ecs_kurose_compnetwork_7/cw/content/interactiveanimations/selective-repeat-protocol/index.html
漏桶算法
漏桶,令牌的算法的缺点就是时间复杂度:O(N) ,不适用于大并发的请求
滑动窗口的缺点是需要双方先定好协议