异步延迟双删
- 取消视频点赞及取消关注好友
- 使用asynq
用户取消关注
生成关注用户缓存Key的函数GenFollowUserCacheKey
func GenFollowUserCacheKey(userId, followUserId int64) string {
return "follow_user_" + Int642Str(userId) + "_" + Int642Str(followUserId)
}
- 前缀"follow_user_",表示这是关注用户的key
- 传入用户id与被关注用户id
定义删除缓存的异步任务
const TypeDelCache = "cache:del"
type DelCachePayload struct {
Key string
}
func NewDelCacheTask(key string) (*asynq.Task, error) {
payload, err := json.Marshal(DelCachePayload{Key: key})
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal payload for task %q", TypeDelCache)
}
return asynq.NewTask(TypeDelCache, payload), nil
}
- 定义了一个任务类型常量 TypeDelCache = "cache:del",表示这是删除缓存的任务类型。
- 定义了任务的Payload结构DelCachePayload,它包含了要删除的缓存Key。
- NewDelCacheTask函数用来生成一个删除缓存的任务:
- 接收要删除的缓存key作为参数
- 将key封装到DelCachePayload payload中
- 将payload序列化成JSON格式
- 使用asynq.NewTask来创建一个任务,指定任务类型是TypeDelCache,任务参数是序列化后的payload
- 返回生成的任务对象
取消关注用户的逻辑
task, err := mq.NewDelCacheTask(utils.GenFollowUserCacheKey(in.UserId, in.UnFollowUserId))
if err != nil {
logx.WithContext(l.ctx).Errorf("创建入伍失败: %v", err)
return nil, status.Error(rpcErr.MQError.Code, err.Error())
}
if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
return nil, status.Error(rpcErr.MQError.Code, Error())
}
- 调用NewDelCacheTask,传入要删除的缓存key,生成一个asynq的删除缓存任务。
- 对生成任务的错误进行检查,如果有错误则直接返回错误。
- 调用asynq客户端的Enqueue方法,将生成的任务提交到asynq服务器。
- 再次检查Enqueue的错误,如果提交失败则返回错误。
- 使用logx进行错误日志记录。
- 返回错误使用status.Error来设置RPC的错误状态码和信息。
异步延迟双删具体逻辑
func (l *AsynqServer) delCacheHandler(ctx context.Context, t *asynq.Task) error {
var p mq.DelCachePayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
l.Logger.Errorf("json.Unmarshal failed: %v", err)
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
}
if err := l.svcCtx.Redis.Del(ctx, p.Key).Err(); err != nil {
l.Logger.Errorf("redis.Del failed: %v", err)
return err
}
time.Sleep(1 * time.Second)
if err := l.svcCtx.Redis.Del(ctx, p.Key).Err(); err != nil {
l.Logger.Errorf("redis.Del failed: %v", err)
return err
}
return nil
}
- 从asynq任务t中解析出任务Payload,反序列化成DelCachePayload结构体p
- 使用Redis客户端调用Del命令,依据p.Key从Redis删除缓存
- sleep 1秒,再次调用Del命令删除,防止删除失败
- 如果任何步骤有错误,记录日志并返回错误
- 返回nil表示任务执行成功
asynq服务
type AsynqServer struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewAsynqServer(ctx context.Context, svcCtx *svc.ServiceContext) *AsynqServer {
return &AsynqServer{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *AsynqServer) Start() {
fmt.Println("AsynqTask start")
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: l.svcCtx.Config.Redis.Address, Password: l.svcCtx.Config.Redis.Password},
asynq.Config{
// Specify how many concurrent workers to use
Concurrency: 10,
// Optionally specify multiple queues with different priority.
Queues: map[string]int{
"critical": 6,
"default": 3,
"low": 1,
},
// See the godoc for other configuration options
},
)
// mux maps a type to a handler
mux := asynq.NewServeMux()
mux.HandleFunc(mq.TypeTryMakeFriends, l.tryMakeFriendsHandler)
mux.HandleFunc(mq.TypeLoseFriends, l.loseFriendsHandler)
mux.HandleFunc(mq.TypeDelCache, l.delCacheHandler)
mux.HandleFunc(mq.TypeAddCacheValue, l.addCacheValueHandler)
mux.HandleFunc(cron.TypeSyncUserInfoCache, l.syncUserInfoCacheHandler)
mux.HandleFunc(cron.TypeSyncVideoInfoCache, l.syncVideoInfoCacheHandler)
// ...register other handlers...
if err := srv.Run(mux); err != nil {
log.Fatalf("could not run server: %v", err)
}
}
func (l *AsynqServer) Stop() {
fmt.Println("AsynqTask stop")
}
- 定义了AsynqServer结构体,包含了服务上下文,日志等信息
- NewAsynqServer函数实例化了一个AsynqServer
- Start方法中首先创建了一个asynq服务器实例,指定了Redis作为后端以及一些并发等配置
- 然后新建了一个ServeMux(类似net/http的多路复用器)
- 使用HandleFunc方法注册了一系列任务handler函数,对应不同的任务类型,如好友相关任务、缓存相关任务等
- 最后启动asynq服务器,将ServeMux传递给它,这样asynq就会使用这些handler来处理对应的类型任务
- Stop方法用于关闭/停止asynq服务器
Config
- Concurrency - 工作协程数,会创建这个数量的goroutine来并发处理任务。
- Queues - 优先级队列的设置,可以为不同优先级任务指定不同的goroutine数,比如:
- critical: 最高优先级队列,分配了6个goroutine
- default: 正常优先级队列,3个goroutine
- low: 最低优先级队列,1个goroutine