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

2022-05-31 19:10:33 +08:00
 dfgxcvbcv

需求是每秒查一次 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 在本机(指爬数据的服务器上)。

被爬网站均没有限流。

2564 次点击
所在节点    程序员
21 条回复
Juszoe
2022-05-31 20:03:06 +08:00
不懂 C#,但是 async 异步在大部分语言里都是单线程运行吧
dfgxcvbcv
2022-05-31 20:04:54 +08:00
@Juszoe #1 Google 了下好像确实是这样,那这种场景怎么设计最好呢?
Juszoe
2022-05-31 20:16:06 +08:00
@dfgxcvbcv #2 你的任务负载这么大,得上多线程才能利用更多的核心。将任务分解出来,每个线程内可以用 async ,一个线程带几万个协程还是太为难了
ly841000
2022-05-31 20:19:57 +08:00
@dfgxcvbcv c# async 不是单线程
Building
2022-05-31 20:31:02 +08:00
多线程应该和 thread 有关吧,async 只是把任务放到不同的 queue ,thread 不变,当然就只有一个核心的 CPU 在跑,不知道 C# 是不是这样
ikesnowy
2022-05-31 20:34:21 +08:00
你在本地运行可以跑满多个 CPU 吗?排除服务器和部署的原因,如果本地也跑不满的话就是代码写得有问题了。
thinkershare
2022-05-31 20:37:35 +08:00
使用一个主线程去发送请求(和 MySQL 打交道, 发送异步请求), 关闭同步上下文, 然后在主线程上发送 HTTP 请求, 然后主线程就等待(用 Task.Delay()), 时间到了再次轮询数据库. 在 HTTP 的 IO 的 Task 上附加成功的后续操作, 这个操着会在线程池中获取一个空闲线程, 然后执行. 一开始配置好线程池的物理线程数量和 I/O 线程数量. 你给出来的信息太有限了, 很难给你分析. 另外真个场景我估计瓶颈不在 CPU, 如果后续操作费事很少, 则 CPU 应该一直空闲, 如果你开启了上下文同步, 则可能导致主线程假死!
thinkershare
2022-05-31 20:38:41 +08:00
@Building C#的 Task 比较复杂, 并不是简单的协程, 既可以多线程, 也可以单线程
Soar360
2022-05-31 20:43:12 +08:00
如果是 framework 的话,看看是不是限制并发了?
Buges
2022-05-31 20:43:43 +08:00
你这种情况,盲猜是阻塞了。哪里有 CPU 密集型都任务,把线程给占了。
纯异步 IO 话单线程 /多线程的 executor 没有太显著的差异。
ragnaroks
2022-05-31 20:44:00 +08:00
不知道楼主什么水平,但是并不是 public async void function1() 就是异步,没有代码不好诊断,可以先把任务压倒一个 list ,用并行去跑,简单快捷
netnr
2022-05-31 21:16:35 +08:00
C# 并行任务 Parallel 类
https://www.netnr.com/home/list/139
ration
2022-05-31 21:34:13 +08:00
异步的话所有方法都得异步,排查下是不是有些方法写成同步阻塞了。
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
2022-05-31 22:56:34 +08:00
不发代码不好分析,可能就是和楼上说的一样立即 await
forgottencoast
2022-05-31 23:39:04 +08:00
上面很多人说了,异步和并行是两个概念。
默认情况下异步你用 await 的话,CPU 就是单线程的,但是 IO 方面可以并发。
简单一点,你可以不要 await ,然后任务完成以后自己处理回调。
要想优雅就用前面#12 提到的 Parallel 。
一般情况下爬虫 CPU 不应该跑满的,除非你大量分析内容,带宽要先跑满,你还是要重新分析一下自己的代码。
fanxiao
2022-06-01 08:31:51 +08:00
直接将这个 task 丢到 ThreadPool 里面运行就好了 , 主线程 await io 资源还没有返回,不会往下面执行,本质跟你写个同步语句没有区别,只是当 io 资源满足之后方便回到现场,所以还需要 task.Run 或者 ThreadPool 来支持多线程.
fanxiao
2022-06-01 08:33:19 +08:00
ClorisYe
2022-06-01 09:59:41 +08:00
异步是开辟一个后台线程去做 IO 的,当你 await 的时候,主线程在等待异步线程的执行回调。所以,异步本身是多线程实现的,这个你可以通过在异步方法内打印线程 ID 可以看到与主线程的是不一样的。如果你想同时开多个异步 IO ,你可以把任务存放到一个队列里面,然后遍历 await 。这样批量任务可以同时执行。
beginor
2022-06-01 12:45:31 +08:00
#12 楼上正解,parallel 可以调用全部 CPU 核心

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/856525

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX