配置redis适用与golang云原生架构。包括redis与数据库一致性等重要内容
1、编写redis配置文件、使用viper读取
- 配置文件
db.yml
yml
redis:
addr: 127.0.0.1
port: 6379
password: tiktokRedis
db: 0 # 数据库编号
- 读取配置文件
go
var (
config = viper.Init("db")
zapLogger = zap.InitLogger()
redisOnce sync.Once
redisHelper *RedisHelper
FavoriteMutex *redsync.Mutex
RelationMutex *redsync.Mutex
)
2、新建并初始化一个redisClient
-
新建
- 其中sync.Once 是 Go 标准库中提供的一个同步原语,用于保证某个操作仅执行一次。
调用 redisOnce.Do() 方法,传入一个函数作为参数。Do() 方法会判断该函数是否已经被执行过,如果没有,则执行该函数。在这个例子中,函数中的代码如下:
gordh := new(RedisHelper) rdh.Client = rdb redisHelper = rdh
在这段代码中,首先创建了一个 RedisHelper 对象,并将 rdb 赋值给其中的 Client 字段。然后,将 rdh 赋值给 redisHelper 变量。redisHelper 是一个全局变量,用于保存单例实例。
通过这样的方式,可以保证在多个并发请求中,只有第一次执行 Do() 方法时会初始化 Redis 连接单例,后续的请求都会直接返回已经初始化好的连接对象。
- 其中sync.Once 是 Go 标准库中提供的一个同步原语,用于保证某个操作仅执行一次。
go
func NewRedisHelper() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%s", config.Viper.GetString("redis.addr"), config.Viper.GetString("redis.port")),
Password: config.Viper.GetString("redis.password"),
DB: config.Viper.GetInt("redis.db"),
DialTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
//MaxConnAge: 1 * time.Minute, go-redis v9 已删去
PoolSize: 10,
PoolTimeout: 30 * time.Second,
})
redisOnce.Do(func() {
rdh := new(RedisHelper)
rdh.Client = rdb
redisHelper = rdh
})
return rdb
}
-
初始化
- 为什么需要传入 ctx?
控制请求的超时和取消 :通过传入 ctx,可以设置请求的超时时间,当超过指定时间后,可以自动取消请求,避免无限等待。这对于处理网络请求或长时间运行的操作非常重要,可以确保你的程序具有良好的健壮性,并能及时释放资源。
传递请求特定的值 :ctx 可以用于在请求的不同组件之间传递请求特定的值,如身份验证令牌、用户信息等。通过使用 ctx,可以将这些值以一种可靠且类型安全的方式传递给相关的函数和方法。
协调并发请求:在并发请求的场景中,ctx 可以用于协调各个请求的完成状态。你可以通过 ctx 来等待所有请求完成或收集并汇总结果。
- 为什么需要传入 ctx?
go
func init() {
ctx := context.Background()
rdb := NewRedisHelper()
//典型测试连接的代码
if _, err := rdb.Ping(ctx).Result(); err != nil {
zapLogger.Fatalln(err.Error())
return
}
zapLogger.Info("Redis server connection successful!")
// 开启定时同步至数据库
GoCronFavorite()
GoCronRelation()
zapLogger.Info("MySQL synchronization is enabled.")
// Redis锁
// 创建Redis连接池
pool := goredis.NewPool(rdb)
// Create an instance of redisync to be used to obtain a mutual exclusion lock.
rs := redsync.New(pool)
// Obtain a new mutex by using the same name for all instances wanting the same lock.
FavoriteMutex = rs.NewMutex("mutex-favorite")
RelationMutex = rs.NewMutex("mutex-relation")
}
3. redis和mysql的一致性
使用定时同步的思想,将redis中的数据定时同步到数据库中
3.1 异步定时任务的执行
具体做法:
- 使用了 GoCron 库来创建一个定时任务,每隔一定频率执行 RedisDataMoveToDB 函数。
- 首先,通过 gocron.NewSchedule() 创建了一个调度器对象 s。
- 接下来,调用 s.Every(frequency) 方法设置定时任务的执行频率,frequency 是一个时间间隔。这里使用 .Seconds() 方法表示以秒为单位进行间隔设置。
- 然后,通过 .Tag("favoriteRedis") 方法给定时任务添加了一个标签,用于标识该任务,以便后续操作或管理。
- 最后,调用 .Do(FavoriteMoveToDB) 方法指定定时任务要执行的函数为 FavoriteMoveToDB,即在每个频率间隔结束时执行该函数。
- 最后一行的 s.StartAsync() 表示以异步方式开始执行定时任务。这会启动一个新的 goroutine 来执行定时任务,而不会阻塞当前的程序执行。这样可以保证定时任务在后台按照设定的频率执行,而不会影响主程序的执行。
go
func GoCronFavorite() {
s := gocron.NewSchedule()
s.Every(frequency).Tag("favoriteRedis").Seconds().Do(FavoriteMoveToDB)
s.StartAsync()
}
其中使用到的gocron:
go
// Schedule 定时任务 gocron
type Schedule struct {
*gocron.Scheduler
}
func NewSchedule() *gocron.Scheduler {
return gocron.NewScheduler(time.Local)
}
3.2 redis同步到数据库
go
func RelationMoveToDB() error {
logger := zap.InitLogger()
ctx := context.Background()
keys, err := getKeys(ctx, "user::*::to_user::*::w")
if err != nil {
logger.Errorln(err)
return err
}
for _, key := range keys {
res, err := GetRedisHelper().Get(ctx, key).Result()
vSplit := strings.Split(res, "::")
_, redisAt := vSplit[0], vSplit[1]
if err != nil {
logger.Errorln(err.Error())
return err
}
// 拆分得key
kSplit := strings.Split(key, "::")
uid, tid := kSplit[1], kSplit[3]
userID, err := strconv.ParseInt(uid, 10, 64)
if err != nil {
logger.Errorln(err.Error())
return err
}
toUserID, err := strconv.ParseInt(tid, 10, 64)
if err != nil {
logger.Errorln(err.Error())
return err
}
// 检查是否存在对应ID
u, err := db.GetUserByID(ctx, userID)
if err != nil {
logger.Errorln(err.Error())
return err
}
tu, err := db.GetUserByID(ctx, toUserID)
if err != nil {
logger.Errorln(err.Error())
return err
}
if u == nil || tu == nil {
delErr := deleteKeys(ctx, key, RelationMutex)
if delErr != nil {
logger.Errorln(delErr.Error())
return delErr
}
continue
}
// 查询是否存在关注记录
relation, err := db.GetRelationByUserIDs(ctx, userID, toUserID)
if err != nil {
logger.Errorln(err.Error())
return err
} else if relation == nil && redisAt == "1" {
// 数据库中没有该关注记录,且最终状态为关注,则插入数据库
err = db.CreateRelation(ctx, userID, toUserID)
// 插入后,删除Redis中对应记录
delErr := deleteKeys(ctx, key, RelationMutex)
if delErr != nil {
logger.Errorln(delErr.Error())
return delErr
}
if err != nil {
logger.Errorln(err.Error())
return err
}
} else if relation != nil && redisAt == "2" {
// 数据库中有该关注记录,且最终状态为取消关注,则从数据库中删除该记录
err = db.DelRelationByUserIDs(ctx, userID, toUserID)
// 删除Redis中对应记录
delErr := deleteKeys(ctx, key, RelationMutex)
if delErr != nil {
logger.Errorln(delErr.Error())
return delErr
}
if err != nil {
logger.Errorln(err.Error())
return err
}
}
// 删除Redis中对应记录
delErr := deleteKeys(ctx, key, RelationMutex)
if delErr != nil {
logger.Errorln(delErr.Error())
return delErr
}
}
return nil
}