返回

高并发秒杀抢购场景系统如何稳定,你需要这个挡箭牌!

开发工具

在高并发场景下,确保系统稳定性的关键策略

在当今互联网时代,高并发场景已变得司空见惯,从双十一购物节到限时抢购活动,这些场景都会给系统带来巨大的压力。若未充分准备,系统崩溃和数据丢失的风险极高。因此,在高并发场景下,保证系统稳定性至关重要。

以下是一些经过业界验证的关键策略,助您应对高并发挑战:

1. 限流:守护系统安全的阀门

限流就像设置了一道阀门,限制同时涌入系统的请求数量,防止其被过载压垮。令牌桶算法和滑动窗口算法是常用的限流方法。

  • 令牌桶算法: 想象一个装着令牌的桶,每个请求代表一个令牌。当请求到来时,从桶中取一个令牌;若桶空了,请求将被拒绝。
  • 滑动窗口算法: 将请求划分为多个时间窗口,并统计每个窗口内的请求数。若某窗口内的请求数超过阈值,则该窗口内的所有请求都将被拒绝。
// 令牌桶算法示例
public class TokenBucket {
    private int capacity;
    private int tokens;
    private long lastUpdateTime;

    public TokenBucket(int capacity) {
        this.capacity = capacity;
        this.tokens = capacity;
        this.lastUpdateTime = System.currentTimeMillis();
    }

    public synchronized boolean getToken() {
        long now = System.currentTimeMillis();
        long elapsedTime = now - lastUpdateTime;
        tokens += (int) (elapsedTime / 1000); // 每秒补充令牌
        tokens = Math.min(capacity, tokens);
        lastUpdateTime = now;

        if (tokens > 0) {
            tokens--;
            return true;
        } else {
            return false;
        }
    }
}

// 滑动窗口算法示例
public class SlidingWindow {
    private int windowSize;
    private int[] requests;
    private int index;

    public SlidingWindow(int windowSize) {
        this.windowSize = windowSize;
        this.requests = new int[windowSize];
        this.index = 0;
    }

    public synchronized boolean checkRequest(int request) {
        requests[index++] = request;
        index %= windowSize;

        int totalRequests = 0;
        for (int i = 0; i < windowSize; i++) {
            totalRequests += requests[i];
        }

        return totalRequests <= windowSize * threshold;
    }
}

2. 降级:当舍弃成为一种艺术

降级是一门艺术,它主动舍弃部分功能或服务,以保障核心功能的正常运行。当系统遇到故障或过载时,降级可以起到缓冲作用。

  • 熔断器: 就好比电路中的熔断器,当某个服务的请求失败率过高时,熔断器会自动将其熔断(停止调用),避免继续消耗资源。当失败率下降后,熔断器会重新打开。
  • 限流降级: 将限流和降级结合起来,根据系统负载情况动态调整限流阈值。当负载高时,降低阈值,减少请求数量;当负载低时,提高阈值,允许更多请求进入。
// 熔断器示例
public class CircuitBreaker {
    private int failureThreshold;
    private int successThreshold;
    private boolean isOpen;
    private long lastOpenTime;

    public CircuitBreaker(int failureThreshold, int successThreshold) {
        this.failureThreshold = failureThreshold;
        this.successThreshold = successThreshold;
        this.isOpen = false;
        this.lastOpenTime = 0;
    }

    public synchronized boolean check(boolean success) {
        if (success) {
            if (isOpen) {
                // 成功次数达到阈值,关闭熔断器
                if (successThreshold <= 0 || successCount >= successThreshold) {
                    isOpen = false;
                    successCount = 0;
                    lastOpenTime = 0;
                }
            } else {
                successCount++;
            }
        } else {
            if (!isOpen) {
                // 失败次数达到阈值,打开熔断器
                if (failureThreshold <= 0 || failureCount >= failureThreshold) {
                    isOpen = true;
                    lastOpenTime = System.currentTimeMillis();
                    failureCount = 0;
                }
            } else {
                failureCount++;
            }
        }

        return !isOpen;
    }
}

// 限流降级示例
public class RateLimitWithFallback {
    private RateLimiter rateLimiter;
    private FallbackService fallbackService;

    public RateLimitWithFallback(RateLimiter rateLimiter, FallbackService fallbackService) {
        this.rateLimiter = rateLimiter;
        this.fallbackService = fallbackService;
    }

    public synchronized Object processRequest() {
        if (rateLimiter.tryAcquire()) {
            // 获得令牌,执行正常流程
            return normalService.process();
        } else {
            // 未获得令牌,降级到备用服务
            return fallbackService.process();
        }
    }
}

3. 容错:当失败不再是选项

容错就像给系统穿上了一件坚硬的盔甲,它让系统即使在遭遇故障时也能保持屹立不倒。副本和负载均衡是常见的容错策略。

  • 副本: 就像电影里的替身演员,副本可以完美复制数据或服务,当某个节点发生故障时,副本可以无缝接替,保证数据或服务的可用性。
  • 负载均衡: 就像交通中的分流,负载均衡可以将请求均匀地分配到多个服务器上,防止某个服务器被过载压垮。
// 副本示例
public class Replication {
    private List<Node> nodes;

    public Replication(List<Node> nodes) {
        this.nodes = nodes;
    }

    public synchronized Object read(String key) {
        // 从任意一个副本节点读取数据
        for (Node node : nodes) {
            Object value = node.read(key);
            if (value != null) {
                return value;
            }
        }
        return null;
    }

    public synchronized void write(String key, Object value) {
        // 将数据写入所有副本节点
        for (Node node : nodes) {
            node.write(key, value);
        }
    }
}

// 负载均衡示例
public class LoadBalancer {
    private List<Server> servers;

    public LoadBalancer(List<Server> servers) {
        this.servers = servers;
    }

    public synchronized Server getServer() {
        // 根据某种算法(如轮询、权重等)选择一个服务器
        return servers.get(index++);
    }
}

4. 弹性伸缩:像橡皮筋一样伸缩自如

弹性伸缩就像橡皮筋,它可以让系统根据需求自动调整资源分配,灵活应对流量波动。自动伸缩和手动伸缩是常见的弹性伸缩方法。

  • 自动伸缩: 系统根据某个指标(如CPU利用率、内存使用率等)自动调整资源分配。当指标达到某个阈值时,系统会自动增加或减少资源。
  • 手动伸缩: 系统管理员手动调整资源分配,以应对突发流量或其他特殊情况。
// 自动伸缩示例
public class AutoScaling {
    private ScalingPolicy scalingPolicy;
    private ResourceProvider resourceProvider;

    public AutoScaling(ScalingPolicy scalingPolicy, ResourceProvider resourceProvider) {
        this.scalingPolicy = scalingPolicy;
        this.resourceProvider = resourceProvider;
    }

    public synchronized void scale() {
        int desiredSize = scalingPolicy.getDesiredSize();
        int actualSize = resourceProvider.getSize();

        if (desiredSize > actualSize) {
            resourceProvider.increaseSize(desiredSize - actualSize);
        } else if (desiredSize < actualSize) {
            resourceProvider.decreaseSize(actualSize - desiredSize);
        }
    }
}

// 手动伸缩示例
public class ManualScaling {
    private ResourceProvider resourceProvider;

    public ManualScaling(ResourceProvider resourceProvider) {
        this.resourceProvider = resourceProvider;
    }

    public synchronized void scale(int desiredSize) {
        int actualSize = resourceProvider.getSize();

        if (desiredSize > actualSize) {
            resourceProvider.increaseSize(desiredSize - actualSize);
        } else if (desiredSize < actualSize) {
            resourceProvider.decreaseSize(actualSize - desiredSize);
        }
    }
}

5. 缓存:缩短访问数据的距离

缓存就像一个快速通道,它将数据临时存储在内存中,减少对数据库或其他存储系统的访问次数,从而