Go 分布式任务和定时任务太难?sasynq 让异步任务从未如此简单

后台任务的那些"痛点",你中了几枪?

在 Go 应用里,总有些任务不适合当场做完,比如:

  • 发送邮件/短信:总不能让用户点一下按钮,就一直眼巴巴地盯着屏幕等着"发送成功"吧?
  • 处理耗时计算:比如生成报表、分析数据,总不能让 CPU 一直被占着,其他请求都卡住吧?
  • 定时"叫魂" :每天凌晨的统计任务、每小时的数据同步,难道要我们自己写个无限循环的 time.Sleep 吗?

这些任务,通常会把它们扔进一个"异步任务队列"里,让后台的"工人(Worker)"慢慢去处理。

听起来很美好,但自己动手搞一套,或者用一些比较"原始"的库,马上就会遇到新的麻烦:

  • Goroutine 泛滥 :任务一多,是不是就疯狂 go func()?然后发现成千上万的 Goroutine 难以管理,调度压力山大。
  • 失败了怎么办:网络抖动、服务暂时不可用,任务失败了就失败了?当然不行!得有重试机制。
  • 任务也分三六九等:关键的支付通知任务,和普通的日志记录任务,能享受同等待遇吗?必须得有优先级!
  • 代码越写越乱:定义任务、序列化、反序列化、注册处理器...... 一套流程下来,业务代码和队列逻辑混在一起,简直是一团乱麻。

如果你对以上任何一点感到"是我了",那么,恭喜你,sasynq 就是为你量身定做的"解药"!

sasynq 是什么?

简单来说,sasynq 是一个基于大名鼎鼎的 asynq 库的封装。 asynq 本身已经很优秀了,它是一个基于 Redis 的分布式任务队列,稳定又高效。

觉得asynq 还能更简单、更"傻瓜"一点!sasynq 的目标就是提供一个更简单、更用户友好的 SDK ,同时又完全兼容原生 asynq 的所有高级玩法。

它有什么绝活呢?

  • 开箱即用:支持 Redis Cluster 和 Sentinel,高可用、高扩展性,再也不用担心你的任务队列"单点故障"了。
  • 功能全面:优先级队列、延迟队列、任务去重、定时任务,这些复杂的调度功能,它都帮你做好了。
  • 安全可靠:任务重试、超时、截止时间(Deadline),各种"保险丝"都给你装上了,保证任务"要么成功,要么可控"。
  • API 极简 :这是最关键的!它把 asynq 的使用流程大大简化,让你能用更清爽、更优雅的方式编写代码。

sasynq 到底有多好用?

口说无凭,代码为证,直接上代码,看看 sasynq 是如何把复杂的事情变简单的。

第一步:定义你的任务------三种姿势,任君选择

sasynq 里,定义一个任务和它的处理器,就像呼吸一样自然。它甚至贴心地提供了三种方式:

go 复制代码
// example/common/task.go
package common

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/hibiken/asynq"
	"github.com/go-dev-frame/sponge/pkg/sasynq"
)

// ----------------------------- 姿势一 (推荐!) ----------------------------------
const TypeEmailSend = "email:send"

// 任务长啥样
type EmailPayload struct {
	UserID  int    `json:"user_id"`
	Message string `json:"message"`
}

// 任务怎么做
func HandleEmailTask(ctx context.Context, p *EmailPayload) error {
	fmt.Printf("[Email] 嗨,%d号用户,你的邮件发送成功啦!\n", p.UserID)
	return nil
}

// ----------------------------- 姿势二 ----------------------------------
const TypeSMSSend = "sms:send"

type SMSPayload struct {
	UserID  int    `json:"user_id"`
	Message string `json:"message"`
}

// 让 Payload 自己实现处理方法
func (p *SMSPayload) ProcessTask(ctx context.Context, t *asynq.Task) error {
	fmt.Printf("[SMS] 喂,%d号用户,你的短信已送达!\n", p.UserID)
	return nil
}

