Asp-Net-Core开发笔记:集成Hangfire实现异步任务队列和定时任务
前言
最近把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 吧~
参考资料
- https://docs.hangfire.io/en/latest/index.html
- https://codewithmukesh.com/blog/hangfire-in-aspnet-core-3-1/
- https://github.com/gonzigonz/HangfireCore-Example
Asp-Net-Core开发笔记:集成Hangfire实现异步任务队列和定时任务的更多相关文章
- ASP.NET Core开发-后台任务利器Hangfire使用
ASP.NET Core开发系列之后台任务利器Hangfire 使用. Hangfire 是一款强大的.NET开源后台任务利器,无需Windows服务/任务计划程序. 可以使用于ASP.NET 应用也 ...
- 在CentOS7 开发与部署 asp.net core app笔记
原文:在CentOS7 开发与部署 asp.net core app笔记 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lihongzhai/art ...
- Core开发-后台任务利器Hangfire使用
Core开发-后台任务利器Hangfire使用 ASP.NET Core开发系列之后台任务利器Hangfire 使用. Hangfire 是一款强大的.NET开源后台任务利器,无需Windows服务/ ...
- windows/Linux下设置ASP.Net Core开发环境并部署应用
10分钟学会在windows/Linux下设置ASP.Net Core开发环境并部署应用 创建和开发ASP.NET Core应用可以有二种方式:最简单的方式是通过Visual Studio 2017 ...
- 2月送书福利:ASP.NET Core开发实战
大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...
- [转]ASP.NET Core 开发-Logging 使用NLog 写日志文件
本文转自:http://www.cnblogs.com/Leo_wl/p/5561812.html ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 . ...
- ASP.NET Core 开发-中间件(Middleware)
ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...
- ASP.NET Core开发-Docker部署运行
ASP.NET Core开发Docker部署,.NET Core支持Docker 部署运行.我们将ASP.NET Core 部署在Docker 上运行. 大家可能都见识过Docker ,今天我们就详细 ...
- ASP.NET Core开发-读取配置文件Configuration
ASP.NET Core 是如何读取配置文件,今天我们来学习. ASP.NET Core的配置系统已经和之前版本的ASP.NET有所不同了,之前是依赖于System.Configuration和XML ...
随机推荐
- .Net CLR R2R编译的原理简析
前言 躺平了好一段时间了,都懒得动了.本文均为个人理解所述,如有疏漏,请指正. 楔子 金庸武侠天龙八部里面,少林寺至高无上的镇寺之宝,武林人士梦寐以求的内功秘笈易筋经被阿朱偷了,但是少林寺也没有大张旗 ...
- zabbix监控添加学习笔记
在实际生产环境中,除了CPU.内存等一些系统信息可以挂载zabbix的自带模板Template OS Linux:但是一些公司开发的定制服务需要自己写模板或者监控项去监控: 一.监控公司的java服务 ...
- Mybatis的使用(1)
1:新建maven项目,file->project->maven 2:在建好的maven项目中,打开pom.xml文件,加入mybatis所需要的依赖: <!-- mybatis核心 ...
- Linux 系统时间同步服务器配置
# Linux 时间同步 # 查看系统时间: date # 查看硬件日期 # ntp 软件 # chrony 软件 chrony比ntp更精确 # 利用ntp手动瞬间同步时间: ntpdate 172 ...
- Pinhole类声明和实现
针孔相机,带旋转,移动等功能. 类声明: #pragma once #ifndef __PINHOLE_HEADER__ #define __PINHOLE_HEADER__ #include &qu ...
- python-GUI键盘小工具
一.tkinter GUI界面 二.实现功能 连接设备.设备上电.设备使能.键盘按键控制关节移动.配置关节移动速度和角度 三.python源码 1 #coding=utf-8 2 import ms ...
- Word 脚注和尾注是什么?怎么设置?
描述 脚注一般位于页面的底部,作为文档某处内容的注释.尾注一般位于文档的末尾,列出引文的出处等. 设置脚注和尾注 将光标移动到要插入脚注或尾注的地方,然后点击"引用"选项卡. 左边 ...
- RabbitMQ 入门系列:6、保障消息:不丢失:发送方、Rabbit存储端、接收方。
系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...
- 究竟什么是Shadow DOM?
shadow dom 是什么? 顾名思义,shadow dom直译的话就是影子dom,但我更愿把它理解为DOM中的DOM.因为他能够为Web组件中的 DOM和 CSS提供了封装,实际上是在浏览器渲染文 ...
- 如何通过C#/VB.NET设置Word文档段落缩进
缩进是指调整文本与页面边界之间的距离.在水平标尺,有四个段落缩进滑块:首行缩进.悬挂缩进.左缩进以及右缩进.在对于word文档的录入时,常常需要注意录入的格式,通过合理地设置段落格式,可以让文稿看起来 ...