专业的编程技术博客社区

网站首页 > 博客文章 正文

几个常见的秒杀系统流控实现方案选型?

baijin 2024-08-20 10:23:54 博客文章 6 ℃ 0 评论

秒杀系统限流,听上去有点反人类!既然是秒杀系统为什么要限流呢?秒杀系统限流操作是为了解决高并发场景中由于秒杀活动中,用户请求量大导致的服务器过载、崩溃,甚至有是有因为业务逻辑处理不及时导致的数据不一致的问题。为了避免这些问题,限流就成了保证秒杀系统正常使用必不可少的环节,下面就来看看,在秒杀系统中进行限流的常见的解决方案。

令牌桶算法

根据系统的处理能力预先生成一定数量的令牌,用户每次请求需要获取一个令牌,如果令牌池中没有足够的令牌,则拒绝请求。令牌桶算法可以通过限制令牌的生成速率来控制系统的请求速率,适用于平滑限流。

常见的令牌桶算法可以选择Guava RateLimiter、Redisson RateLimiter 等。具体实现如下所示。

// 使用 Guava RateLimiter 实现令牌桶算法
RateLimiter limiter = RateLimiter.create(100); // 每秒产生 100 个令牌
public void seckill() {
    if (limiter.tryAcquire()) {
        // 处理秒杀请求
    } else {
        // 返回秒杀失败信息
    }
}

漏桶算法

类似于一个漏斗,固定速率的请求会被放入漏斗中,超出的请求会被丢弃或等待下一次处理。漏桶算法适用于平稳流量和突发流量的限流场景。

常见的漏桶算法可以使用自定义实现、或者是使用Guava RateLimiter。

// 使用自定义漏桶算法实现
private final BlockingQueue<Object> bucket = new ArrayBlockingQueue<>(100); // 漏桶容量为 100
public void seckill() {
    if (bucket.offer(new Object())) {
        // 处理秒杀请求
    } else {
        // 返回秒杀失败信息
    }
}

基于队列的流控

基于队列实现流控是在秒杀系统、或者是在大促场景下经常使用的一种方案。通过队列对请求进行排队和调度,限制队列的长度或排队等待时间来控制系统的请求量。队列的长度超过一定阈值时,拒绝新的请求加入队列。来实现对于进入系统流量的控制。

常见的队列可以使用LinkedBlockingQueue、DelayQueue 等,当然还可以使用一些高级的消息中间件来实现。

// 使用 LinkedBlockingQueue 实现队列流控
private final BlockingQueue<Request> queue = new LinkedBlockingQueue<>(100); // 队列容量为 100
public void seckill(Request request) {
    if (queue.offer(request)) {
        // 处理秒杀请求
    } else {
        // 返回秒杀失败信息
    }
}

限制IP访问频率

根据用户的IP地址来限制其访问频率,设置一定的时间窗口内允许的最大访问次数,超过限制则拒绝请求。避免单个IP多次刷新,刷爆秒杀接口。

可以使用Servlet Filter、Spring Interceptor、Guava Cache 等来实现限制,当然还可以已使用Redis来完成IP限流操作。下面是使用 Guava Cache 实现 IP 访问频率限制实现。

// 使用 Guava Cache 实现 IP 访问频率限制
private static final Cache<String, AtomicInteger> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(1, TimeUnit.SECONDS) // 设置过期时间为 1 秒
        .build();
public void seckill(HttpServletRequest request) {
    String ip = request.getRemoteAddr();
    AtomicInteger counter = cache.get(ip, () -> new AtomicInteger(0));
    if (counter.incrementAndGet() <= 100) { // 每秒最多处理 100 次请求
        // 处理秒杀请求
    } else {
        // 返回秒杀失败信息
    }
}

使用Redis实现IP限流操作?

基于Redis实现IP访问率限制的流控操作可以通过Redis的计数器和过期时间来实现。代码如下所示。

public class IPAccessRateLimiter {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final int MAX_REQUESTS_PER_SECOND = 100; // 每秒最大请求次数

    private Jedis jedis;

    public IPAccessRateLimiter() {
        jedis = new Jedis(REDIS_HOST, REDIS_PORT);
    }

    // 判断 IP 是否允许访问
    public boolean allowAccess(String ip) {
        String key = "rate_limit:" + ip;
        long count = jedis.incr(key); // 计数器自增
        jedis.expire(key, 1); // 设置过期时间为 1 秒
        return count <= MAX_REQUESTS_PER_SECOND; // 判断访问次数是否超过限制
    }

    public static void main(String[] args) {
        IPAccessRateLimiter limiter = new IPAccessRateLimiter();
        for (int i = 0; i < 150; i++) {
            String ip = "192.168.1.1"; // 模拟 IP 地址
            boolean allowed = limiter.allowAccess(ip);
            System.out.println("IP " + ip + " access allowed: " + allowed);
        }
    }
}

在上面的示例中,我们使用Redis中的计数器来记录每个IP地址的访问次数,然后设置了过期时间为1秒,即每秒钟重置一次访问次数。通过比较当前的访问次数与最大请求次数,判断是否允许访问。如果访问次数超过了限制,则返回false,否则返回true

将上述的逻辑判断加入到Spring Boot拦截器或者是过滤器中,就可以实现接口的限流操作。

在实际使用场景中,需要根据系统的访问流量,操作业务等选择合适的流控方案。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表