乐趣区

关于golang:几种缓存更新的设计方法值得一看

前言

Hello,everybody,我是 asong,上一篇文章咱们一起聊一聊了面试中几个常见的缓存问题,明天我仍然聊一聊缓存,不过明天咱们聊的不是面试了,咱们一起来看一看咱们在零碎中缓存更新的设计,因本人教训无限,所以这些缓存设计来源于网上,我只是在这里总结一下,有什么不对的欢送指出~~~????。

缓存预热 To solve 缓存冷启动

在上一篇文章中[常见面试题之缓存雪崩、缓存穿透、缓存击穿],遗记讲了一个概念——缓存预热,所以在这篇文章补充一下,开一个好头,预热嘛~~~。

什么是缓存预热呢?咱们都晓得平时在跑步前都要热身,能够预防肌肉拉伤等一系例的益处。所以缓存预热具备同样的情理,咱们的新零碎上线后,咱们能够将相干的缓存数据间接加载到缓存零碎。这样能够防止在用户申请的时候,先去查询数据库,而后再将数据缓存的问题。用户能够间接查问当时已被预热的缓存数据。其实缓存预热是为了解决缓存冷启动问题,咱们新零碎上线后,redis 集群启动后,没有任何的缓存数据,这就是 redis 的冷启动。

如上图所示,如果不进行预热,那么 Redis 初识状态数据为空,零碎上线初期,对于高并发的流量,都会拜访到数据库中,对数据库造成流量的压力。

如何解决

当初咱们曾经晓得会有缓存预热这个问题,那么就要想一下对策咯。能够剖析出以下两点:

  • 须要统计拜访频度较高的热点数据
  • 应用 LRU 数据删除策略,构建数据留存队列

所以咱们能够设计一个如下计划:

  • 首先,通过 nginx + lua 的形式,把拜访流量数据上报到 Kafka,也能够是其它的 mq 队列。
  • 而后应用实时计算框架 (如 storm、spark streaming、flume) 从 kafka 中生产拜访流量数据,实时计算出拜访频率高的数据,这里统计进去的可能只会有编号信息,如商品编号或博客编号等。
  • 最初,依据编号从 mysql 数据库中查问出具体的信息,写入 redis,开始提供服务。

缓存更新的几种设计

1. 先删除缓存,在更新数据库

尽管这是一种错误方法,然而这种设计也是属于缓存更新的一种办法,所以大家还是要晓得为什么不能够这么做。还是那句话:知其所以然嘛。

这种办法就是在更新数据库时,先删除缓存,而后在更新数据库,而后续的操作会把数据在装载到缓存中,这种逻辑在并发时就会先脏数据,看如下图:

咱们解释一下上图的操作,两个并发操作,一个是更新操作,另一个是查问操作,更新操作删除缓存后,查问操作没有命中缓存,先把老数据读出来后放到缓存中,而后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还始终这样脏上来了。所以这个设计是谬误的,不倡议应用。

2. Cache aside

这是咱们最罕用的一种设计模式,其逻辑如下:

  • 查问:程序先从 cache 中获取数据,有数据间接返回,没有失去,则去数据库中取数据,胜利后更新到缓存中。
  • 更新:先把数据存到数据库中,胜利后,再让缓存生效。

这种设计正好能解决上文呈现脏数据的问题。咱们来理一下,一个是查问操作,一个是更新操作的并发,没有了删除 cache 数据的操作了,而是先更新了数据库中的数据,此时,缓存仍旧无效,所以,并发的查问操作拿的是没有更新的数据,然而,更新操作马上让缓存的生效了,后续的查问操作再把数据从数据库中拉进去。而不会像文章结尾的那个逻辑产生的问题,后续的查问操作始终都在取老的数据。

那么是不是这种设计就不会存在并发问题了呢?不是的,比方,一个是读操作,然而没有命中缓存,而后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存生效,而后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。但,这个 case 实践上会呈现,不过,实际上呈现的概率可能非常低,因为这个条件须要产生在读缓存时缓存生效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必须在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率根本并不大。

咱们能够为缓存设置上过期工夫,这样能够无效解决这个问题。

3. Read/Write Through

这个模式其实就是将 缓存服务 作为次要的存储,利用的所有读写申请都是间接与缓存服务打交道,而不论最初端的数据库了,数据库的数据由缓存服务来保护和更新。不过缓存中数据变更的时候是同步去更新数据库的,在利用的眼中只有缓存服务。
流程如下:

  • Read Through

