C# Polly 弹性策略库指南

基于 Polly v8 编写,适用于 .NET 6+ 项目。结合 Shopify GraphQL 批量调用场景进行实战演示。


目录

  1. [Polly 是什么](#Polly 是什么 "#1-polly-%E6%98%AF%E4%BB%80%E4%B9%88")
  2. 安装
  3. 核心策略
  4. [策略组合 Pipeline](#策略组合 Pipeline "#4-%E7%AD%96%E7%95%A5%E7%BB%84%E5%90%88-pipeline")
  5. 执行顺序详解
  6. 线程安全
  7. [完整实战:Shopify 批量调用](#完整实战:Shopify 批量调用 "#7-%E5%AE%8C%E6%95%B4%E5%AE%9E%E6%88%98shopify-%E6%89%B9%E9%87%8F%E8%B0%83%E7%94%A8")
  8. [v7 vs v8 差异速查](#v7 vs v8 差异速查 "#8-v7-vs-v8-%E5%B7%AE%E5%BC%82%E9%80%9F%E6%9F%A5")

1. Polly 是什么

Polly 是 .NET 生态中最主流的弹性与瞬态故障处理库 ,由 .NET Foundation 官方维护,采用 BSD-3-Clause 开源协议,完全免费可商用

它允许你用声明式的方式组合多种弹性策略(重试、断路器、超时、限流、隔离、回退),构建一个弹性执行管道(ResiliencePipeline) 。所有策略都是线程安全的,天然适合多线程/并发场景。

典型应用场景:

  • 调用外部 HTTP API(如 Shopify、AWS、Azure)时网络抖动
  • 数据库瞬态连接失败
  • 消息队列临时不可用
  • 需要控制并发度防止资源耗尽

2. 安装

bash 复制代码
# 核心库(必须)
dotnet add package Polly.Core

# 限流(需要 System.Threading.RateLimiting)
dotnet add package Polly.RateLimiting

# 如果要配合 HttpClientFactory
dotnet add package Microsoft.Extensions.Http.Resilience

3. 核心策略

3.1 Retry(重试)

当操作失败时,按指定规则自动重试。这是最常用的策略。

关键参数:

参数 说明
MaxRetryAttempts 最大重试次数(不包括首次尝试)
Delay 首次重试前的等待时间
BackoffType 退避类型:Fixed(固定)、Linear(线性)、Exponential(指数)
UseJitter 是否加随机抖动,防止多个线程同时重试(惊群效应)
ShouldHandle 定义哪些异常/结果需要重试
OnRetry 重试前的回调,可记录日志

示例:Shopify 遇到 429 时指数退避重试

csharp 复制代码
using Polly;
using Polly.Retry;

var retryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRetry(new RetryStrategyOptions<HttpResponseMessage>
    {
        MaxRetryAttempts = 5,
        Delay = TimeSpan.FromSeconds(1),
        BackoffType = DelayBackoffType.Exponential,  // 1s, 2s, 4s, 8s, 16s
        UseJitter = true,                               // 加随机偏移,错峰重试

        ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
            .HandleResult(r => (int)r.StatusCode == 429)           // HTTP 429
            .HandleResult(r => IsGraphQLThrottled(r))             // GraphQL THROTTLED
            .Handle<HttpRequestException>(),                       // 网络异常

        OnRetry = args =>
        {
            Console.WriteLine($"第 {args.AttemptNumber} 次重试,等待 {args.RetryDelay.TotalSeconds}s");
            return ValueTask.CompletedTask;
        }
    })
    .Build();

// 使用
var response = await retryPipeline.ExecuteAsync(
    async token => await httpClient.GetAsync("https://api.example.com", token),
    CancellationToken.None
);

重要UseJitter = true 在批量调用场景下非常关键。如果 5 个线程同时触发 429,不加抖动的话它们会在完全相同的时刻醒来重试,再次一起撞墙。


3.2 Circuit Breaker(断路器)

当连续失败次数达到阈值时,快速失败一段时间(不再真正调用下游),给下游服务恢复的机会。就像电路跳闸。

关键参数:

参数 说明
FailureRatio 触发断路器的失败比例(如 0.5 表示 50% 失败率)
SamplingDuration 统计失败率的采样窗口
MinimumThroughput 采样窗口内最少需要多少请求才判断
BreakDuration 断路器打开后持续多久
ShouldHandle 定义哪些异常/结果算失败

示例:Shopify 连续失败 50% 时熔断 30 秒

csharp 复制代码
var circuitBreakerPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage>
    {
        FailureRatio = 0.5,                    // 50% 失败率触发
        SamplingDuration = TimeSpan.FromSeconds(30),  // 统计最近 30 秒
        MinimumThroughput = 10,                // 至少 10 个请求才判断
        BreakDuration = TimeSpan.FromSeconds(30),     // 熔断 30 秒

        ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
            .HandleResult(r => (int)r.StatusCode >= 500)
            .Handle<HttpRequestException>(),

        OnOpened = args =>
        {
            Console.WriteLine($"⚠️ 断路器打开!持续 {args.BreakDuration.TotalSeconds}s");
            return ValueTask.CompletedTask;
        },
        OnClosed = args =>
        {
            Console.WriteLine("✅ 断路器关闭,恢复正常");
            return ValueTask.CompletedTask;
        }
    })
    .Build();

三种状态:

  • Closed(关闭):正常通过请求
  • Open(打开):快速失败,不调用下游
  • Half-Open(半开):熔断时间结束后,放少量请求试探,成功则关闭,失败则重新打开

适用场景:Shopify 服务完全宕机或大面积限流时,避免你的程序疯狂重试,浪费线程和 CPU。


3.3 Timeout(超时)

为整个操作设置一个总时间上限,防止某个请求卡死(比如 Shopify 偶尔响应极慢)。

两种模式:

  • TimeoutStrategyOptions.TimeoutType = TimeoutType.Optimistic:使用 CancellationToken,协作式取消
  • TimeoutStrategyOptions.TimeoutType = TimeoutType.Pessimistic:强制超时,即使代码不检查 CancellationToken

示例:单个请求最多等 10 秒

csharp 复制代码
var timeoutPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddTimeout(new TimeoutStrategyOptions
    {
        Timeout = TimeSpan.FromSeconds(10),
        OnTimeout = args =>
        {
            Console.WriteLine($"⏱️ 请求超时!已等待 {args.Timeout.TotalSeconds}s");
            return ValueTask.CompletedTask;
        }
    })
    .Build();

注意 :Timeout 应该放在 Pipeline 的最外层(最先 Add),这样它控制的是整个 Pipeline 的总时间(包括重试的等待时间)。


3.4 RateLimiter(限流)

基于 .NET 原生的 System.Threading.RateLimiting,控制请求速率。适合客户端主动限速。

常见限流器:

类型 说明
FixedWindowRateLimiter 固定窗口,每秒/每分 N 个
SlidingWindowRateLimiter 滑动窗口,更平滑
TokenBucketRateLimiter 令牌桶,可突发
ConcurrencyLimiter 限制并发数(见 3.5)

示例:每秒最多 10 个请求

csharp 复制代码
using System.Threading.RateLimiting;

var rateLimiterPipeline = new ResiliencePipelineBuilder()
    .AddRateLimiter(new SlidingWindowRateLimiter(
        new SlidingWindowRateLimiterOptions
        {
            PermitLimit = 10,
            Window = TimeSpan.FromSeconds(1),
            SegmentsPerWindow = 10,
            QueueLimit = 5  // 超限时最多排队 5 个,其余抛异常
        }))
    .Build();

Shopify 场景不适用:因为 Shopify 是动态积分制(不同查询成本不同),固定速率限流器无法精确匹配。建议不用 RateLimiter,靠 Retry 兜底即可。


3.5 ConcurrencyLimiter(并发隔离)

限制同时执行的请求数量,防止线程池耗尽。Polly v8 中替代了旧版的 Bulkhead

关键参数:

参数 说明
PermitLimit 最大并发数
QueueLimit 队列中最多排多少个,超出的抛异常

示例:最多 5 个并发,队列排 10 个

csharp 复制代码
var concurrencyPipeline = new ResiliencePipelineBuilder()
    .AddConcurrencyLimiter(new ConcurrencyLimiterOptions
    {
        PermitLimit = 5,
        QueueLimit = 10
    })
    .Build();

与 SemaphoreSlim 的区别 :Polly 的 ConcurrencyLimiter 是策略级的,可以和其他策略组合;SemaphoreSlim 是代码级的。两者可以配合使用。


3.6 Fallback(回退)

当所有策略都失败时,返回一个默认值 或执行降级逻辑,避免直接抛异常给上层。

示例:Shopify 查询失败时返回缓存数据

csharp 复制代码
var fallbackPipeline = new ResiliencePipelineBuilder<string>()
    .AddFallback(new FallbackStrategyOptions<string>
    {
        FallbackAction = args =>
        {
            // 返回默认值或从缓存读取
            return new ValueTask<string>("{"data":null,"fromCache":true}");
        },
        OnFallback = args =>
        {
            Console.WriteLine("🔄 触发降级,返回默认值");
            return ValueTask.CompletedTask;
        }
    })
    .Build();

适用场景:非关键数据查询(如商品详情展示),失败时显示"暂无数据"比直接报错更友好。


4. 策略组合 Pipeline

Polly 的强大之处在于可以组合多个策略 成一个执行管道。用 ResiliencePipelineBuilder 链式调用即可。

csharp 复制代码
var pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddTimeout(TimeSpan.FromSeconds(30))           // 最外层:总时间上限
    .AddConcurrencyLimiter(5, 10)                   // 限制并发
    .AddRetry(new RetryStrategyOptions<...> { ... }) // 失败时重试
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions<...> { ... }) // 连续失败熔断
    .Build();

5. 执行顺序详解

这是 Polly 最容易踩坑的地方。

Pipeline 的策略是外层包裹内层 的,先 Add 的策略在最外层,后 Add 的策略在内层。

csharp 复制代码
builder
    .AddStrategyA()  // 最外层:请求先经过 A
    .AddStrategyB()  // 中间层
    .AddStrategyC()  // 最内层:实际业务代码在这里执行

执行流程(正向进入,反向退出):

css 复制代码
请求 → [StrategyA] → [StrategyB] → [StrategyC] → 执行业务代码
返回 ← [StrategyA] ← [StrategyB] ← [StrategyC] ← 结果/异常

常见组合的正确顺序

场景 推荐顺序(从外到内) 原因
超时 + 重试 TimeoutRetry Timeout 控制总时间(含重试等待)
限流 + 重试 RateLimiterRetry 重试不额外抢配额
熔断 + 重试 CircuitBreakerRetry 先熔断判断,再决定重试
并发 + 重试 ConcurrencyLimiterRetry 先拿到并发许可,再重试
完整组合 TimeoutCircuitBreakerConcurrencyLimiterRetry 总时间 → 熔断 → 并发 → 重试

错误顺序的陷阱

csharp 复制代码
// ❌ 错误:Retry 在外层,RateLimiter 在内层
builder
    .AddRetry(...)        // 每次重试都重新进入内层
    .AddRateLimiter(...)  // 每次重试都要重新抢许可 → 正常请求被挤掉

// ✅ 正确:RateLimiter 在外层,Retry 在内层
builder
    .AddRateLimiter(...)  // 只控制"能否进入管道",拿到许可后不再检查
    .AddRetry(...)        // 失败后在当前线程等待,不重新抢许可

6. 线程安全

所有 Polly 策略都是线程安全的,可以创建一个 Pipeline 实例,在多个线程/任务中共享使用。

csharp 复制代码
// 创建一个全局 Pipeline(推荐作为单例)
public static readonly ResiliencePipeline<HttpResponseMessage> SharedPipeline = 
    new ResiliencePipelineBuilder<HttpResponseMessage>()
        .AddRetry(...)
        .Build();

// 多个线程安全共享
var tasks = Enumerable.Range(0, 100).Select(i =>
    SharedPipeline.ExecuteAsync(async token => 
        await httpClient.GetAsync($"/api/items/{i}", token), 
        CancellationToken.None)
);
await Task.WhenAll(tasks);

最佳实践 :Pipeline 创建有一定开销,建议作为单例复用,不要每次请求都重新 Build。


7. 完整实战:Shopify 批量调用

场景描述

  • 需要批量调用 Shopify GraphQL Admin API
  • 积分池由多个项目/品牌共享,不可硬编码积分值
  • 使用 5 个长期工作线程处理队列
  • 只有收到 429/THROTTLED 时才退避重试
  • 一个线程重试时不能阻塞其他线程

完整代码

csharp 复制代码
using System.Text;
using System.Threading.Channels;
using Polly;
using Polly.CircuitBreaker;
using Polly.Retry;
using Polly.Timeout;

/// <summary>
/// Shopify 批量任务处理器
/// 5 个长期工作线程 + Polly 弹性管道
/// </summary>
public class ShopifyBatchProcessor : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly Channel<ShopifyJob> _queue;
    private readonly SemaphoreSlim _semaphore;
    private readonly ResiliencePipeline<string> _pipeline;
    private readonly CancellationTokenSource _cts = new();
    private readonly List<Task> _workers = new();

    public ShopifyBatchProcessor(HttpClient httpClient, int workerCount = 5)
    {
        _httpClient = httpClient;
        _semaphore = new SemaphoreSlim(workerCount);

        // 无界队列:随时扔任务,不阻塞生产者
        _queue = Channel.CreateUnbounded<ShopifyJob>(
            new UnboundedChannelOptions
            {
                SingleReader = false,  // 多个 worker 同时读
                SingleWriter = false   // 多个生产者同时写
            });

        // 构建弹性管道(从外到内)
        _pipeline = new ResiliencePipelineBuilder<string>()
            // 最外层:总时间上限(包含所有重试等待时间)
            .AddTimeout(new TimeoutStrategyOptions
            {
                Timeout = TimeSpan.FromSeconds(60),
                OnTimeout = args =>
                {
                    Log($"⏱️ 请求总超时(含重试):{args.Timeout.TotalSeconds}s");
                    return ValueTask.CompletedTask;
                }
            })
            // 第二层:断路器,Shopify 大面积故障时快速失败
            .AddCircuitBreaker(new CircuitBreakerStrategyOptions<string>
            {
                FailureRatio = 0.5,
                SamplingDuration = TimeSpan.FromSeconds(30),
                MinimumThroughput = 10,
                BreakDuration = TimeSpan.FromSeconds(30),
                ShouldHandle = new PredicateBuilder<string>()
                    .Handle<ShopifyThrottledException>()
                    .Handle<HttpRequestException>(),
                OnOpened = args =>
                {
                    Log($"⚠️ 断路器打开!暂停 {args.BreakDuration.TotalSeconds}s");
                    return ValueTask.CompletedTask;
                },
                OnClosed = _ =>
                {
                    Log("✅ 断路器关闭,恢复调用");
                    return ValueTask.CompletedTask;
                }
            })
            // 最内层:重试策略,遇到 429/THROTTLED 时指数退避
            .AddRetry(new RetryStrategyOptions<string>
            {
                MaxRetryAttempts = 5,
                Delay = TimeSpan.FromSeconds(1),
                BackoffType = DelayBackoffType.Exponential, // 1s, 2s, 4s, 8s, 16s
                UseJitter = true, // 关键:防止 5 个线程同时醒来重试

                ShouldHandle = new PredicateBuilder<string>()
                    .Handle<ShopifyThrottledException>()   // GraphQL THROTTLED
                    .Handle<HttpRequestException>(),       // 网络异常

                OnRetry = args =>
                {
                    Log($"🔄 第 {args.AttemptNumber} 次重试,等待 {args.RetryDelay.TotalSeconds:F1}s | " +
                        $"异常:{args.Outcome.Exception?.Message}");
                    return ValueTask.CompletedTask;
                }
            })
            .Build();

        // 启动工作线程
        for (int i = 0; i < workerCount; i++)
        {
            _workers.Add(Task.Run(() => WorkerLoop(i)));
        }
    }

    /// <summary>
    /// 提交一个任务到队列,返回可 await 的 Task
    /// </summary>
    public Task<T> EnqueueAsync<T>(Func<CancellationToken, Task<T>> work, string jobName = "")
    {
        var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);

        var job = new ShopifyJob
        {
            Name = jobName,
            Execute = async ct =>
            {
                try
                {
                    var result = await work(ct);
                    tcs.SetResult(result);
                }
                catch (Exception ex)
                {
                    tcs.SetException(ex);
                }
            }
        };

        _queue.Writer.TryWrite(job);
        return tcs.Task;
    }

    /// <summary>
    /// 发送 Shopify GraphQL 查询,自动走弹性管道
    /// </summary>
    public async Task<string> QueryGraphQLAsync(string query, string variables = "{}", 
        CancellationToken ct = default)
    {
        return await _pipeline.ExecuteAsync(async token =>
        {
            var payload = new
            {
                query = query,
                variables = variables
            };

            var json = System.Text.Json.JsonSerializer.Serialize(payload);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var response = await _httpClient.PostAsync(
                "admin/api/2024-07/graphql.json", content, token);

            var body = await response.Content.ReadAsStringAsync(token);

            // 检查 HTTP 429
            if ((int)response.StatusCode == 429)
            {
                throw new ShopifyThrottledException("HTTP 429 Too Many Requests");
            }

            // 检查 GraphQL 200 OK 但业务错误 THROTTLED
            if (body.Contains(""code":"THROTTLED"") || 
                body.Contains(""code":"MAXIMUM_CALCULATED_COST_EXCEEDED""))
            {
                throw new ShopifyThrottledException("GraphQL THROTTLED");
            }

            // 检查其他 GraphQL 错误
            if (body.Contains(""errors":["))
            {
                throw new InvalidOperationException($"GraphQL Error: {body}");
            }

            return body;
        }, ct);
    }

    /// <summary>
    /// 工作线程主循环
    /// </summary>
    private async Task WorkerLoop(int workerId)
    {
        await foreach (var job in _queue.Reader.ReadAllAsync(_cts.Token))
        {
            await _semaphore.WaitAsync(_cts.Token);
            try
            {
                Log($"[Worker {workerId}] 处理: {job.Name}");
                await job.Execute(_cts.Token);
            }
            catch (OperationCanceledException) when (_cts.Token.IsCancellationRequested)
            {
                break; // 正常退出
            }
            catch (Exception ex)
            {
                Log($"[Worker {workerId}] 异常: {ex.Message}");
                // 工作线程不死,继续处理下一个
            }
            finally
            {
                _semaphore.Release();
            }
        }
    }

    public void Dispose()
    {
        _queue.Writer.Complete();
        _cts.Cancel();
        _cts.Dispose();
        _semaphore.Dispose();
    }

    private void Log(string message) => Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {message}");

    private class ShopifyJob
    {
        public string Name { get; set; } = "";
        public required Func<CancellationToken, Task> Execute { get; set; }
    }
}