// ----------------------------- 姿势三 ----------------------------------
const TypeMsgNotification = "msg:notification"

type MsgNotificationPayload struct {
	UserID  int    `json:"user_id"`
	Message string `json:"message"`
}

// 最原始的方式,自己解析 Payload
func HandleMsgNotificationTask(ctx context.Context, t *asynq.Task) error {
	var p MsgNotificationPayload
	if err := json.Unmarshal(t.Payload(), &p); err != nil {
		return fmt.Errorf("failed to unmarshal payload: %w", err)
	}
	fmt.Printf("[MSG] 报告!给 %d号用户的消息通知搞定!\n", p.UserID)
	return nil
}

看到了吗?尤其是第一种方法,直接把 Payload 类型和处理函数分离开 ,代码结构是不是超级清晰?你再也不用在处理函数里写那段烦人的 json.Unmarshal 了!

第二步:生产任务------想怎么扔,就怎么扔

作为"生产者",你的任务就是把活儿派发出去。sasynq 提供了极其流畅的 API,让你随心所欲地派发任务。

go 复制代码
// example/producer/main.go
package main

import (
	"fmt"
	"time"

	"github.com/go-dev-frame/sponge/pkg/sasynq"
	"example/common"
)

func runProducer(client *sasynq.Client) error {
	// 立刻执行!还是十万火急的那种!
	payload1 := &common.EmailPayload{UserID: 101, Message: "这个任务最重要!"}
	_, _, err := client.EnqueueNow(common.TypeEmailSend, payload1,
		sasynq.WithQueue("critical"), // 扔到"紧急"队列
		sasynq.WithRetry(5),          // 失败了给我重试5次!
	)
	if err != nil { return err }

	// 5秒后执行,优先级一般
	payload2 := &common.SMSPayload{UserID: 202, Message: "这个任务不着急..."}
	_, _, err = client.EnqueueIn(5*time.Second, common.TypeSMSSend, payload2,
		sasynq.WithQueue("default"),
		sasynq.WithRetry(3),
	)
	if err != nil { return err }

	// 10秒后执行,优先级最低
	payload3 := &common.MsgNotificationPayload{UserID: 303, Message: "有空再做吧"}
	_, _, err = client.EnqueueAt(time.Now().Add(10*time.Second), common.TypeMsgNotification, payload3,
		sasynq.WithQueue("low"),
		sasynq.WithRetry(1),
	)
	if err != nil { return err }

	// 独一无二的任务,别重复执行!
	payload4 := &common.EmailPayload{UserID: 404, Message: "这个任务15秒内必须做完!"}
	task, _ := sasynq.NewTask(common.TypeEmailSend, payload4)
	_, err = client.Enqueue(task,
		sasynq.WithQueue("low"),
		sasynq.WithDeadline(time.Now().Add(15*time.Second)), // 设置截止时间
		sasynq.WithUniqueID("unique-id-xxxx-xxxx"),        // 设置唯一ID
	)
	if err != nil { return err }

	return nil
}

EnqueueNow, EnqueueIn, EnqueueAt,这命名,是不是看一眼就知道是干嘛的?通过链式调用的 sasynq.WithXXX 选项,设置队列、重试次数、截止时间都变得异常直观。

第三步:消费任务------注册处理器,so easy!

有了任务,就得有"工人"来干活。sasynq 的消费端(Server)注册处理器也同样简单到令人发指。

go 复制代码
// example/consumer/main.go
package main

import (
	"github.com/go-dev-frame/sponge/pkg/sasynq"
	"example/common"
)

func runConsumer(redisCfg sasynq.RedisConfig) (*sasynq.Server, error) {
	// 默认就给你分好了 critical, default, low 三个队列
	serverCfg := sasynq.DefaultServerConfig()
	srv := sasynq.NewServer(redisCfg, serverCfg)

	// 注册处理器,注册的三种方式分别对应定义payload的三种定义
	// 方式一 (强烈推荐!)
	sasynq.RegisterTaskHandler(srv.Mux(), common.TypeEmailSend, sasynq.HandleFunc(common.HandleEmailTask))
	// 方式二
	srv.Register(common.TypeSMSSend, &common.SMSPayload{})
	// 方式三
	srv.RegisterFunc(common.TypeMsgNotification, common.HandleMsgNotificationTask)

	srv.Run()
	return srv, nil
}

