gozero api服务调用rpc服务,rpc服务返回值为一个数据类型指针,一个error,踩坑,将两个值都设成nil返回时,在api中接收到的是nil+一个非nil的报错信息(deadline),因此,如果rpc未查到信息,正确返回值设置为数据类型的指针(其中的数据设为空数组之类的)+nil
在查询用户数据时,将该用户粉丝数和关注数存入redis,并设置过期时间为50分加10分内的一个随机值,防止缓存雪崩,在有关注操作时,判断redis中是否有对应的数据,有的话对redis数据进行更新,并刷新过期时间。
今日工作:
-
编写rpc-获取用户作品数点赞数(简单的查询数据库count),但要在user服务中新增content数据库的配置,不然访问不到数据库
-
修复获赞数不兼容用户作品为空的bug,修复评论列表不兼容评论列表为空的bug,这两个不兼容为空的bug都一样,就是在sql有in操作,但是为空的情况下,in()中没有值,sql报错,这块判断最开始列表为空就直接返回了,解决了bug还顺便优化了性能。
-
编写用户信息redis宕机容灾,考虑到业务比较简单,多考虑一些极端情况,当redis服务宕机了之后(由于没做哨兵机制),数据要从数据库中取,取到后再回写redis(兼容暂时宕机)。
关注任务,之前考虑如果并发太高,采用开启协程用time.NewTicker的方式延迟执行,后来发现采用go关键字开启协程的方式不可行,因为协程往往要等待延迟的时间后才会结束,而主线程早就执行完毕,协程被迫死亡
go func(userId, followId string) { //新开协程执行延迟写的操作
randomInterval := time.Duration(rand.Int63n(int64(10 * time.Minute))) //10分钟内的随机时间
ticker := time.NewTicker(randomInterval) //延迟执行写入数据库
OuterLoop:
for {
select {
case _ = <-ticker.C:
_, err := l.svcCtx.UserRpcClient.AddFollows(l.ctx, &pb.AddFollowsReq{
UserId: userId,
FollowId: followId,
})
if err == nil { //确实写进去了再结束
ticker.Stop()
break OuterLoop
}
}
}
}(strconv.Itoa(int(logger.UserID)), strconv.FormatInt(req.ToUserId, 10))
改用time.AfterFunc的方式,不阻塞并延迟执行,同时使用time.ticker,用于写入失败后的重试
time.AfterFunc(randomInterval, func() {
userId := strconv.Itoa(int(logger.UserID))
followId := strconv.FormatInt(req.ToUserId, 10)
_, err := l.svcCtx.UserRpcClient.AddFollows(l.ctx, &pb.AddFollowsReq{
UserId: userId,
FollowId: followId,
})
if err != nil { //没写进去则隔一段时间重试
randomInterval := time.Duration(rand.Int63n(int64(20 * time.Second))) //20s内的随机时间
ticker := time.NewTicker(randomInterval) //延迟执行写入数据库
OuterLoop:
for {
select {
case _ = <-ticker.C:
_, err := l.svcCtx.UserRpcClient.AddFollows(l.ctx, &pb.AddFollowsReq{
UserId: userId,
FollowId: followId,
})
if err == nil { //确实写进去了再结束
ticker.Stop()
break OuterLoop
}
}
}
}
})
time.AfterFunc和go关键字用于不同的目的,有以下区别:
- 功能不同:
time.AfterFunc用于在指定的时间间隔后执行一个函数,而go关键字用于开启一个新的并发协程来执行函数。 - 阻塞行为不同:
time.AfterFunc是非阻塞的,即它会创建一个定时器并立即返回,不会阻塞当前的程序流程,而go关键字会启动一个新的并发协程,会在协程的函数执行完成之前阻塞当前的程序流程。 - 控制能力不同:
time.AfterFunc提供了对定时器的控制,可以通过返回的*Timer类型值调用Stop()方法来取消定时器。而go关键字启动的协程在启动后就无法对其进行直接的控制,除非使用一些其他的同步机制,如通道、互斥锁等。 - 使用场景不同:
time.AfterFunc适用于需要在一定时间后执行某个操作的场景,例如定时任务、延迟执行等。而go关键字适用于需要并发执行的场景,可以同时执行多个函数或任务。
综上所述,time.AfterFunc和go关键字用于不同的场景和目的,选择使用哪个取决于具体的需求。如果需要在指定时间后执行某个函数,可以使用time.AfterFunc;如果需要并发执行函数或任务,可以使用go关键字开启协程。