【讨论】:缓存同步、如何保证缓存一致性、缓存误用

发表于 4年以前  | 总阅读数:667 次

转自:segmentfault 作者:Huangy远

segmentfault.com/a/1190000015804406

缓存误用

缓存,是互联网分层架构中,非常重要的一个部分,通常用它来降低数据库压力,提升系统整体性能,缩短访问时间。 有架构师说“缓存是万金油,哪里有问题,加个缓存,就能优化”,缓存的滥用,可能会导致一些错误用法。 缓存,你真的用对了么? 误用一:把缓存作为服务与服务之间传递数据的媒介

如上图: 服务1和服务2约定好key和value,通过缓存传递数据 服务1将数据写入缓存,服务2从缓存读取数据,达到两个服务通信的目的 该方案存在的问题是: 1、数据管道,数据通知场景,MQ更加适合 (1)MQ是互联网常见的逻辑解耦,物理解耦组件,支持1对1,1对多各种模式,非常成熟的数据通道,而cache反而会将service-A/B/C/D耦合在一起,大家要彼此协同约定key的格式,ip地址等 (2)MQ能够支持push,而cache只能拉取,不实时,有时延 (3)MQ天然支持集群,支持高可用,而cache未必 (4)MQ能支持数据落地,cache具备将数据存在内存里,具有“易失”性,当然,有些cache支持落地,但互联网技术选型的原则是,让专业的软件干专业的事情:nginx做反向代理,db做固化,cache做缓存,mq做通道 2、多个服务关联同一个缓存实例,会导致服务耦合 (1)大家要彼此协同约定key的格式,ip地址等,耦合(2)约定好同一个key,可能会产生数据覆盖,导致数据不一致(3)不同服务业务模式,数据量,并发量不一样,会因为一个cache相互影响,例如service-A数据量大,占用了cache的绝大部分内存,会导致service-B的热数据全部被挤出cache,导致cache失效;又例如service-A并发量高,占用了cache的绝大部分连接,会导致service-B拿不到cache的连接,从而服务异常 误用二:使用缓存未考虑雪崩

常规的缓存玩法,如上图: 服务先读缓存,缓存命中则返回 缓存不命中,再读数据库 什么时候会产生雪崩? 答:如果缓存挂掉,所有的请求会压到数据库,如果未提前做容量预估,可能会把数据库压垮(在缓存恢复之前,数据库可能一直都起不来),导致系统整体不可服务。 如何应对潜在的雪崩? 答:提前做容量预估,如果缓存挂掉,数据库仍能扛住,才能执行上述方案。 否则,就要进一步设计。 常见方案一:高可用缓存

如上图:使用高可用缓存集群,一个缓存实例挂掉后,能够自动做故障转移。 常见方案二:缓存水平切分

如上图:使用缓存水平切分(推荐使用一致性哈希算法进行切分),一个缓存实例挂掉后,不至于所有的流量都压到数据库上。 误用三:调用方缓存数据

如上图: 服务提供方缓存,向调用方屏蔽数据获取的复杂性(这个没问题) 服务调用方,也缓存一份数据,先读自己的缓存,再决定是否调用服务(这个有问题) 该方案存在的问题是: 1、调用方需要关注数据获取的复杂性(耦合问题) 2、更严重的,服务修改db里的数据,淘汰了服务cache之后,难以通知调用方淘汰其cache里的数据,从而导致数据不一致(带入一致性问题) 3、有人说,服务可以通过MQ通知调用方淘汰数据,额,难道下游的服务要依赖上游的调用方,分层架构设计不是这么玩的(反向依赖问题) 误用四:多服务共用缓存实例

如上图:服务A和服务B共用一个缓存实例(不是通过这个缓存实例交互数据) 该方案存在的问题是: 1、可能导致key冲突,彼此冲掉对方的数据 画外音:可能需要服务A和服务B提前约定好了key,以确保不冲突,常见的约定方式是使用namespace:key的方式来做key。 2、不同服务对应的数据量,吞吐量不一样,共用一个实例容易导致一个服务把另一个服务的热数据挤出去 3、共用一个实例,会导致服务之间的耦合,与微服务架构的“数据库,缓存私有”的设计原则是相悖的 建议的玩法是

