互联网大厂高并发下商品详情页面Redis+Lua实战

之前我们商品详情页为了应对高并发采用redis进行分布式锁的做法防止缓存穿透,缓存击穿,缓存雪崩并发问题:
Redis缓存穿透,缓存击穿,缓存雪崩实战代码总结

本篇对之前进行优化在应用高并发时,先进行一个限流,防止大量请求打入redis或者Mysql

限流的算法有哪些?

简单介绍 4 种非常好理解并且容易实现的限流算法!

  • 固定窗口计数器算法

规定我们单位时间处理的请求数量。比如我们规定我们的一个接口一分钟只能访问10次的话。使用固定窗口计数器算法的话可以这样实现:给定一个变量counter来记录处理的请求数量,当1分钟之内处理一个请求之后counter+1,1分钟之内的如果counter=100的话,后续的请求就会被全部拒绝。等到 1分钟结束后,将counter回归成0,重新开始计数(ps:只要过了一个周期就讲counter回归成0)。
这种限流算法无法保证限流速率,因而无法保证突然激增的流量。比如我们限制一个接口一分钟只能访问10次的话,前半分钟一个请求没有接收,后半分钟接收了10个请求。

  • 滑动窗口计数器算法

算的上是固定窗口计数器算法的升级版。滑动窗口计数器算法相比于固定窗口计数器算法的优化在于:它把时间以一定比例分片。例如我们的借口限流每分钟处理60个请求,我们可以把 1 分钟分为60个窗口。每隔1秒移动一次,每个窗口一秒只能处理 不大于 60(请求数)/60(窗口数) 的请求, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
很显然:当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

  • 漏桶算法

我们可以把发请求的动作比作成注水到桶中,我们处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。如果想要实现这个算法的话也很简单,准备一个队列用来保存请求,然后我们定期从队列中拿请求来执行就好了。

  • 令牌桶算法

令牌桶算法也比较简单。和漏桶算法算法一样,我们的主角还是桶(这限流算法和桶过不去啊)。不过现在桶里装的是令牌了,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃(删除)。我们根据限流大小,按照一定的速率往桶里添加令牌。

方案一:redis+lua:

 /**
     * 判断是否使用限流代码
     *
     * @return
     * @throws Exception
     */
    public boolean acqiuer() throws Exception {
     
        String luaScript = "local key = KEYS[1] --限流KEY(一秒一个)\n" +
                "local limit = tonumber(ARGV[1]) --限流大小\n" +
                "local current = tonumber(redis.call('get',key) or \"0\")\n" +
                "if current + 1 > limit then --如果超出限流大小\n" +
                "    return 0\n" +
                "else --请求数+1,并设置2秒过期\n" +
                "    redis.call(\"INCRBY\",key,\"1\")\n" +
                "    redis.call(\"expire\",key,\"2\")\n" +
                "    return 1\n" +
                "end";
        System.out.println(luaScript);
        Jedis jedis = new Jedis("localhost", 6379);
        //此处获取当前时间截取秒数
        String key = "ip" + System.currentTimeMillis() / 1000;
        String limit = "3";
        return (Long) jedis.eval(luaScript, Lists.newArrayList(key), Lists.newArrayList(limit)) == 1;
    }
 public PmsProduct list(Long id, String ip) throws Exception {
     
        System.out.println("当前" + ip + "正在访问");
        boolean acqiuer = this.acqiuer();
        System.out.println("启用限流" + acqiuer);

访问系统之前先进行判断是否在固定流量之内

方案二:Nginx+lua

  • 创建文件limit.lua
local locks = require "resty.lock"
local function acquire()
	local lock = locks:new("locks")
	local elapsed,err = lock:lock("limit_key") --互斥锁
	local limit_counter = ngx.shared.limit_counter --计数器
	local key = "ip:" ..os.time()
	local = limit  = 5 --限流大小
	local current = limit_counter:get(key)
if current ~= nil and current + 1> limit then --如果超出限流大小
	lock:unlock()
	return 0
end
if current == nul then
	limit-counter;set(key,1,1) --第一次需要设置过期时间,设置key的值为1
else 
	limit_counter:incr(key,1) --第二次开始加1即可
end
lock:unlock()
return 1
end
ngx.print(acquire())

  • nginx文件配置(定义两个共享字典分别存放锁和计数器)
http{
     
		lua_shared_dict lock 10m;
		lua_shared_dict lintit_counter 10m;
}

你可能感兴趣的:(亿级流量分布式系统实战,BAT大厂面试必问系列,缓存,分布式,java,redis)