Open cyningsun opened 3 years ago
关于更好的处理方案 “缓存” 存储准实时的数据 + “异步更新” 数据到缓存 有一点疑问: 异步增量更新到redis等缓存,redis发生主从切换是可能丢掉数据更新的,应该如何保证数据的最终一致性呢?
关于更好的处理方案 “缓存” 存储准实时的数据 + “异步更新” 数据到缓存 有一点疑问: 异步增量更新到redis等缓存,redis发生主从切换是可能丢掉数据更新的,应该如何保证数据的最终一致性呢?
@sttming 数据存储在 Mysql,然后异步更新到 Redis,此时缓存中存储的是准实时的数据。最终一致依赖的是 Mysql 中的数据,因此主从切换不会有影响
@cyningsun 抱歉,没有描述清楚。按照我的理解,<“缓存” 存储准实时的数据>指的是redis中会缓存全量mysql的数据,依靠<“异步更新” 数据到缓存>对redis中的数据进行更新。我的疑问是异步更新数据到redis时,如果redis发生主从切换,会有概率导致redis丢失这次更新的数据,该如何应对这种情况并保证redis和mysql的数据最终一致呢?
@cyningsun 抱歉,没有描述清楚。按照我的理解,<“缓存” 存储准实时的数据>指的是redis中会缓存全量mysql的数据,依靠<“异步更新” 数据到缓存>对redis中的数据进行更新。我的疑问是异步更新数据到redis时,如果redis发生主从切换,会有概率导致redis丢失这次更新的数据,该如何应对这种情况并保证redis和mysql的数据最终一致呢?
文中更多谈低并发更新,高并发读取。异步更新数据到缓存,是指数据直接更新到 Mysql,然后从 Mysql 异步更新到缓存。 高并发更新的最终一致性,要先把并发降低下来,然后依靠一个可靠存储来保证最终一致性(可靠存储的性能一般不高
单并发问题,通过key := key + randN(100)来实现也可以吧,可以减少额外的goroutine。 阻塞读的问题不是很理解,我们对下游rpc请求都会设定一个超时值,例如500ms。当没有使用singleflight的时候,所有请求都会最多等待500ms。如果使用DoChan,并额外设置一个超时时间(例如5ms),那么除了第一个请求,其他请求的超时时间相当于被修改成5ms了,这样是不符合预期的。
单并发问题,通过key := key + randN(100)来实现也可以吧,可以减少额外的goroutine。 阻塞读的问题不是很理解,我们对下游rpc请求都会设定一个超时值,例如500ms。当没有使用singleflight的时候,所有请求都会最多等待500ms。如果使用DoChan,并额外设置一个超时时间(例如5ms),那么除了第一个请求,其他请求的超时时间相当于被修改成5ms了,这样是不符合预期的。
在函数执行完成后直接进行forget不知是否合适?
ch := g.DoChan(key, func() (interface{}, error) {
ret, err := find(context.Background(), key)
g.Forget(key)
return ret, err
})
// Create our timeout
timeout := time.After(500 * time.Millisecond)
var ret singleflight.Result
select {
case <-timeout: // Timeout elapsed
fmt.Println("Timeout")
return
case ret = <-ch: // Received result from channel
fmt.Printf("index: %d, val: %v, shared: %v\n", j, ret.Val, ret.Shared)
}
在函数执行完成后直接进行forget不知是否合适?
ch := g.DoChan(key, func() (interface{}, error) { ret, err := find(context.Background(), key) g.Forget(key) return ret, err }) // Create our timeout timeout := time.After(500 * time.Millisecond) var ret singleflight.Result select { case <-timeout: // Timeout elapsed fmt.Println("Timeout") return case ret = <-ch: // Received result from channel fmt.Printf("index: %d, val: %v, shared: %v\n", j, ret.Val, ret.Shared) }
Forget 是执行完毕之后才调用的。在执行过程中,可能其他协程已拿到同一个 Channel。该部分协程不会受到 Forget 影响
shared的解释有误, shared表示返回的result是否被多个请求公用, 而不是说是调用fn返回的.
shared的解释有误, shared表示返回的result是否被多个请求公用, 而不是说是调用fn返回的.
没错,你的解释更为准确
https://www.cyningsun.com/01-11-2021/golang-concurrency-singleflight.html
背景缓存 在各种场景中被大量使用,在 Cache Miss(缓存未命中)的情况下,就会出现下图的情况:所有的请求被同时打到下游存储上,将会影响下游存储的服务质量,因此需要严格限制访问下游存储的并发量。使用 Golang 编程的人,倾向于不假思索的使用 singleflight 应对 Cache Miss(缓存未命中),即:在绝大多数场景下,singlefli