/// <summary>
/// Shopify 限流异常
/// </summary>
public class ShopifyThrottledException : Exception
{
    public ShopifyThrottledException(string message) : base(message) { }
}

使用示例

csharp 复制代码
// 初始化(建议作为单例注册到 DI)
var httpClient = new HttpClient
{
    BaseAddress = new Uri("https://your-shop.myshopify.com/"),
    DefaultRequestHeaders =
    {
        { "X-Shopify-Access-Token", "your-access-token" }
    }
};

using var processor = new ShopifyBatchProcessor(httpClient, workerCount: 5);

// 批量提交 1000 个任务(每批 200 个只是逻辑分组,实际全部进同一个队列)
var allTasks = new List<Task<string>>();

for (int batch = 0; batch < 5; batch++) // 5 批,每批 200 个
{
    for (int i = 0; i < 200; i++)
    {
        int itemId = batch * 200 + i;

        var task = processor.EnqueueAsync(
            async ct => await processor.QueryGraphQLAsync(
                $"{{ product(id: "gid://shopify/Product/{itemId}") {{ title variants(first: 5) {{ edges {{ node {{ title }} }} }} }} }}",
                "{}",
                ct),
            jobName: $"Product-{itemId}"
        );

        allTasks.Add(task);
    }

    Console.WriteLine($"第 {batch + 1} 批 200 个已提交到队列");
    // 不需要等待上一批完成!下一批立即提交
}

