浏览器怎么打开网站服务器下载,什么是网络设计?,邵东微网站建设,有哪些做笔译的网站文章目录 一、在高并发下#xff0c;如何避免大量请求直接访问数据库#xff1f;二、避免缓存击穿二、避免缓存穿透三、避免缓存雪崩四、延迟双删保证数据一致性五、在使用 Go 的 time.AfterFunc 函数时#xff0c;如果删除缓存操作失败怎么办#xff1f; MySQL和
Redis是… 文章目录 一、在高并发下如何避免大量请求直接访问数据库二、避免缓存击穿二、避免缓存穿透三、避免缓存雪崩四、延迟双删保证数据一致性五、在使用 Go 的 time.AfterFunc 函数时如果删除缓存操作失败怎么办 MySQL和
Redis是工作中最常见的两个组件那么在使用过程中也有一些常见的问题和解决办法。本文通过
Go实现介绍它们。 一、在高并发下如何避免大量请求直接访问数据库
在高并发下如果让大量请求直接访问数据库可能会造成数据库压力过大响应延迟上升严重时甚至可能导致数据库崩溃。所以我们需要采取一些措施来保护数据库避免大量请求直接访问数据库。以下是一些常见的策略 使用缓存缓存是最常见、最直接的方法可以大大减少数据库的请求压力。对于读多写少的场景尤其有效。不仅如此合理使用缓存还可以提高系统的响应速度。但是在使用缓存时要考虑BigKey、HotKey、缓存击穿、缓存穿透、缓存雪崩以及数据一致性等众多问题。HotKey和BigKey可以看本人之前的两篇文章 HotKey29.Go处理Redis HotKey BigKey 30.Go处理Redis BigKey 限流使用限流算法如漏桶算法、令牌桶算法来对访问进行限制保证系统在可接受的压力范围内运行防止数据库被过多的请求冲垮。 限流13. Go中常见限流算法示例代码 熔断机制引入熔断器当侦测到某个服务的错误次数过多如因数据库连接问题导致的错误时熔断该服务的所有请求直到服务恢复。 限流与熔断48.Go简要实现令牌桶限流与熔断器并集成到Gin框架中 负载均衡如果有足够的资源可以采用负载均衡策略如数据库读写分离主从复制等将读写压力分散到多个数据库节点上。 负载均衡15. Go实现负载均衡算法 使用队列对于非实时性的数据请求可以采用异步处理的方式将请求先放入队列中然后通过队列对数据库请求进行削峰填谷避免大量请求同时涌向数据库。 MQ28.windows安装kafkaGo操作kafka示例 数据库优化适当地对数据库进行优化如合理的索引、合理的表结构设计、SQL 函数优化等都可以提高数据库处理请求的能力。
以上就是一些避免数据库在高并发下被大量请求直接访问的策略可以根据具体的场景和需求选择适合的策略。
二、避免缓存击穿
缓存击穿问题也叫热点Key问题就是一个被高并发访问并且缓存重建业务较复杂的Key突然失效了无数的请求访问会在瞬间给数据库带来巨大的冲击即瞬时的高并发击穿了缓存去请求DB。
缓存雪崩和缓存击穿的区别在于缓存击穿针对某一Key缓存缓存雪崩则是很多Key。
由于缓存击穿是某个热点Key突然过期导致的那么我们继续把它加载进缓存就可以了但是不能让所有请求都去加载只需要放行一个请求去加载之后其他请求直接从缓存获取避免高并发流量打挂DB通过互斥锁即可实现。
互斥锁在读取缓存的过程中如果缓存未命中则添加锁并从数据库中查询。这样可以避免在高并发下大量的请求直接访问数据库。
注意使用double check机制此外从DB查询后如果DB也不存在应该缓存一个空对象,否则这些高并发请求会继续请求DB。在设置空对象时也可以设置一个较短的过期时间避免长时间缓存空对象。
v, found : cache.Get(key)
if !found {lock.Lock()defer lock.Unlock()// double checkv, found cache.Get(key)if !found {v, err : db.Get(key)if err ! nil {// handle errorreturn nil, err}if v nil {v EmptyObject}cache.Set(key, v, cache.DefaultExpiration)}
}
return v, nil二、避免缓存穿透
缓存穿透是指用户不断对一个缓存和数据库都不存在的数据进行访问对于这种情况由于既在缓存中查不到也在数据库中查不到于是每次都会对数据库进行一次查询造成数据库压力增大这种请求有可能是一个恶意攻击。
Go 语言中我们可以使用以下策略来避免缓存穿透
缓存空对象把空结果也进行缓存当后续请求再次查询时即使查不到数据也会在缓存中得到一个空结果而不会再对数据库进行查询。同时为了避免未来的查询都返回空结果需要对空结果设置一个较短的过期时间。
伪代码如下
v, found : cache.Get(key)
if found {return v
}v, err : db.Get(key)
if err ! nil {// handle errorreturn nil, err
}if v nil {v EmptyObjectcache.Set(key, v, cache.DefaultExpiration)
}
return v, nil使用布隆过滤器布隆过滤器Bloom filter是一种用于测试一个元素是否在一个集合中的数据结构。由于它的存储效率高且可以非常快速的查询所以常常被用来过滤掉一部分肯定不存在的数据避免了对数据库的无谓请求在大数据量查找中有很高效率。
伪代码如下
if !bloomFilter.Exists(key) {return nil
}v, found : cache.Get(key)
if found {return v
}v, err : db.Get(key)
if err ! nil {// handle errorreturn nil, err
}if v nil {v EmptyObject
}cache.Set(key, v, cache.DefaultExpiration)
return v, nil在上面的示例中如果布隆过滤器中不存在请求中的 key则直接返回不对数据库做查询。
上述两种方法可以有效应对缓存穿透能够减轻数据库的压力提升程序的响应速度。
三、避免缓存雪崩
缓存雪崩是指在缓存系统中大量数据同时过期在访问频率高的情况下可能会引起数据库的过载。
在Go语言中我们可以采用以下措施来避免缓存雪崩
1、设置缓存失效时间的随机性 使得每一个key的失效时间都是随机的防止所有缓存在同一时刻全部失效。例如我们可以在原有的失效时间基础上增加一个随机的延长时间。
expires : baseExpires time.Duration(rand.Intn(randExpires)) * time.Second
cache.Set(key, value, expires)2、使用数据版本控制 通过在每次缓存数据时将数据打上版本可以是时间戳、或是递增版本号等每次访问时先访问缓存如果缓存不存在或者版本低于当前的版本就更新缓存数据。这样即使缓存失效也可以由单一线程去做更新其它请求只需要等待即可。
func Get(key string, currentVersion int) (string, error) {v, version, found : cache.GetWithVersion(key)if found version currentVersion {return v, nil}// Single flight to load from the database and put to the cache.// Usually done with a lock or using sync.Once type of logic.// ...return v, nil
}3、熔断机制和降级
在系统压力过大或者服务不可用的情况下可以进行熔断降级比如返回一些默认值或者从备份缓存中读数据。
整体来说防止缓存雪崩主要是预防工作以及在系统异常时的快速应对。每一个解决方案都有其应用场景需要根据具体业务情况进行选择。
四、延迟双删保证数据一致性
使用到缓存一般就需要考虑缓存与数据库的一致性。如更新时是先更新缓存还是先更新DB或者是先删除缓存还是先删除DB、或者是否要通过监听Binlog同步缓存或者做一些其他旁路校验等。方案很多需要针对当前业务是否能够容忍缓存与DB的不一致以及容忍的程度如何来做具体涉设。但是最常用的还是延迟双删方案成本低容易实现且能基本保障一致性。
延迟双删是解决缓存更新一致性问题的一个策略具体策略如下
先删除缓存再更新数据库最后延时删除缓存
这样做的目的是为了应对并发情况下缓存与数据库数据不一致的问题。
Go语言实现延迟双删的一个简单示例如下
// 先删除缓存
cache.Delete(key)// 更新数据库
err : db.Update(key, value)
if err ! nil {fmt.Printf(DB update error: %v, err)return
}// 延迟删除缓存
time.AfterFunc(time.Duration(delayMillisecond)*time.Millisecond, func() {cache.Delete(key)
})上述代码的逻辑
首先通过 cache.Delete(key) 删除旧的缓存数据。然后通过 db.Update(key, value) 更新数据库数据。最后使用 Go 的 time.AfterFunc 函数实现一段时间后再次删除缓存。时间可以依据实际业务情况设定。
这种方式可以在大部分场景下确保缓存和数据库的数据一致但还是存在极端情况下的问题比如在第二次删除缓存之前有其他请求把旧的数据加载到了缓存。这种情况的出现几率较小如果业务对此有较高的要求可能需要使用更严格的方案如监听Binlog同步缓存。
五、在使用 Go 的 time.AfterFunc 函数时如果删除缓存操作失败怎么办
在使用time.AfterFunc时如果删除缓存操作失败最常见的处理办法是进行重试操作。不过在设置重试次数和重试延迟时应谨慎考虑以防止无效操作导致系统资源的浪费。
func deleteCacheWithRetry(key string, retryTimes int, delay time.Duration) {for i : 0; i retryTimes; i {// 尝试删除缓存err : cache.Delete(key)if err nil {// 删除成功return}// 如果删除失败则等待一段时间再重试time.Sleep(delay)}// 在此处处理连续失败的情况例如记录日志、发送告警等fmt.Printf(Failed to delete cache for key %s after %d attempts\n, key, retryTimes)
}// 在 time.AfterFunc 中使用
time.AfterFunc(time.Duration(delayMillisecond)*time.Millisecond, func() {deleteCacheWithRetry(key, 3, 1*time.Second)
})在以上代码中我们定义了一个名为 deleteCacheWithRetry 的函数它接受一个缓存键、重试次数和每次重试延迟的时间。它将尝试删除缓存如果失败将等待一段时间后重试直到达到最大重试次数。如果超过重试次数仍未成功将通过日志记录这个异常情况。 当然具体的处理方式要根据项目具体需求和场景去判断以上只是一个参考示例。