之前用户反映了一个Redis API V3的现网问题,用户在使用API V3接口去修改实例的自动备份配置(接口名:ModifyAutoBackupConfig)的时候,出现了设置不生效的问题,用户请求Request如下:
{
"InstanceId":"crs-3hqwaohz",
"WeekDays":[
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
],
"TimePeriod":"00:00-01:00"
}
该接口功能是将实例crs-3hqwaohz的自动备份时间设置为每天晚上的00:00至01:00之间,用户得到如下Response:
{
"Response":{
"AutoBackupType":1,
"WeekDays":[
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
],
"TimePeriod":"00:00-01:00",
"RequestId":"d2671fb8-08e5-432e-b7ca-79bee157f676"
}
}
单纯从Response看,应该是成功设置了用户的备份时间,但是问题出在用户重新使用查询实例备份配置接口的时候,获取到的信息却是没有更新时间的,备份时间并没有更新为每天的0点到1点,而是依旧使用的老配置,6点到7点备份。查询结果如下显示:
{
"Response":{
"AutoBackupType":1,
"WeekDays":[
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
],
"TimePeriod":"06:00-07:00",
"RequestId":"e6a43f93-a73a-44f2-b984-0f02ed1c5098"
}
}
这里的问题简单概括为,修改接口使用成功,查询接口也没有报错;而且修改接口的Response数据是正确的,但是查询接口返回的数据内容则是有问题的;那么首先分析,用户两次操作之间多调用了一次修改接口,才出现了这个问题。然而很快通过分析日志发现并没有出现这种用户使用的问题,日志如下:
2019/05/08 15:44:25 service_tencentcloud_redis.go:611: [DEBUG]-1 api[ModifyAutoBackupConfig] , request body [{"InstanceId":"crs-3hqwaohz","WeekDays":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"TimePeriod":"00:00-01:00"}], response body[{"Response":{"AutoBackupType":1,"WeekDays":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"TimePeriod":"00:00-01:00","RequestId":"d2671fb8-08e5-432e-b7ca-79bee157f676"}}]
2019/05/08 15:44:25 service_tencentcloud_redis.go:632: [DEBUG]-2 api[DescribeAutoBackupConfig] , request body [{"InstanceId":"crs-3hqwaohz"}], response body[{"Response":{"AutoBackupType":1,"WeekDays":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"TimePeriod":"06:00-07:00","RequestId":"e6a43f93-a73a-44f2-b984-0f02ed1c5098"}}]
那么可以确定的是接口有Bug存在,尝试现网/测试环境进行复现,发现均不能复现用户问题,似乎不是一个稳定复现的问题,这条思路暂时断掉;那么接下来,另外一个思路,先摸清实例当前的具体情况,需要确认用户操作的实例备份配置后端真正设置到底是几点备份,通过查看后端数据库获取后端实例信息:
这里发现备份的开始时间有问题,开始时间没有被设置成应该的值:0,而是保留了实例原来的旧值:6;另一方面,备份的结束时间却是正确的1。那么问题来了,实例在设置开始时间的时候,这个字段没有成功而且也没有报错。那么到这里推测是SQL语句有问题,查看日志,找到对应的SQL语句:
UPDATE redis_oss.redis_autobackup_config SET endtime = 1 where instanceId = 60000687;
发现只更新了结束时间,没有更新开始时间,那么这里SQL有问题?为什么自己尝试复现的时候没有问题?于是查看设置成功的时候的SQL语句:
UPDATE redis_oss.redis_autobackup_config SET starttime = 6 , endtime = 7 where instanceId = 60000687;
发现这条语句是正常的,难道是更新字段的值不同导致的?于是重新尝试复现,和用户使用的时间一致,更新为0点到1点备份,结果发现,果然只有在设置成这个时间,SQL语句生成才是有问题的。
于是,既然能稳定复现这个问题了,基本也就确认了问题出现的位置,于是查看代码,找到这条SQL语句生成的地方,代码如下:
/**
记录redis自动备份周期与时间,没有设置就设置自动备份
*/
func CreateOrUpdateAutoBackupConfig(autoBackupConfig *db_model.RedisAutobackupConfig) (err error) {
defer func() {
if errs := recover(); errs != nil {
log.Error("RecordAutoBackupConfig recover err, autoBackupConfig: ", autoBackupConfig, ", err:", errs)
err = NewRuntimeError(GetErrMsg(errs))
return
}
}()
preAutoBackupConfig, err, isFound := GetAutoBackupConfigByInstanceId(autoBackupConfig.InstanceID)
if !isFound { // 没有找到,则插入
return GetTxManager().GetOrmDB().Save(autoBackupConfig).Error
}
if preAutoBackupConfig != nil {
err = GetTxManager().GetOrmDB().Model(&db_model.RedisAutobackupConfig{}).Where(" instanceId=? and status=?",
autoBackupConfig.InstanceID, redis_model.AUTO_BACKUP_VALID).Update(autoBackupConfig).Error
if err != nil {
return err
}
}
return errors.New("CreateOrUpdateAutoBackupConfig error")
}
这里利用了Gorm的Update方法来更新数据库记录,使用上来说也非常方便,只是将对象传入Update方法,即可轻松的更新数据库记录,那么也只可能是这里出现了问题,于是分析,Gorm在更新数据库记录的实例,为什么生成的SQL语句的更新字段会不一致呢?很有可能是Gorm有自己的更新字段逻辑判断,依据某一种规则来判断是否需要更新某一个字段;于是,通过查询资料了解了Gorm的字段更新策略,发现Gorm使用上的一个坑,Gorm对于对象的更新策略:
由于用户设置了开始时间为0,而该字段在对象中表示为int类型,如下所示:
type RedisAutobackupConfig struct {
AlarmTime string `gorm:"column:alarmTime"`
AutoBackupType int `gorm:"column:autobackuptype"`
BackupOneHour int64 `gorm:"column:backupOneHour"`
CreateTime string `gorm:"column:createtime"`
EndTime int `gorm:"column:endtime"`
ID int64 `gorm:"column:id;primary_key"`
InstanceID int64 `gorm:"column:instanceId"`
Operator string `gorm:"column:operator"`
StartTime int `gorm:"column:starttime"`
Status int `gorm:"column:status"`
SysConf int `gorm:"column:sysConf"`
UpdateTime string `gorm:"column:updatetime"`
UserType int `gorm:"column:usertype"`
Weekdays string `gorm:"column:weekdays"`
}
// TableName sets the insert table name for this struct type
func (r *RedisAutobackupConfig) TableName() string {
return "redis_autobackup_config"
}
Gorm发现StartTime字段类型为int,int默认值为0,而用户要设置的值也是0,这里Gorm认为默认值不需要更新,字段也就没有更新,完美解释上述情况和问题。
难道Gorm这么坑?不能设置默认值0?当然有启发解决方式,最直接的方法便是直接使用手写SQL,强制执行;但这样就失去了使用Gorm的意义。那么能否使用其他方式指定需要更新的字段呢?最终选择了另外一个方式:Map;即Update方法不再使用对象去更新,而是使用Map指定字段更新,代码修复如下:
updateMap := make(map[string]interface{}, 0)
updateMap["StartTime"] = autoBackupConfig.StartTime
updateMap["EndTime"] = autoBackupConfig.EndTime
GetTxManager().GetOrmDB().Model(&db_model.RedisAutobackupConfig{}).Where(" instanceId=? and status=?",autoBackupConfig.InstanceID,redis_model.AUTO_BACKUP_VALID).Update(updateMap).Error
这样便解决了问题。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。