如上图:各个服务私有化自己的数据存储,对上游屏蔽底层的复杂性。 总结 1、服务与服务之间不要通过缓存传递数据 2、如果缓存挂掉,可能导致雪崩,此时要做高可用缓存,或者水平切分 3、调用方不宜再单独使用缓存存储服务底层的数据,容易出现数据不一致,以及反向依赖 4、不同服务,缓存实例要做垂直拆分

缓存,究竟是淘汰,还是修改?

KV缓存都缓存了一些什么数据? 答: (1)朴素类型的数据,例如:int (2)序列化后的对象,例如:User实体,本质是binary (3)文本数据,例如:json或者html (4)...

淘汰缓存中的这些数据,修改缓存中的这些数据,有什么差别?

答: (1)淘汰某个key,操作简单,直接将key置为无效,但下一次该key的访问会cache miss (2)修改某个key的内容,逻辑相对复杂,但下一次该key的访问仍会cache hit

可以看到,差异仅仅在于一次cache miss。

缓存中的value数据一般是怎么修改的? 答: (1)朴素类型的数据,直接set修改后的值即可 (2)序列化后的对象:一般需要先get数据,反序列化成对象,修改其中的成员,再序列化为binary,再set数据 (3)json或者html数据:一般也需要先get文本,parse成dom树对象,修改相关元素,序列化为文本,再set数据

结论:对于对象类型,或者文本类型,修改缓存value的成本较高,一般选择直接淘汰缓存。

问:对于朴素类型的数据,究竟应该修改缓存,还是淘汰缓存? 答:仍然视情况而定。 案例1: 假设,缓存里存了某一个用户uid=123的余额是money=100元,业务场景是,购买了一个商品pid=456。 分析:如果修改缓存,可能需要: (1)去db查询pid的价格是50元 (2)去db查询活动的折扣是8折(商品实际价格是40元) (3)去db查询用户的优惠券是10元(用户实际要支付30元) (4)从cache查询get用户的余额是100元 (5)计算出剩余余额是100 - 30 = 70 (6)到cache设置set用户的余额是70

为了避免一次cache miss,需要额外增加若干次db与cache的交互,得不偿失。

结论:此时,应该淘汰缓存,而不是修改缓存。

案例2: 假设,缓存里存了某一个用户uid=123的余额是money=100元,业务场景是,需要扣减30元。 分析:如果修改缓存,需要: (1)从cache查询get用户的余额是100元 (2)计算出剩余余额是100 - 30 = 70 (3)到cache设置set用户的余额是70

为了避免一次cache miss,需要额外增加若干次cache的交互,以及业务的计算,得不偿失。

结论:此时,应该淘汰缓存,而不是修改缓存。

案例3: 假设,缓存里存了某一个用户uid=123的余额是money=100元,业务场景是,余额要变为70元。

分析:如果修改缓存,需要:

(1)到cache设置set用户的余额是70

修改缓存成本很低。

结论:此时,可以选择修改缓存。当然,如果选择淘汰缓存,只会额外增加一次cache miss,成本也不高。

总结:

允许cache miss的KV缓存写场景:

大部分情况,修改value成本会高于“增加一次cache miss”,因此应该淘汰缓存

如果还在纠结,总是淘汰缓存,问题也不大

先操作数据库,还是先操作缓存?

这里分了两种观点,Cache Aside Pattern的观点、沈老师的观点。下面两种观点分析一下。

Cache Aside Pattern

什么是“Cache Aside Pattern”?

答:旁路缓存方案的经验实践,这个实践又分读实践,写实践。

对于读请求

先读cache,再读db

如果,cache hit,则直接返回数据

如果,cache miss,则访问db,并将数据set回缓存

(1)先从cache中尝试get数据,结果miss了

(2)再从db中读取数据,从库,读写分离

(3)最后把数据set回cache,方便下次读命中

对于写请求

先操作数据库,再淘汰缓存(淘汰缓存,而不是更新缓存)

如上图:

(1)第一步要操作数据库,第二步操作缓存

(2)缓存,采用delete淘汰,而不是set更新

Cache Aside Pattern为什么建议淘汰缓存,而不是更新缓存?