看看 RegisterTaskHandler,它优雅地将任务类型和我们之前定义的 HandleEmailTask 绑定在一起。整个过程行云流水,没有任何多余的胶水代码。

定时任务------你的专属小闹钟

需要每隔几秒、几分钟、几小时就执行一次任务?sasynqScheduler 让你轻松搞定!

go 复制代码
package main

// ... (省略定义和处理器)

func registerSchedulerTasks(scheduler *sasynq.Scheduler) error {
	// 每2秒去"骚扰"一次谷歌
	payload1 := &ScheduledGetPayload{URL: "https://google.com"}
	scheduler.RegisterTask("@every 2s", TypeScheduledGet, payload1)

	// 每3秒去"问候"一下必应
	payload2 := &ScheduledGetPayload{URL: "https://bing.com"}
	scheduler.RegisterTask("@every 3s", TypeScheduledGet, payload2)

	scheduler.Run()
	return nil
}

只需要一行 scheduler.RegisterTask,传入一个 Cron 表达式(@every 2s 这种简单格式也支持),剩下的事情就交给 sasynq 吧!

结论

sasynq 通过巧妙的封装和设计,它隐藏了 asynq 底层复杂的细节,使用更为简单易用。

它解决了我们日常开发中关于异步任务的种种痛点:代码臃肿、配置复杂、流程不清 。使用 sasynq,你可以更专注于业务逻辑本身,而不是和任务队列的实现细节"斗智斗勇"。

如果你正在寻找一个能在 Go 项目中快速、安全地实现异步和分布式任务处理的方案,那么 sasynq 绝对是你的不错的选择。它能帮你写出更整洁、更可维护的后台任务代码,让你的后台任务也享受到"丝般顺滑"的体验!


sasynq 地址:github.com/go-dev-frame/sponge/pkg/sasynq

sasynq 是 Sponge 框架的一个子组件,Sponge 是一个强大且易用的 Go 开发框架,其核心理念是 定义即代码 (Definition is Code),帮助开发者以"低代码"方式轻松构建稳定可靠的高性能后端服务(包括 RESTful API、gRPC、HTTP+gRPC、gRPC Gateway 等)。

👉 Sponge 项目地址github.com/go-dev-fram...

相关推荐
金融数据出海20 小时前
java对接美股股票api涵盖实时行情、K 线、指数等核心接口。
后端
认真的小羽❅20 小时前
从入门到精通:Spring Boot 整合 MyBatis 全攻略
spring boot·后端·mybatis
摆烂工程师20 小时前
教你如何查询 Codex 最新额度是多少,以及 ChatGPT Pro、Plus、Business 最新额度变化
前端·后端·ai编程
任聪聪20 小时前
我做了一款通用本地化部署模型运行调度器,运行所有大模型!
后端
开发者如是说21 小时前
可能是最好用的多语言管理工具
android·前端·后端
苗苗大佬21 小时前
学习go语言
go
何陋轩1 天前
AI时代,程序员何去何从?别慌,看完这篇你就明白了
后端·面试
weixin_408099671 天前
OCR 识别率提升实战:模糊 / 倾斜 / 反光图片全套优化方案(附 Python / Java / PHP 代码)
图像处理·人工智能·后端·python·ocr·api·抠图
weixin_408099671 天前
【实战教程】懒人精灵如何实现 OCR 文字识别?接口调用完整指南(附可运行示例)
java·前端·人工智能·后端·ocr·api·懒人精灵
珍朱(珠)奶茶1 天前
Spring Boot3整合Jxls工具包实现模版excel导出文件
spring boot·后端·excel