V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dfgxcvbcv
V2EX  ›  程序员

C#爬虫总是只用一个 CPU 核心怎么排查?

  •  
  •   dfgxcvbcv · 2022-05-31 19:10:33 +08:00 · 2513 次点击
    这是一个创建于 666 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求是每秒查一次 MySQL ,发现有新任务就开始爬数据,速度要求每分钟 1~2 万个网页(内容有时效性,必须尽快爬完,每个网页的响应速度差别很大),解析出需要的数据后存入 MySQL 。(需要爬取的 URL 在程序运行时动态接收并添加到 MySQL 中,超时 /失败则按 MySQL 中的该域名的指定间隔(大部分是 1 秒,也有一些是 1 小时)重试,达到 MySQL 中的指定次数则在 MySQL 中标记失败不再重试)

    目前的做法是 async 异步请求(域名超时和重试次数限制用一个缓存模块自动缓存到 Redis ,失败任务如果设置的重试间隔小于一分钟则 Redis 存下次重试时间,否则存 MySQL ,MySQL 查询任务列表的时候用条件过滤没到时间的)+SemaphoreSlim 限制最大同时执行任务数量为 400 (因为有时候一分钟会添加十几万个网页,同时发这么多请求程序会卡死)。发现爬取速度比较慢,查了下 CPU 有一个核心占用 100%,剩下都不到 1%。带宽因为大部分是下行,阿里云共享不知道多大的下行带宽只用了 18Mbps ,独享 7Mbps 上行带宽只用了 2Mbps 。MySQL 用的是阿里云云数据库。Redis 在本机(指爬数据的服务器上)。

    被爬网站均没有限流。

    21 条回复    2022-06-01 15:14:35 +08:00
    Juszoe
        1
    Juszoe  
       2022-05-31 20:03:06 +08:00
    不懂 C#,但是 async 异步在大部分语言里都是单线程运行吧
    dfgxcvbcv
        2
    dfgxcvbcv  
    OP
       2022-05-31 20:04:54 +08:00
    @Juszoe #1 Google 了下好像确实是这样,那这种场景怎么设计最好呢?
    Juszoe
        3
    Juszoe  
       2022-05-31 20:16:06 +08:00
    @dfgxcvbcv #2 你的任务负载这么大,得上多线程才能利用更多的核心。将任务分解出来,每个线程内可以用 async ,一个线程带几万个协程还是太为难了
    ly841000
        4
    ly841000  
       2022-05-31 20:19:57 +08:00
    @dfgxcvbcv c# async 不是单线程
    Building
        5
    Building  
       2022-05-31 20:31:02 +08:00
    多线程应该和 thread 有关吧,async 只是把任务放到不同的 queue ,thread 不变,当然就只有一个核心的 CPU 在跑,不知道 C# 是不是这样
    ikesnowy
        6
    ikesnowy  
       2022-05-31 20:34:21 +08:00
    你在本地运行可以跑满多个 CPU 吗?排除服务器和部署的原因,如果本地也跑不满的话就是代码写得有问题了。
    thinkershare
        7
    thinkershare  
       2022-05-31 20:37:35 +08:00
    使用一个主线程去发送请求(和 MySQL 打交道, 发送异步请求), 关闭同步上下文, 然后在主线程上发送 HTTP 请求, 然后主线程就等待(用 Task.Delay()), 时间到了再次轮询数据库. 在 HTTP 的 IO 的 Task 上附加成功的后续操作, 这个操着会在线程池中获取一个空闲线程, 然后执行. 一开始配置好线程池的物理线程数量和 I/O 线程数量. 你给出来的信息太有限了, 很难给你分析. 另外真个场景我估计瓶颈不在 CPU, 如果后续操作费事很少, 则 CPU 应该一直空闲, 如果你开启了上下文同步, 则可能导致主线程假死!
    thinkershare
        8
    thinkershare  
       2022-05-31 20:38:41 +08:00
    @Building C#的 Task 比较复杂, 并不是简单的协程, 既可以多线程, 也可以单线程
    Soar360
        9
    Soar360  
       2022-05-31 20:43:12 +08:00
    如果是 framework 的话,看看是不是限制并发了?
    Buges
        10
    Buges  
       2022-05-31 20:43:43 +08:00 via Android
    你这种情况,盲猜是阻塞了。哪里有 CPU 密集型都任务,把线程给占了。
    纯异步 IO 话单线程 /多线程的 executor 没有太显著的差异。
    ragnaroks
        11
    ragnaroks  
       2022-05-31 20:44:00 +08:00
    不知道楼主什么水平,但是并不是 public async void function1() 就是异步,没有代码不好诊断,可以先把任务压倒一个 list ,用并行去跑,简单快捷
    netnr
        12
    netnr  
       2022-05-31 21:16:35 +08:00
    C# 并行任务 Parallel 类
    https://www.netnr.com/home/list/139
    ration
        13
    ration  
       2022-05-31 21:34:13 +08:00 via Android
    异步的话所有方法都得异步,排查下是不是有些方法写成同步阻塞了。
    darklights
        14
    darklights  
       2022-05-31 22:29:48 +08:00
    async 是异步,不是并行,如果代码这样写:
    while (link != null) {
    var content = await Fetch(link);
    await Save(content);
    link = FindNext(html);
    }

    那跑起来跟 JS 一样,只能用一个核。
    SMGdcAt4kPPQ
        15
    SMGdcAt4kPPQ  
       2022-05-31 22:56:34 +08:00
    不发代码不好分析,可能就是和楼上说的一样立即 await
    forgottencoast
        16
    forgottencoast  
       2022-05-31 23:39:04 +08:00
    上面很多人说了,异步和并行是两个概念。
    默认情况下异步你用 await 的话,CPU 就是单线程的,但是 IO 方面可以并发。
    简单一点,你可以不要 await ,然后任务完成以后自己处理回调。
    要想优雅就用前面#12 提到的 Parallel 。
    一般情况下爬虫 CPU 不应该跑满的,除非你大量分析内容,带宽要先跑满,你还是要重新分析一下自己的代码。
    fanxiao
        17
    fanxiao  
       2022-06-01 08:31:51 +08:00
    直接将这个 task 丢到 ThreadPool 里面运行就好了 , 主线程 await io 资源还没有返回,不会往下面执行,本质跟你写个同步语句没有区别,只是当 io 资源满足之后方便回到现场,所以还需要 task.Run 或者 ThreadPool 来支持多线程.
    fanxiao
        18
    fanxiao  
       2022-06-01 08:33:19 +08:00
    ClorisYe
        19
    ClorisYe  
       2022-06-01 09:59:41 +08:00
    异步是开辟一个后台线程去做 IO 的,当你 await 的时候,主线程在等待异步线程的执行回调。所以,异步本身是多线程实现的,这个你可以通过在异步方法内打印线程 ID 可以看到与主线程的是不一样的。如果你想同时开多个异步 IO ,你可以把任务存放到一个队列里面,然后遍历 await 。这样批量任务可以同时执行。
    beginor
        20
    beginor  
       2022-06-01 12:45:31 +08:00 via Android
    #12 楼上正解,parallel 可以调用全部 CPU 核心
    billzhuang
        21
    billzhuang  
       2022-06-01 15:14:35 +08:00
    不管是查 mysql 还是爬虫,都是 io bound ,正确使用 async/await 一个核心都嫌多。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4864 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 09:54 · PVG 17:54 · LAX 02:54 · JFK 05:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.