前言

最近把Python写的数据采集平台往.Net Core上迁移,原本的采集任务使用多进程+线程池的方式来加快采集速度,使用Celery作为异步任务队列兼具定时任务功能,这套东西用着还行,但反正就折腾嘛,直接上C#~

本文记录 Hangfire 在实际应用里的用法,我发现网络上找到的大部分文章都是用 Hangfire 的异步任务输出个 Hello World,然后就没了。我实在不知道这样的文章写了有什么意义??除了浪费看的人的时间之外,还浪费自己写文章的时间……

.NetCore作为一个高性能的平台,自然不可能输给Python,不过我不想造轮子了(菜),找个现成的方案来用,免得踩坑~

先是调研了一下.NetCore目前的生态,发现有几个选择:

  • FreeScheduler
  • Quartz.net
  • Hangfire

第一个 FreeScheduler 和 FreeSQL 项目出自同一个作者之手,刚好我的项目也是用的 FreeSQL 作为 ORM,不过看了一下Github上stars比较少,而且文档暂时还不完善,我最关心的依赖注入功能暂时还不好搞,于是只好作罢。

然后在 Quartz.net 和 Hangfire 两者中,我选择了后者,原因是我之前做 CrawlCenterNet 项目的时候用过 Hangfire,还挺好用的,且带有一个简单的 dashboard,比较直观~

那么就开始吧~

关于后端的选择

这里的后端指的是任务队列的存储后端,也就是 Hangfire 文档中写的 Storage。

看了一下官网,大部分关系型非关系型数据库都是可以的,那我就放心了。由于目前生产环境在使用 Oracle 数据库,所以一开始我选择了 Oracle 作为 Storage,但是在同时开到2000多个任务的时候报错了,看了下原因是表空间不足,是 Oracle 的问题…

所以为了性能和稳定性,我弃坑了,接着尝试了 SQLite (仅作为本地测试),结果发现配置里面定义了 Connection String 但它不理我,直接把这个 Connection String 作为数据库的名称了,无语… 这样就没办法把 SQLite 数据库设置为异步模式,那速度就直接乌龟爬了…

再次弃坑… 这次直接上 Redis 了,为了提高性能,舍弃持久化能力~ Redis也没让我失望,几千个任务压根不带眨眼的,nice~

数据采集代码

Hangfire 组件不是一开始就引入的,这里先上最基本的数据采集代码,后面的介绍才能更清楚~

关键的代码在 Services/CrawlService.cs 文件中

public class CrawlService {
// 依赖注入一些服务
private readonly IBaseRepository<Proc> _repo; public async Task CrawlAllProc() {
for(var i=1; i<2000; i++) {
await CrawlProcList(i);
}
} public async Task CrawlProcList(int page) {
// 具体代码省略了
var procList = ; //...
foreach (var proc in procList) {
await CrawlProc(proc);
}
} public async Task CrawlProc(Proc proc) { }
}

然后,当启动采集任务的时候,直接去调用 CrawlAllProc 方法,这样就开始一页一页采集,每页又有很多的 Proc 数据,全都采集下来。

上面的代码用的是 await ,会等待异步方法完成,速度很慢,去掉 await ,在新线程中执行任务,不等待其结束,不过问题也很明显,如果出错了很难调试,这样就不好保证系统的稳定性。

接下来我们用 Hangfire 来改造。

安装 Hangfire

本项目用到了以下依赖:

  • Hangfire.Core
  • Hangfire.AspNetCore
  • Hangfire.Redis.StackExchange

直接 nuget 一把梭完事

注册服务

为了跟后面的内容区分,这里先来官方的例子

注册服务:

