异步延迟双删

239 阅读3分钟

异步延迟双删

  • 取消视频点赞及取消关注好友
  • 使用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