答:如果更新缓存,在并发写时,可能出现数据不一致。

如上图所示,如果采用set缓存。

在1和2两个并发写发生时,由于无法保证时序,此时不管先操作缓存还是先操作数据库,都可能出现:

(1)请求1先操作数据库,请求2后操作数据库

(2)请求2先set了缓存,请求1后set了缓存

导致,数据库与缓存之间的数据不一致。

所以,Cache Aside Pattern建议,delete缓存,而不是set缓存。

Cache Aside Pattern为什么建议先操作数据库,再操作缓存?

答:如果先操作缓存,在读写并发时,可能出现数据不一致。

如上图所示,如果先操作缓存。

在1和2并发读写发生时,由于无法保证时序,可能出现:

(1)写请求淘汰了缓

(2)写请求操作了数据库(主从同步没有完成)

(3)读请求读了缓存(cache miss)

(4)读请求读了从库(读了一个旧数据)

(5)读请求set回缓存(set了一个旧数据)

(6)数据库主从同步完成

导致,数据库与缓存的数据不一致。

所以,Cache Aside Pattern建议,先操作数据库,再操作缓存。

Cache Aside Pattern方案存在什么问题?

答:如果先操作数据库,再淘汰缓存,在原子性被破坏时:

(1)修改数据库成功了

(2)淘汰缓存失败了

导致,数据库与缓存的数据不一致。

个人见解:这里个人觉得可以使用重试的方法,在淘汰缓存的时候,如果失败,则重试一定的次数。如果失败一定次数还不行,那就是其他原因了。比如说redis故障、内网出了问题。

关于这个问题,沈老师的解决方案是,使用先操作缓存(delete),再操作数据库。假如删除缓存成功,更新数据库失败了。缓存里没有数据,数据库里是之前的数据,数据没有不一致,对业务无影响。只是下一次读取,会多一次cache miss。这里我觉得沈老师可能忽略了并发的问题,比如说以下情况:

一个写请求过来,删除了缓存,准备更新数据库(还没更新完成)。

然后一个读请求过来,缓存未命中,从数据库读取旧数据,再次放到缓存中,这时候,数据库更新完成了。此时的情况是,缓存中是旧数据,数据库里面是新数据,同样存在数据不一致的问题。

如图:

不一致解决场景及解决方案

答:发生写请求后(不管是先操作DB,还是先淘汰Cache),在主从数据库同步完成之前,如果有读请求,都可能发生读Cache Miss,读从库把旧数据存入缓存的情况。此时怎么办呢? 一、数据库主从不一致 先回顾下,无缓存时,数据库主从不一致问题。

如上图,发生的场景是,写后立刻读: (1)主库一个写请求(主从没同步完成) (2)从库接着一个读请求,读到了旧数据 (3)最后,主从同步完成

导致的结果是:主动同步完成之前,会读取到旧数据。 可以看到,主从不一致的影响时间很短,在主从同步完成后,就会读到新数据。 二、缓存与数据库不一致 再看,引入缓存后,缓存和数据库不一致问题。

如上图,发生的场景也是,写后立刻读: (1+2)先一个写请求,淘汰缓存,写数据库 (3+4+5)接着立刻一个读请求,读缓存,cache miss,读从库,写缓存放入数据,以便后续的读能够cache hit(主从同步没有完成,缓存中放入了旧数据) (6)最后,主从同步完成 导致的结果是:旧数据放入缓存,即使主从同步完成,后续仍然会从缓存一直读取到旧数据。 可以看到,加入缓存后,导致的不一致影响时间会很长,并且最终也不会达到一致。 三、问题分析 可以看到,这里提到的缓存与数据库数据不一致,根本上是由数据库主从不一致引起的。当主库上发生写操作之后,从库binlog同步的时间间隔内,读请求,可能导致有旧数据入缓存。 思路:那能不能写操作记录下来,在主从时延的时间段内,读取修改过的数据的话,强制读主,并且更新缓存,这样子缓存内的数据就是最新。在主从时延过后,这部分数据继续读从库,从而继续利用从库提高读取能力。 四、不一致解决方案 选择性读主 可以利用一个缓存记录必须读主的数据。

如上图,当写请求发生时: (1)写主库 (2)将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延”