Read Through 套路就是在查问操作中更新缓存,也就是说,当缓存生效的时候(过期或 LRU 换出),Cache Aside 是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务本人来加载,从而对利用方是通明的。

  • Write Through

Write Through 套路和 Read Through 相仿,不过是在更新数据时产生。当有数据更新的时候,如果没有命中缓存,间接更新数据库,而后返回。如果命中了缓存,则更新缓存,而后再由 Cache 本人更新数据库(这是一个同步操作)

这个模式的特点就是呈现脏数据的概率就比拟低,然而就强依赖缓存了,对缓存服务的稳定性有较大要求,另外,减少新缓存节点时还会有初始状态空数据问题。

4. Write Behind Caching

Write Behind Caching 又叫做 Write Back,就是在更新数据的时候,只更新缓存,不更新数据库,而缓存会异步地批量更新数据库。这个设计的益处是让数据的 I / O 操作能够很快,异步的操作还能够合并对同一个数据的屡次操作,性能上是十分可观的。

然而,其带来的问题是,数据不是强一致性的,而且可能会失落。在软件设计上,咱们基本上不可能做出一个没有缺点的设计,就像算法设计中的工夫换空间,空间换工夫一个情理,有时候,强一致性和高性能,高可用和高性性是有抵触的。软件设计素来都是取舍 Trade-Off。另外,Write Back 实现逻辑比较复杂,因为他须要 track 有哪数据是被更新了的,须要刷到长久层上。操作系统的 write back 会在仅当这个 cache 须要生效的时候,才会被真正长久起来,比方,内存不够了,或是过程退出了等状况,这又叫 lazy write。

这个模式的特点就是速度很快,效率会十分高,然而数据的一致性比拟差,还可能会有数据的失落状况,实现逻辑也较为简单。

总结

下面讲的这几种缓存更新设计,都是一些前人应用的总结,这些设计也不是完满的,这个世界上没有完满的设计,所以咱们的设计多多少少会有问题,比方咱们没有思考缓存(Cache)和长久层(Repository)的整体事务的问题。比方,更新 Cache 胜利,更新数据库失败了怎么吗?或是反过来。对于这个事,如果你须要强一致性,就要好好思考怎么解决这个问题。在软件开发或设计中,我十分倡议在之前先去参考一下已有的设计和思路,看看相应的 guideline,best practice 或 design pattern,吃透了已有的这些货色,再决定是否要从新创造轮子。千万不要似是而非地,想当然的做软件设计。

好啦,这一篇文章到这里就完结了,心愿对你们有用,又不对的中央欢送指出,可增加我的 golang 交换群,咱们一起学习交换。

结尾给大家发一个小福利吧,最近我在看 [微服务架构设计模式] 这一本书,讲的很好,本人也收集了一本 PDF,有须要的小伙能够到自行下载。获取形式:关注公众号:[Golang 梦工厂],后盾回复:[微服务],即可获取。

我翻译了一份 GIN 中文文档,会定期进行保护,有须要的小伙伴后盾回复 [gin] 即可下载。

我是 asong,一名普普通通的程序猿,让我一起缓缓变强吧。我本人建了一个 golang 交换群,有须要的小伙伴加我vx, 我拉你入群。欢送各位的关注,咱们下期见~~~

举荐往期文章:

  • 手把手教姐姐写音讯队列
  • 常见面试题之缓存雪崩、缓存穿透、缓存击穿
  • 详解 Context 包,看这一篇就够了!!!
  • go-ElasticSearch 入门看这一篇就够了(一)
  • 面试官:go 中 for-range 应用过吗?这几个问题你能解释一下起因吗
  • 学会 wire 依赖注入、cron 定时工作其实就这么简略!
  • 据说你还不会 jwt 和 swagger- 饭我都不吃了带着实际我的项目我就来了
  • 把握这些 Go 语言个性,你的程度将进步 N 个品位(二)
  • go 实现多人聊天室,在这里你想聊什么都能够的啦!!!
  • grpc 实际 - 学会 grpc 就是这么简略
  • go 规范库 rpc 实际
  • 2020 最新 Gin 框架中文文档 asong 又捡起来了英语,用心翻译
  • 基于 gin 的几种热加载形式
退出移动版