services.AddHangfire(
configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
// 根据实际使用的 Storage 来注册
.UseRedisStorage();
services.AddHangfireServer();

添加中间件:

app.UseHangfireDashboard();

简单使用

Hangfire 注册的时候默认是单例模式,所以在任意代码中使用其静态方法就能添加异步任务或者定时任务。

异步任务:

BackgroundJobs.Enqueue(() => Console.WriteLine("Hello world!"));

定时任务:

Hangfire的定时任务叫做 recurrent tasks,我之前一般习惯叫 scheduled task,一开始差点找不到文档~

以下代码添加一个每天执行一次的任务,如果需要其他时间,可以自定义后面的 Cron 参数,具体自行研究 Cron 语法~

RecurringJob.AddOrUpdate("easyjob", () => Console.Write("Easy!"), Cron.Daily);

正经使用

OK,终于进到正文

正如我一开始说的,前面介绍的用法是远远不够的,如果只是介绍个 Hello World,那也没必要专门写篇文章了…

现在开始介绍如何将 Hangfire 结合我的 CrawlService 使用

因为 CrawlService 需要操作数据库,所以是用到了依赖注入的,所以我们需要让 Hangfire 也支持依赖注入。

官方文档有一小节是关于 IOC 容器的(https://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html),不过并没有介绍 AspNetCore 的容器,直接自己动手 丰衣足食咯~

添加 AspNetCore 的依赖注入容器

一开始搜了好久没找到,最终是在Github上找到一个例子代码,里面的 AspNetCore 版本好老,居然是1.1版本,我都没用过… 不过并不影响我节俭他的写法~

这一步需要 JobActivator 的子类

来写一个,我把它放在 Infrastructure 目录下

using Hangfire;

public class HangfireActivator : JobActivator {
private readonly IServiceProvider _serviceProvider; public HangfireActivator(IServiceProvider serviceProvider) {
_serviceProvider = serviceProvider;
} public override object? ActivateJob(Type jobType) {
return _serviceProvider.GetService(jobType);
}
}

这里是在 HangfireActivator 的构造函数中把 AspNetCore 的 IOC 容器对象传入,并且重写 ActivateJob 方法,让 Hangfire 才激活任务的时候从 IOC 容器中获取对象,比较好理解。

修改服务注册代码

其实服务注册部分是一样的,无须修改

不过按照习惯,为了使 Program.cs 或者 Startup.cs 代码比较简洁,我还是写了扩展方法来实现这部分。

Extensions 目录中添加 ConfigureHangfire.cs

public static class ConfigureHangfire {
public static void AddHangfirePkg(this IServiceCollection services, IConfiguration configuration) {
services.AddHangfire(conf => conf
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseRedisStorage()
); services.AddHangfireServer();
} public static void UseHangfire(this WebApplication app) {
GlobalConfiguration.Configuration.UseActivator(new HangfireActivator(app.Services));
app.UseHangfireDashboard();
}
}

可以看到有修改的地方就是在添加中间件之前,配置了 Activator 这行代码:

GlobalConfiguration.Configuration.UseActivator(new HangfireActivator(app.Services));

直接把 IOC 容器传入

搞定~

接着在 Program.cs (我用的 .Net6)中使用这个扩展方法就完事了~

builder.Services.AddHangfirePkg(builder.Configuration);
// 中间件
app.UseHangfire();

创建任务

有了依赖注入之后,创建异步任务是这样的。也就是多了个泛型参数。

BackgroundJob.Enqueue<CrawlService>(a => a.CrawlAllProc());

定时任务,每小时执行一次

RecurringJob.AddOrUpdate<CrawlService>(a => a.CrawlAllProc(), Cron.Hourly);

改造一下数据采集代码

OK,最后回到一开始的数据采集代码,做如下修改:

public class CrawlService {
// 依赖注入一些服务
private readonly IBaseRepository<Proc> _repo; public async Task CrawlAllProc() {
for(var i=1; i<2000; i++) {
// await CrawlProcList(i);
BackgroundJob.Enqueue(() => CrawlProcList(i, 100));
}
} public async Task CrawlProcList(int page, int pageSize = 100) {
// 具体代码省略了
var procList = ; //...
foreach (var proc in procList) {
// await CrawlProc(proc);
BackgroundJob.Enqueue(() => CrawlProc(proc));
}
} public async Task CrawlProc(Proc proc) { }
}

把原来 await 的地方注释掉,换成用 Hangfire 创建异步任务,运行起来,打开dashboard,可以看到任务噌的一下就上到几千,速度极快~

需要注意的就是 CrawlProcList 方法的第二个参数 pageSize 我们给了默认值100,在正常使用是没问题的,可以不传入这个参数,默认就是100。

BackgroundJob.Enqueue 方法里不能省略这个参数,不然会报错说编译器无法解析啥的,这个应该是C#的语言限制,具体我暂时还没去深入研究。

小结

OK,这样就初步搞定了数据采集 & 定时采集的功能,这部分刚好是我国庆第一天加班完成的,后续的就交给时间吧~ 国庆剩下几天的假期让它跑个够,等假期结束再回去看看效果如何,到时有新的进展我也会及时更新博客。

对了,我还打算封装个异步任务和定时任务的接口(似乎 AspNetCore 没有这部分功能?),因为我不想代码和 Hangfire 有太高的耦合,封装成抽象的接口,以后如果换别的组件也没有压力。

就把这件事先加入 todo list 吧~

参考资料

Asp-Net-Core开发笔记:集成Hangfire实现异步任务队列和定时任务的更多相关文章

  1. ASP.NET Core开发-后台任务利器Hangfire使用

    ASP.NET Core开发系列之后台任务利器Hangfire 使用. Hangfire 是一款强大的.NET开源后台任务利器,无需Windows服务/任务计划程序. 可以使用于ASP.NET 应用也 ...

  2. 在CentOS7 开发与部署 asp.net core app笔记

    原文:在CentOS7 开发与部署 asp.net core app笔记 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lihongzhai/art ...

  3. Core开发-后台任务利器Hangfire使用

    Core开发-后台任务利器Hangfire使用 ASP.NET Core开发系列之后台任务利器Hangfire 使用. Hangfire 是一款强大的.NET开源后台任务利器,无需Windows服务/ ...

  4. windows/Linux下设置ASP.Net Core开发环境并部署应用

    10分钟学会在windows/Linux下设置ASP.Net Core开发环境并部署应用 创建和开发ASP.NET Core应用可以有二种方式:最简单的方式是通过Visual Studio 2017 ...

  5. 2月送书福利:ASP.NET Core开发实战

    大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...

  6. [转]ASP.NET Core 开发-Logging 使用NLog 写日志文件

    本文转自:http://www.cnblogs.com/Leo_wl/p/5561812.html ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 . ...

  7. ASP.NET Core 开发-中间件(Middleware)

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

  8. ASP.NET Core开发-Docker部署运行

    ASP.NET Core开发Docker部署,.NET Core支持Docker 部署运行.我们将ASP.NET Core 部署在Docker 上运行. 大家可能都见识过Docker ,今天我们就详细 ...

  9. ASP.NET Core开发-读取配置文件Configuration

    ASP.NET Core 是如何读取配置文件,今天我们来学习. ASP.NET Core的配置系统已经和之前版本的ASP.NET有所不同了,之前是依赖于System.Configuration和XML ...

随机推荐

  1. NOI / 2.1基本算法之枚举-8760:Cantor表

    总时间限制: 1000ms 内存限制: 65536kB 描述 现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的.他是用下面这一张表来证明这一命题的: 我们以Z字形给上表的每一项编 ...

  2. selenium环境配置和八大元素定位

    一.环境配置 1.selenium下载安装 安装一:pip install selenium(多数会超时安装失败) 安装二:pip install -i https://pypi.tuna.tsing ...

  3. cordova 发送ajax请求的相关配置

    <access origin="*" /> <preference name="scheme" value="http" ...

  4. python:GUI图形化数据库巡检工具

    问题描述:时间过得真快,一眨眼又一个月过去,2022又过去大半,7月的尾巴,终于稍微做出来点 东西,本人也不是开发,也是在不断学习的一枚小白.这次使用tkinter制作了一个mysql的巡检工具,使用 ...

  5. Mybatis的使用(1)

    1:新建maven项目,file->project->maven 2:在建好的maven项目中,打开pom.xml文件,加入mybatis所需要的依赖: <!-- mybatis核心 ...

  6. 使用Fiddler劫持网络资源为前端开发助力(示例:Dynamic CRM 表单开发 也能热更新? )

    背景: 使用过vue开发的童鞋应该都知道,在开发vue项目的过程中,有个叫"热更新"的功能特别爽,在传统html开发到初次接触vue时,才发现原来前端开发可以这么香.热更新的表现形 ...

  7. 深入理解 Spring 事务:入门、使用、原理

    大家好,我是树哥. Spring 事务是复杂一致性业务必备的知识点,掌握好 Spring 事务可以让我们写出更好地代码.这篇文章我们将介绍 Spring 事务的诞生背景,从而让我们可以更清晰地了解 S ...

  8. js实现多条件排序

    1.sort排序允许接受一个参数(函数),这个函数接受2个形参a,b,并且通过冒泡的方式比较.例子:Arr.sort((a,b) => a-b);   升序排列Arr.sort((a,b) =& ...

  9. 【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?

    问题描述 在使用Azure Spring Cloud服务时,如果要收集应用程序的日志.有控制台输出(实时流日志),也可以配置Log Analytics服务. 日志流式处理 可以通过以下命令在 Azur ...

  10. HiveSql调优系列之Hive严格模式,如何合理使用Hive严格模式

    目录 综述 1.严格模式 1.1 参数设置 1.2 查看参数 1.3 严格模式限制内容及对应参数设置 2.实际操作 2.1 分区表查询时必须指定分区 2.2 order by必须指定limit 2.3 ...