PS:key的格式为“db:table:PK”,假设主从延时为1s,这个key的cache超时时间也为1s。

如上图,当读请求发生时: 这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果, (1)cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询。并且把主库的数据set到缓存中,防止下一次cahce miss。 (2)cache里没有这个key,说明最近没有发生过写请求,此时就可以去从库查询以此,保证读到的一定不是不一致的脏数据。 PS:如果系统可以接收短时间的不一致,建议建议定时更新缓存就可以了。避免系统过于复杂。

进程内缓存

除了常见的redis/memcache等进程外缓存服务,缓存还有一种常见的玩法,进程内缓存。 什么是进程内缓存? 答:将一些数据缓存在站点,或者服务的进程内,这就是进程内缓存。 进程内缓存的实现载体,最简单的,可以是一个带锁的Map。又或者,可以使用第三方库,例如leveldb、guave本地缓存 进程内缓存能存储啥? 答:redis/memcache等进程外缓存服务能存什么,进程内缓存就能存什么。

如上图,可以存储json数据,可以存储html页面,可以存储对象。 进程内缓存有什么好处? 答:与没有缓存相比,进程内缓存的好处是,数据读取不再需要访问后端,例如数据库。

如上图,整个访问流程要经过1,2,3,4四个步骤。

如果引入进程内缓存,

如上图,整个访问流程只要经过1,2两个步骤。

与进程外缓存相比(例如redis/memcache),进程内缓存省去了网络开销,所以一来节省了内网带宽,二来响应时延会更低。 进程内缓存有什么缺点? 答:统一缓存服务虽然多一次网络交互,但仍是统一存储。

如上图,站点和服务中的多个节点访问统一的缓存服务,数据统一存储,容易保证数据的一致性。

而进程内缓存,如上图,如果数据缓存在站点和服务的多个节点内,数据存了多份,一致性比较难保障。

如何保证进程内缓存的数据一致性? 答:保障进程内缓存一致性,有三种方案。 第一种方案

可以通过单节点通知其他节点。如上图:写请求发生在server1,在修改完自己内存数据与数据库中的数据之后,可以主动通知其他server节点,也修改内存的数据。如下图:

这种方案的缺点是:同一功能的一个集群的多个节点,相互耦合在一起,特别是节点较多时,网状连接关系极其复杂。

第二种方案

可以通过MQ通知其他节点。如上图,写请求发生在server1,在修改完自己内存数据与数据库中的数据之后,给MQ发布数据变化通知,其他server节点订阅MQ消息,也修改内存数据。

这种方案虽然解除了节点之间的耦合,但引入了MQ,使得系统更加复杂。 前两种方案,节点数量越多,数据冗余份数越多,数据同时更新的原子性越难保证,一致性也就越难保证。

第三种方案

为了避免耦合,降低复杂性,干脆放弃了“实时一致性”,每个节点启动一个timer,定时从后端拉取最新的数据,更新内存缓存。在有节点更新后端数据,而其他节点通过timer更新数据之间,会读到脏数据。

为什么不能频繁使用进程内缓存? 答:分层架构设计,有一条准则:站点层、服务层要做到无数据无状态,这样才能任意的加节点水平扩展,数据和状态尽量存储到后端的数据存储服务,例如数据库服务或者缓存服务。

可以看到,站点与服务的进程内缓存,实际上违背了分层架构设计的无状态准则,故一般不推荐使用。 什么时候可以使用进程内缓存? 答:以下情况,可以考虑使用进程内缓存。

情况一

只读数据,可以考虑在进程启动时加载到内存。 画外音:此时也可以把数据加载到redis / memcache,进程外缓存服务也能解决这类问题。

情况二

极其高并发的,如果透传后端压力极大的场景,可以考虑使用进程内缓存。 例如,秒杀业务,并发量极高,需要站点层挡住流量,可以使用内存缓存。

情况三

一定程度上允许数据不一致业务。 例如,有一些计数场景,运营场景,页面对数据一致性要求较低,可以考虑使用进程内页面缓存。 再次强调,进程内缓存的适用场景并不如redis/memcache广泛,不要为了炫技而使用。更多的时候,还是老老实实使用redis/mc吧。

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237332次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8178次阅读
 目录