滑动时间窗口实现简单接口限流思路

标题本文目的

在学习阿里开源框架sentinel后,为加深对滑动时间窗口的理解,故自己实现简单接口限流。

标题Sentinel

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel的一切功能都是以流量统计为基础。基于滑动时间窗口实现秒级的流量统计。

标题滑动时间窗口

Sentinel以1秒为时间宽度,将1秒平均分隔成指定数量的时间窗口,任意的时间对应唯一的一个时间窗口,每个时间窗口有一个开始时间。在每个时间窗口内统计当前时间窗口的流量。因为时间窗口比较小的,所以避免了大量的统计并发冲突,提高统计性能。当要统计1秒内的流量时,只需要统计当前时间窗口前的n(1秒除以时间窗口的宽度)个时间窗口的数据。

标题简单实现

  1. 创建简单的springboot工程,添加测试接口
@RestController
public class TestController {
 
    @GetMapping("/hello")
    public String hello() {
        return "hello world";
    }
}

标题2. 时间窗口类 Window

@Data
public class Window {
 
    private String url;
 
    private long time;
 
    private AtomicInteger count;
 
}

标题3. 时间窗口统计类 Statistics

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class Statistics {
 
    // 时间窗口宽度
    private long WINDOW_PERIOD = 500l;
 
 
 
    // 统计时长,单位:秒
    private long STATICTIS_PERIOD = 1;
 
    // 时间窗口数组,保存所有时间窗口,数组长度由统计时长与时间窗口宽度共同决定, 需要能整除
    private AtomicReferenceArray<Window> array = new AtomicReferenceArray(STATICTIS_PERIOD * 1000 / WINDOW_PERIOD);
 
    private Lock lock = new ReentrantLock();
 
    /**
     * 根据当前时间获取时间窗口
    */
    public Window getWindow(long time) {
        // 1、根据当前时间,算出该时间的timeId,timeId就是在整个时间轴的位置
        long timeId = time / WINDOW_PERIOD;
        // 2、据timeId算出当前时间窗口在采样窗口区间中的索引idx
        int idx = (int)(timeId % array.length());
 
        // 3、根据当前时间算出当前窗口应该对应的窗口开始时间time,以毫秒为单位
        time = time - time % WINDOW_PERIOD;
 
        // 循环判断直到获取到一个当前时间窗口
        while(true) {
 
            // 获取时间窗口数组中idx位置的时间窗口
            Window oldWindow = array.get(idx);
            // 如果时间窗口不存在,则新建一个时间窗口并添加到时间窗口数组中
            if (oldWindow == null) {
                Window window = new Window();
                window.setTime(time);
                window.setCount(new AtomicInteger(0));
                array.compareAndSet(idx, null, window);
                return window;
            // 时间窗口的时间等于当前时间,则直接返回该时间窗口
            } else if (time == oldWindow.getTime()) {
                return oldWindow;
            // 当前时间大于该时间窗口,则表明该时间窗口已过期,重置时间窗口
            } else if (time > oldWindow.getTime()) {
                if (lock.tryLock()) {
                    try {
                        oldWindow.setTime(time);
                        oldWindow.getCount().set(0);
                        return oldWindow;
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
 
    }
 
    // 统计指定时间内的流量数据
    public int count(long time, long period) {
        int count = 0;
        for(int i = 0; i < array.length(); i++) {
            Window window = array.get(i);
            if (null != window && time - period <= window.getTime()) {
                count += window.getCount().get();
            }
        }
        return count;
    }
}

4## 标题. 限流过滤器

import com.example.demo.timewindow.Statistics;
import com.example.demo.timewindow.Window;
 
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
 
public class CurrentLimitingFilter implements Filter {
 
    Statistics statistics = new Statistics();
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        long time = System.currentTimeMillis();
        int count = statistics.count(time, 1000);
        if (count <= 15) {
            Window window = statistics.getWindow(time);
            window.getCount().getAndIncrement();
            System.out.println(time / 1000 + "-" + (time % 1000) + "requst url : " + request.getRequestURI() + "-" + count);
        } else {
            System.out.println(time / 1000 + "-" + (time % 1000) + "request url blocked" + "-" + count);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

5.## 标题 配置过滤器

import com.example.demo.filter.CurrentLimitingFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class FilterConfig {
 
    @Bean
    public FilterRegistrationBean registerAuthFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CurrentLimitingFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(1);  //值越小,Filter越靠前。
        return registration;
    }
}

标题测试

使用jmeter模拟并发请求
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lua可以通过使用Redis的有序集合(sorted set)和Lua脚本来实现滑动窗口限流。下面是一个示例的Lua脚本实现滑动窗口限流的方法: ```lua -- 设置滑动窗口的大小和时间间隔 local windowSize = 5 local timeInterval = 1 -- 获取当前时间戳 local currentTime = tonumber(redis.call('TIME')[1]) -- 计算窗口的起始时间 local windowStart = currentTime - timeInterval -- 移除窗口时间范围之外的元素 redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, windowStart) -- 获取当前窗口内的请求数量 local currentCount = redis.call('ZCARD', KEYS[1]) -- 判断当前请求数量是否超过限制 if currentCount >= windowSize then return 0 else -- 添加当前请求到有序集合中,并设置当前时间作为分数 redis.call('ZADD', KEYS[1], currentTime, currentTime) -- 设置有序集合的过期时间窗口时间间隔的两倍,确保窗口内的数据都会被清除 redis.call('EXPIRE', KEYS[1], timeInterval * 2) return 1 end ``` 上述Lua脚本实现了一个滑动窗口限流的逻辑,其中`KEYS`表示Redis的有序集合的键名,可以根据实际情况进行修改。该脚本首先获取当前时间戳,然后计算窗口的起始时间,并移除窗口时间范围之外的元素。接着获取当前窗口内的请求数量,如果超过了限制的大小,则返回0表示限流,否则将当前请求添加到有序集合中,并设置过期时间窗口时间间隔的两倍,确保窗口内的数据都会被清除。 你可以将上述Lua脚本通过Redis的`EVAL`命令执行,示例如下: ```shell EVAL "lua脚本" 1 "有序集合的键名" ``` 请注意将上述示例中的"lua脚本"替换为实际的Lua脚本内容,"有序集合的键名"替换为实际的键名。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值