// 等待全部完成(可选)
var results = await Task.WhenAll(allTasks);
Console.WriteLine($"全部完成,成功 {results.Length} 个");

架构图

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                     生产者(主线程)                           │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐                        │
│  │ 任务 1  │ │ 任务 2  │ │ 任务 N  │ ──→ Channel 队列        │
│  └─────────┘ └─────────┘ └─────────┘                        │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    5 个 Worker 线程                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ SemaphoreSlim(5) → 同时最多 5 个任务在执行          │   │
│  │                                                     │   │
│  │ 每个 Worker 执行 Pipeline(从外到内):              │   │
│  │ 1. Timeout(60s) ──→ 总时间上限                        │   │
│  │ 2. CircuitBreaker ──→ 连续失败熔断                  │   │
│  │ 3. Retry(指数退避+Jitter) ──→ 429 时重试            │   │
│  │    └── 在当前线程等待,不阻塞其他 Worker              │   │
│  │ 4. 实际 HTTP 请求 → Shopify GraphQL API             │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

8. v7 vs v8 差异速查

特性 Polly v7 Polly v8
核心 API Policy.Handle<> ResiliencePipelineBuilder
组合方式 Policy.Wrap() builder.AddXxx().AddYyy()
限流 BulkheadPolicy AddConcurrencyLimiter / AddRateLimiter
执行 policy.ExecuteAsync(...) pipeline.ExecuteAsync(...)
异步 Context 传参 直接在 lambda 中闭包
性能 较好 更好(减少分配)
推荐 维护中 ✅ 新项目首选

