分布式锁处理缓存一致性问题
使用redis做为mysql的缓存,缓存失效时或者后台修改mysql内容时如果并发量大,可能会造成数据库的值与redis缓存值不一致的情况。一般解决方案是采用延迟双删除来处理,本文给出分布式锁的处理方案,压力测试结果,在10万并发的高频读高频写情况下,最终数据库与缓存也能保持数据一致
当一个请求来读取数据时,如果有缓存则直接取走返回,如果没有缓存,则尝试获取一次锁,如果成功,则查询mysql 更新缓存 释放锁,如果没有成功,则查mysql返回结果 (文章之前考虑先查库再去尝试获取锁可能会有问题,就是在两个操作之间有其它客户端完成了对mysql的修改,则又陷入到不一致的情况中)
当一个请求来改数据时,先获取分布式锁,成功后开启mysql事务,修改数据,检查是否还持有锁,如果锁还在自己手上,则马上提交事务,删除一次缓存,释放锁,如果锁不在自己手上则回滚返回修改失败
这样做的原因是尽可能的避免事务提交与锁释放先后顺序的问题
部分代码 查询操作:
func Query(id int) int {
get := pool.Get()
defer get.Close() //从redis池里获取链接与释放一定要放在同一个协程里,否则会有问题
i, err := redigo.Int(get.Do("get", id))
if err != nil {
fmt.Println(err,"query redis err")
}
if i == 0 {
//如果没有缓存 先尝试获取一次锁,如果获取成功,则要担负起改缓存的责任,如果没有,则直接查库返回
a := RedisLock{
LockName: "zp",
LockId: "",
Timeout: 10000,
TimeWait: 0,
}
test2 := Test{}
lock, _ := a.Lock(get)
if lock {
db.Where("id = ?", id).First(&test2)
if test2.ID <=0 {
a.UnLock(get)
return 0
}
_, err := get.Do("setex", id, 100, test2.Age)
if err != nil {
fmt.Println(err)
}
a.UnLock(get)
return test2.Age
}else{
db.Where("id = ?", id).First(&test2)
if test2.ID <=0 {
return 0
}
return test2.Age
}
}else{
return i
}
}
修改操作
func Update(id,num int) (bool,error) {
get := pool.Get()
defer get.Close()
a := RedisLock{
LockName: "zp",
LockId: "",
Timeout: 10000,
TimeWait: 5000,
}
lock, err := a.Lock(get)
if err != nil {
return false,err
}
if lock {
//如果获取到了锁
begin := db.Begin()
t := Test{
}
begin.Model(&t).Where("id = ?",id).Updates(map[string]interface{}{
"age":num,
})
state, err := a.LockState(get)
if err != nil {
begin.Rollback()
return false,err
}
if state {
//如果锁还在
begin.Commit()
get.Do("del",id)
a.UnLock(get)
return true,nil
}else{
//丢失了锁
begin.Rollback()
return false,errors.New("lost lock")
}
}else{
return false,err
}
}
分布式锁使用的是另一文里的
golang 分布式锁
2022/2/12修改,之前查询后面想了一下有漏洞,做了一些优化