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...

相关推荐
武子康1 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
舒一笑2 小时前
我的开源项目-PandaCoder迎来史诗级大更新啦
后端·程序员·intellij idea
@昵称不存在3 小时前
Flask input 和datalist结合
后端·python·flask
东林牧之4 小时前
Django+celery异步:拿来即用,可移植性高
后端·python·django
超浪的晨4 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
AntBlack5 小时前
从小不学好 ,影刀 + ddddocr 实现图片验证码认证自动化
后端·python·计算机视觉
Pomelo_刘金5 小时前
Clean Architecture 整洁架构:借一只闹钟讲明白「整洁架构」的来龙去脉
后端·架构·rust
双力臂4045 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
midsummer_woo7 小时前
基于spring boot的医院挂号就诊系统(源码+论文)
java·spring boot·后端