v7 写法(旧版,了解即可)

csharp 复制代码
// v7 写法
var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

var circuitBreaker = Policy
    .Handle<HttpRequestException>()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

// 组合
var combined = Policy.WrapAsync(circuitBreaker, retryPolicy);

v8 写法(推荐)

csharp 复制代码
// v8 写法
var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions { ... })
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions { ... })
    .Build();

附录:常见错误码速查

场景 异常/状态码 Polly 策略
临时网络断开 HttpRequestException Retry
HTTP 429 Too Many Requests StatusCode 429 Retry + Jitter
Shopify GraphQL THROTTLED THROTTLED in body Retry + 自定义异常
服务端 500/502/503 StatusCode 5xx Retry + CircuitBreaker
请求卡死无响应 超时 Timeout
下游服务完全宕机 持续失败 CircuitBreaker
非关键数据查询失败 任何异常 Fallback
相关推荐
geovindu2 小时前
go: Recursion Algorithm
开发语言·后端·算法·golang·递归算法
Shadow(⊙o⊙)2 小时前
日志与线程池
linux·c++·算法
冷小鱼2 小时前
AI Agent 的核心算法:多智能体协作(Multi-Agent Systems)
前端·人工智能·算法·multi-agent·多智能体协作·systems
QN1幻化引擎2 小时前
# DalinX V8 灵鉴 V2:12维意识评测框架 —— 从 Tononi IIT 到 Friston FEP 的理论统一
数据库·人工智能·算法·机器学习·数据挖掘·agi
牧以南歌〆3 小时前
数据结构<一>顺序表
c语言·数据结构·算法
Fairy要carry3 小时前
面试-训练显存占用估计
算法
触底反弹3 小时前
💥 暴力解 LeetCode 导致 OOM:10000 行数据撑爆 V8 堆内存的完整排查
javascript·算法·面试
cheems95273 小时前
[算法手记]二叉树中序,层序遍历,统计树高
算法