深入浅出Redis 缓存使用问题 | 长文分享
目录
数据一致性
先更新缓存,后更新数据库【一般不考虑】
先更新数据库,再更新缓存【一般不考虑】
先删除缓存,后更新数据库
先更新数据库,后删除缓存【推荐】
怎么选择这些方案?采用哪种合适?
缓存穿透、击穿、雪崩
缓存穿透
解决方案——缓存穿透问题
缓存击穿
缓存雪崩
热点Key、BigKey【数据倾斜】
热点key产生原因&危害
怎么发现热点Key
预估发现
客户端发现
怎么解决热点Key?
使用二级缓存
key分散
什么是BigKey
BigKey危害
发现bigkey
解决BigKey【核心思路:拆分】
Redis脑裂【数据丢失】
哨兵主从集群脑裂
集群脑裂
多级缓存案例
携程金融在Redis的实践
整体方案
数据准确性
并发控制?
基于updateTime的更新顺序控制
数据完整性设计
数据一致性
只要使用到缓存,无论是本地内存做缓存还是使用 redis 做缓存,那么就会存在数据同步的问题。
以 Tomcat 向 MySQL 中写入和删改数据为例,来给你解释一下,数据的增删改操作具体是如何进行的。
数据一致性方案
-
先更新缓存,再更新数据库
-
先更新数据库,再更新缓存
-
先删除缓存,后更新数据库
-
先更新数据库,后删除缓存
新增数据时,数据直接写到数据库中,不需要对缓存做任何的操作,此时,缓存中本身就没有新增数据,而数据库中的值就是最新值,此时缓存&数据库中的数据是一致的。
当我们涉及到更新缓存的时候呢?
先更新缓存,后更新数据库【一般不考虑】
更新缓存成功,更新数据库异常,会导致缓存与数据库数据完全不一致,且很难察觉,因为缓存中的数据一直都存在。
先更新数据库,再更新缓存【一般不考虑】
原因与上一种情况一致。数据库更新成功,缓存更新失败,也会有数据不一致问题。除此以外,还可能存在这样的问题:
-
并发:当请求A与请求B同时进行更新操作。可能出现A请求更新数据库,B请求更新数据库,B请求更新缓存,A此时更新缓存。这就出现请求A更新缓存比请求B更新缓存应该早才对,因网络等不可抗拒的因素,B却比A先一步更新缓存,从而导致出现脏数据。
-
业务场景:写场景较多时,读场景少。采用这种方案导致数据压根没有被读取到,但缓存却一直频繁的更新而浪费性能。
到底是选择更新缓存还是淘汰缓存呢?
主要取决于“更新缓存的复杂度”,更新缓存的代价很小,此时我们应该更倾向于更新缓存,以保证更高的缓存命中率,更新缓存的代价很大,此时我们应该更倾向于淘汰缓存。
先删除缓存,后更新数据库
也存在问题,但是可以解决
出现问题的情况:请求A【更新】和请求B【查询】,A先删除Redis的数据,在数据库中进行更新操作。此时请求B查询时,Redis数据为空,就会去数据库中查询该值,补回到Redis中。但是此时请求A还没有更新成功或者事务还没有提交。请求B从数据库中查询到老数据!这就会产生数据库和缓存中数据不一致的问题。
解决方案:延迟双删。即先淘汰缓存,再写数据库,休眠1秒,再淘汰缓存。
以下是“延迟双删”的伪代码
redis.delKey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)
这么做,可以将【1秒】内所造成的缓存脏数据,再次删除。
针对休眠时间,需要评估自身项目。
项目中写数据的休眠时间在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
上面保证事务提交完以后再进行删除缓存还有一个问题,如果MySQL采取的读写分离架构,主从同步之间会有时间差。
请求A【更新】和请求B【查询】,A更新删除缓存数据,请求主库进行更新操作,主库与从库进行同步数据的操作,请求B发现缓存没有数据,会去库中查询,此时同步数据未完成时,拿到的依旧是旧数据。
解决方案:
-
延迟双删策略,休眠时间在同步的延迟时间基础加上几百毫秒。
-
方案2:如果是对缓存进行填充数据的行为,查询数据库的操作强制从主库进行查询。
采用这种同步淘汰策略,吞吐量降低怎么破?
方案:将第二次删除作为异步。自己起一个线程,异步删除。这样写的请求就不需要休眠几秒后再返回数据,这么做以加大吞吐量。
如果第二次删除删除失败咋办?
啊,震惊。要看下面这种策略了
先更新数据库,后删除缓存【推荐】
这种方式【Cache Aside Pattern】。读数据的时候先读缓存,缓存没有就查数据库。然后取出的数据库放入缓存,同时返回响应。更新的时候,先更新数据库,再删除缓存。
怎么选择这些方案?采用哪种合适?
在线上,更多的偏向与使用删除缓存类操作,因为这种方式的话,会更容易避免一些问题。
因为删除缓存更新缓存的速度比在数据库中要快一些,所以一般情况下我们可能会先用先更新数据库,后删除缓存的操作。
因为这种情况下缓存不一致性的情况只有可能是查询比删除慢的情况,而这种情况相对来说会少很多。同时结合延时双删的处理,可以有效的避免缓存不一致的情况。
缓存穿透、击穿、雪崩
缓存穿透
指查询一个根本不存在的数据,缓存层和存储层都不会命中,于是这个请求就可以随意访问数据库,这个就是缓存穿透,缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。
缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现了缓存穿透问题。
造成缓存穿透的基本原因有两个。
-
自身业务代码或者数据出现问题,比如,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。如果不对参数做校验,数据库id都是大于0的,我一直用小于0的参数去请求你,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。
-
一些恶意攻击、爬虫等造成大量空命中。
解决方案——缓存穿透问题
-
缓存空对象
当存储层不命中,到数据库查发现也没有命中,那么仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。
缓存空对象会有两个问题:
-
空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
-
缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消前面所说的数据一致性方案处理。
-
布隆过滤器拦截
【这种方法适用于数据命中不高、数据相对固定、实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。】
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。例如:一个推荐系统有4亿个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层。
缓存击穿
缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
解决方案:设置热点数据永远不过期。或者加上互斥锁。
-
永不过期
-
从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
-
从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
-
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。
-
使用互斥锁(mutnex key)
-
简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load 数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load 数据库的操作并回设缓存;否则,就重试整个get缓存的方法。
-
缓存雪崩
由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,比如同一时间缓存数据大面积失效,那一瞬间Redis跟没有一样,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。
缓存雪崩的英文原意是stampeding herd(奔逃的野牛),指的是缓存层宕掉后,流量会像奔逃的野牛一样,打向后端存储。
预防和解决缓存雪崩问题,可以从以下三个方面进行着手。
1)保证缓存层服务高可用性。和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如Redis中Sentinel和 Redis Cluster都实现了高可用。
2)依赖隔离组件为后端限流并降级。无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部阻塞(hang)在这个资源上,造成整个系统不可用。
3)提前演练。在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定。
4)将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
热点Key、BigKey【数据倾斜】
数据倾斜其实就是访问量倾斜或者数据量倾斜
-
热点key出现造成集群访问量倾斜
-
bigKey造成集群数据量倾斜。
热点key产生原因&危害
原因:hotkey的原因大致分为两种。
-
用户消费的数据远大于生产的数据。比如热点评论、洛克王国直播(嘿嘿)......
-
日常生活中突发的事件。比如特朗普在前几天增加关税......。
双十一期间某些热门商品的降价促销,当这其中的某一件商品被数万次点击浏览或者购买时,会形成一个较大的需求量,这种情况下就会造成热点问题。同理,被大量刊发、浏览的热点新闻、热点评论、明星直播等,这些典型的读多写少的场景也会产生热点问题。
请求分片集中,超过单Server的性能极限。在服务端读数据进行访问时,往往会对数据进行分片切分,此过程中会在某一主机Server上对相应的Key进行访问,当访问超过Server极限时,就会导致热点Key问题的产生。
危害:
-
流量集中,达到物理网卡上线。
-
请求过多,缓存分片服务被打垮
-
数据库击穿,引起业务雪崩
怎么发现热点Key
预估发现
针对业务提前预估出访问频繁的热点key,例如秒杀商品业务中,秒杀的商品都是热点key。
当然并非所有的业务都容易预估出热点key,可能出现漏掉或者预估错误的情况。
客户端发现
客户端其实是距离key"最近"的地方,因为Redis命令就是从客户端发出的,以Jedis为例,可以在核心命令入口,使用这个Google Guava中的AtomicLongMap进行记录,如下所示。
使用客户端进行热点key的统计非常容易实现,但是同时问题也非常多:
(1) 无法预知key的个数,存在内存泄露的危险。
(2) 对于客户端代码有侵入,各个语言的客户端都需要维护此逻辑,维护成本较高。
(3) 规模化汇总实现比较复杂。
-
redis的monitor指令
-
monitor命令在高并发条件下,内存暴增同时会影响Redis的性能,所以此种方法适合在短时间内使用。只能统计一个Redis节点的热点key,对于Redis集群需要进行汇总统计。
-
-
redis在4.0.3中给redis-cli 提供--hotkeys,用于找到热点key
-
如果键值较多的情况下,执行慢。和热点的概念的有点背道而驰,同时热度定义的不够准确。
-
-
TCP抓包发现
-
Redis客户端使用TCP协议与服务端进行交互,通信协议采用的是RESP。如果站在机器的角度,可以通过对机器上所有Redis端口的TCP数据包进行抓取完成热点key的统计。
-
此种方法对于Redis客户端和服务端来说毫无侵入,是比较完美的方案,但是依然存在3个问题:
-
需要一定的开发成本
-
对于高流量的机器抓包,对机器网络可能会有干扰,同时抓包时候会有丢包的可能性。
-
维护成本过高。
-
-
对于成本问题,有一些开源方案实现了该功能,例如ELK(ElasticSearch Logstash Kibana)体系下的packetbeat[2] 插件,可以实现对Redis、MySQL等众多主流服务的数据包抓取、分析、报表展示
-
怎么解决热点Key?
使用二级缓存
可以使用 guava-cache或hcache,发现热点key之后,将这些热点key加载到JVM中作为本地缓存。访问这些key时直接从本地缓存获取即可,不会直接访问到redis层了,有效的保护了缓存服务器。
key分散
将热点key分散为多个子key,然后存储到缓存集群的不同机器上,这些子key对应的value都和热点key是一样的。当通过热点key去查询数据时,通过某种hash算法随机选择一个子key,然后再去访问缓存机器,将热点分散到了多个子key上。
什么是BigKey
bigkey是指key对应的value所占的内存空间比较大,例如一个字符串类型的value可以最大存到512MB,一个列表类型的value最多可以存储23-1个元素。
如果按照数据结构来细分的话,一般分为字符串类型bigkey和非字符串类型bigkey。
字符串类型:体现在单个value值很大,一般认为超过10KB就是bigkey,但这个值和具体的OPS(Operations Per Second:每秒操作数)相关。
非字符串类型:哈希、列表、集合、有序集合,体现在元素个数过多。
bigkey无论是空间复杂度和时间复杂度都不太友好。
BigKey危害
bigkey的危害体现在三个方面:
-
内存空间不均匀(平衡):例如在Redis Cluster中,bigkey 会造成节点的内存空间使用不均匀。
-
超时阻塞:由于Redis单线程的特性,操作bigkey比较耗时,也就意味着阻塞Redis可能性增大。
-
网络拥塞:每次获取bigkey产生的网络流量较大
假设一个bigkey为1MB,每秒访问量为1000,那么每秒产生1000MB 的流量,对于普通的千兆网卡(按照字节算是128MB/s)的服务器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey可能会对其他实例造成影响,其后果不堪设想。
bigkey的存在并不是完全致命的:
如果这个bigkey存在但是几乎不被访问,那么只有内存空间不均匀的问题存在,相对于另外两个问题没有那么重要紧急,但是如果bigkey是一个热点key(频繁访问),那么其带来的危害不可想象,所以在实际开发和运维时一定要密切关注bigkey的存在。
发现bigkey
-
redis-cli --bigkeys可以命令统计bigkey的分布。但是在生产环境中,开发和运维人员更希望自己可以定义bigkey的大小,而且更希望找到真正的bigkey都有哪些,这样才可以去定位、解决、优化问题。
-
debug object key查看serializedlength属性。判断一个key是否为bigkey,可以执行那个命令,它表示 key对应的value序列化之后的字节数。
如果是要遍历多个,则尽量不要使用keys的命令,可以使用scan的命令来减少压力。它能有效的解决keys命令存在的问题。和keys命令执行时会遍历所有键不同,scan采用渐进式遍历的方式来解决 keys命令可能带来的阻塞问题,但是要真正实现keys的功能,需要执行多次scan。可以想象成只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕。
-
scan cursor [match pattern] [count number]
-
cursor :是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
-
Match pattern :是可选参数,它的作用的是做模式的匹配,这点和keys的模式匹配很像。
-
Count number :是可选参数,它的作用是表明每次要遍历的键个数,默认值是10,此参数可以适当增大。
-
-
除了scan 以外,Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似。
渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无瑕,如果在scan 的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键,这些是我们在开发时需要考虑的。
如果键值个数比较多,scan + debug object会比较慢,可以利用Pipeline机制完成。对于元素个数较多的数据结构,debug object执行速度比较慢,存在阻塞Redis的可能,所以如果有从节点,可以考虑在从节点上执行。
解决BigKey【核心思路:拆分】
对 big key 存储的数据 (big value)进行拆分,变成value1,value2… valueN等等。
例如big value 是个大json 通过 mset 的方式,将这个 key 的内容打散到各个实例中,或者一个hash,每个field代表一个具体属性,通过hget、hmget获取部分value,hset、hmset来更新部分属性。
例如big value 是个大list,可以拆成将list拆成。= list_1, list_2, list3, ...listN
其他数据类型同理。
Redis脑裂【数据丢失】
所谓的脑裂,就是指在有主从集群中,同时有两个主节点,它们都能接收写请求。而脑裂最直接的影响,就是客户端不知道应该往哪个主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。而且,严重的话,脑裂会进一步导致数据丢失。
哨兵主从集群脑裂
现在假设:有三台服务器一台主服务器,两台从服务器,还有一个哨兵。
基于上边的环境,这时候网络环境发生了波动导致了sentinel没有能够心跳感知到master,但是哨兵与slave之间通讯正常。所以通过选举的方式提升了一个salve为新master。如果恰好此时server1仍然连接的是旧的master,而server2连接到了新的master上。数据就不一致了,哨兵恢复对老master节点的感知后,会将其降级为slave节点,然后从新maste同步数据(full resynchronization),导致脑裂期间老master写入的数据丢失。
而且基于setNX指令的分布式锁,可能会拿到相同的锁;基于incr生成的全局唯一id,也可能出现重复。通过配置参数
-
min-replicas-to-write 2
-
min-replicas-max-lag 10
第一个参数表示最少的salve节点为2个
第二个参数表示数据复制和同步的延迟不能超过10秒
配置了这两个参数:如果发生脑裂:原master会在客户端写入操作的时候拒绝请求。这样可以避免大量数据丢失。
集群脑裂
Redis集群的脑裂一般是不存在的,因为Redis集群中存在着过半选举机制,而且当集群16384个槽任何一个没有指派到节点时整个集群不可用。所以我们在构建Redis集群时,应该让集群 Master 节点个数最少为 3 个,且集群可用节点个数为奇数。
不过脑裂问题不是是可以完全避免,只要是分布式系统,必然就会一定的几率出现这个问题,CAP的理论就决定了。
多级缓存案例
首先,用户的请求被负载均衡服务分发到Nginx上,此处常用的负载均衡算法是轮询或者一致性哈希,轮询可以使服务器的请求更加均衡,而一致性哈希可以提升Nginx应用的缓存命中率。
接着,Nginx应用服务器读取本地缓存,实现本地缓存的方式可以是Lua Shared Dict,或者面向磁盘或内存的Nginx Proxy Cache,以及本地的Redis实现等,如果本地缓存命中则直接返回。Nginx应用服务器使用本地缓存可以提升整体的吞吐量,降低后端的压力,尤其应对热点数据的反复读取问题非常有效。
如果Nginx应用服务器的本地缓存没有命中,就会进一步读取相应的分布式缓存——Redis分布式缓存的集群,可以考虑使用主从架构来提升性能和吞吐量,如果分布式缓存命中则直接返回相应数据,并回写到Nginx应用服务器的本地缓存中。
如果Redis分布式缓存也没有命中,则会回源到Tomcat集群,在回源到Tomcat集群时也可以使用轮询和一致性哈希作为负载均衡算法。当然,如果Redis分布式缓存没有命中的话,Nginx应用服务器还可以再尝试一次读主Redis集群操作,目的是防止当从 Redis集群有问题时可能发生的流量冲击。
在Tomcat集群应用中,首先读取本地平台级缓存,如果平台级缓存命中则直接返回数据,并会同步写到主Redis集群,然后再同步到从Redis集群。此处可能存在多个Tomcat实例同时写主Redis集群的情况,可能会造成数据错乱,需要注意缓存的更新机制和原子化操作。
如果所有缓存都没有命中,系统就只能查询数据库或其他相关服务获取相关数据并返回,当然,我们已经知道数据库也是有缓存的。
整体来看,这是一个使用了多级缓存的系统。Nginx应用服务器的本地缓存解决了热点数据的缓存问题,Redis分布式缓存集群减少了访问回源率,Tomcat应用集群使用的平台级缓存防止了相关缓存失效崩溃之后的冲击,数据库缓存提升数据库查询时的效率。正是多级缓存的使用,才能保障系统具备优良的性能。
携程金融在Redis的实践
携程金融形成了自顶向下的多层次系统架构,如业务层、平台层、基础服务层等,其中用户信息、产品信息、订单信息等基础数据由基础平台等底层系统产生,服务于所有的金融系统,对这部分基础数据我们引入了统一的缓存服务(系统名utag)。
缓存数据有三大特点:全量、准实时、永久有效,在数据实时性要求不高的场景下,业务系统可直接调用统一的缓存查询接口。
在构建此统一缓存服务时候,有三个关键目标:
-
数据准确性:数据库中单条数据的更新一定要准确同步到缓存服务。
-
数据完整性:将对应数据库表的全量数据进行缓存且永久有效,从而可以替代对应的数据库查询。
-
系统可用性:我们多个产品线的多个核心服务都已经接入,utag的高可用性显得尤为关键。
整体方案
系统在多地都有部署,故缓存服务也做了相应的异地多机房部署,一来可以让不同地区的服务调用本地区服务,无需跨越网络专线,二来也可以作为一种灾备方案,增加可用性。
对于缓存的写入,由于缓存服务是独立部署的,因此需要感知业务数据库数据变更然后触发缓存的更新,本着“可以多次更新,但不能漏更新”的原则,设计了多种数据更新触发源:定时任务扫描,业务系统MQ、binlog变更MQ,相互之间作为互补来保证数据不会漏更新。
对于MQ使用携程开源消息中间件QMQ 和 Kafka,在公司内部QMQ和Kafka也做了异地机房的互通。
使用MQ来驱动多地多机房的缓存更新,在不同的触发源触发后,会查询最新的数据库数据,然后发出一个缓存更新的MQ消息,不同地区机房的缓存系统同时监听该主题并各自进行缓存的更新。
对于缓存的读取,utag系统提供dubbo协议的缓存查询接口,业务系统可调用本地区的接口,省去了网络专线的耗时(50ms延迟)。在utag内部查询redis数据,并反序列化为对应的业务model,再通过接口返回给业务方。
数据准确性
不同的触发源,对缓存更新过程是一样的,整个更新步骤可抽象为4步:
step1:触发更新,查询DB中的新数据,并发送统一的MQ
step2:接收MQ,查询缓存中的老数据
step3:新老数据对比,判断是否需要更新
step4:若需要,则更新缓存
并发控制?
若一条数据库数据出现了多次更新,且刚好被不同的触发源触发,更新缓存时候若未加控制,可能出现数据更新错乱,如下图所示:
需要将第2、3、4步加锁,使得缓存刷新操作全部串行化。由于utag本身就依赖了redis,此处我们的分布式锁就基于redis实现。
基于updateTime的更新顺序控制
即使加了锁,也需要进一步判断当前数据库数据与缓存数据的新老,因为到达缓存更新流程的顺序并不代表数据的真正更新顺序。我们通过对比新老数据的更新时间来实现数据更新顺序的控制。若新数据的更新时间大于老数据的更新时间,则认为当前数据可以直接写入缓存。
我们系统从建立之初就有自己的MySQL规范,每张表都必须有update_time字段,且设置为ON
UPDATE CURRENT_TIMESTAMP,但是并没有约束时间字段的精度,大部分都是秒级别的,因此在同一秒内的多次更新操作就无法识别出数据的新老。
针对同一秒数据的更新策略我们采用的方案是:先进行数据对比,若当前数据与缓存数据不相等,则直接更新,并且发送一条延迟消息,延迟1秒后再次触发更新流程。
举个例子:假设同一秒内同一条数据出现了两次更新,value=1和value=2,期望最终缓存中的数据是value=2。若这两次更新后的数据被先后触发,分两种情况:
case1:若value=1先更新,value=2后更新,(两者都可更新到缓存中,因为虽然是同一秒,但是值不相等)则缓存中最终数据为value=2。
case2:若value=2先更新,value=1后更新,则第一轮更新后缓存数据为value=1,不是期望数据,之后对比发现是同一秒数据后会通过消息触发二次更新,重新查询数据库数据为value=2,可以更新到缓存中。如下图所示:
数据完整性设计
数据准确性是从单条数据更新角度的设计,而我们构建缓存服务的目的是替代对应数据库表的查询,因此需要缓存对应数据库表的全量数据,而数据的完整性从以下三个方面得到保证:
(1)“把鸡蛋放到多个篮子里”,使用多种触发源(定时任务,业务MQ,binlog MQ)来最大限度降低单条数据更新缺失的可能性。
单一触发源有可能出现问题,比如消息类的触发依赖业务系统、中间件canel、中间件QMQ和Kafka,扫表任务依赖分布式调度平台、MySQL等。中间任何一环都可能出现问题,而这些中间服务同时出概率的可能相对来说就极小了,相互之间可以作为互补。
(2)全量数据刷新任务:全表扫描定时任务,每周执行一次来进行兜底,确保缓存数据的全量准确同步。
(3)数据校验任务:监控Redis和数据库数据是否同步并进行补偿。
相关文章:
深入浅出Redis 缓存使用问题 | 长文分享
目录 数据一致性 先更新缓存,后更新数据库【一般不考虑】 先更新数据库,再更新缓存【一般不考虑】 先删除缓存,后更新数据库 先更新数据库,后删除缓存【推荐】 怎么选择这些方案?采用哪种合适? 缓存…...
PINN:用深度学习PyTorch求解微分方程
神经网络技术已在计算机视觉与自然语言处理等多个领域实现了突破性进展。然而在微分方程求解领域,传统神经网络因其依赖大规模标记数据集的特性而表现出明显局限性。物理信息神经网络(Physics-Informed Neural Networks, PINN)通过将物理定律直接整合到学习过程中&a…...
Ubuntu vs CentOS:Shell 环境加载机制差异分析
CentOS与Ubuntu Shell环境加载机制差异及解决方案 一、问题现象还原 在 Ubuntu 系统中,希望登录时 /etc/profile.d/ipenv.sh 脚本未自动执行,而在 CentOS 上正常工作的根本原因是: Ubuntu 和 CentOS 采用了不同的 shell 初始化机制&#x…...
在 macOS 上修改 最大文件描述符限制(Too many open files) 和 网络端口相关参数 需要调整系统级配置的详细步骤
在 macOS 上修改 最大文件描述符限制(Too many open files) 和 网络端口相关参数 需要调整系统级配置。以下是详细步骤: 在 macOS 上修改 最大文件描述符限制(Too many open files) 和 网络端口相关参数 需要调整系统级…...
C语言 |位域结构体
在C语言中,位域结构体(Bit-field Structure)是一种通过按位分配内存优化存储空间的数据结构,特别适用于需要精确控制二进制位操作的场景(如嵌入式开发、硬件寄存器操作等)。以下是关于位域结构体的核心要点…...
Charles抓包-安装和IOS抓包指导
网络抓包工具使用指南 下载安装 目前市面上主流的抓包工具包括Charles和Sniff Master。其中Charles是一款老牌抓包工具,功能强大;而Sniff Master作为后起之秀,界面简洁,操作更加人性化。 建议从官网下载最新版本: …...
Redis 学习目标
🎯 Redis 学习目标(开发者视角) ✅ 一、学习完成后能掌握的核心能力: 分类具体内容📦 基础能力熟练掌握 Redis 五大数据结构(String、List、Hash、Set、ZSet),会用也会选对场景&am…...
Redis最佳实践——性能优化技巧之Pipeline 批量操作
Redis Pipeline批量操作在电商应用中的性能优化技巧 一、Pipeline核心原理与性能优势 1. 工作机制对比: sequenceDiagramtitle 常规请求 vs Pipeline请求# 常规模式Client->>Redis: 命令1Redis-->>Client: 响应1Client->>Redis: 命令2Redis--&g…...
Redis 集群(Cluster)
1. Redis 集群概述 Redis 集群是一种分布式架构,旨在提供数据分区和高可用性。它能够通过将数据分散到多个节点上来扩展 Redis,使其能够处理更多的数据量和更高的并发请求。Redis 集群实现了自动分片、故障转移和复制等功能。 Redis 集群与传统的单节点…...
XSS 跨站SVGPDFFlashMXSSUXSS配合上传文件添加脚本
#MXSS : https://www.fooying.com/the-art-of-xss-1-introduction/ #UXSS : Universal Cross-Site Scripting(一般是浏览器自身的问题) UXSS 是利用浏览器或者浏览器扩展漏洞来制造产生 XSS 并执行代码的一种攻击类型。 MICR…...
数据库主从延迟全解析:原因、影响与解决之道
目录 一、引言:理解数据库主从架构 二、数据库主从延迟的定义与测量 2.1 主从延迟的技术定义 2.2 如何测量主从延迟 2.3 主从延迟对系统的影响 三、主从延迟的常见原因分析 3.1 网络延迟因素 3.1.1 网络质量与带宽限制 3.1.2 地理位置分布造成的延迟 3.2 …...
BERT、T5、ViT 和 GPT-3 架构概述及代表性应用
BERT、T5、ViT 和 GPT-3 架构概述 1. BERT(Bidirectional Encoder Representations from Transformers) 架构特点 基于 Transformer 编码器:BERT 使用多层双向 Transformer 编码器,能够同时捕捉输入序列中每个词的左右上下文信息…...
第十七天 - Jenkins API集成 - 流水线自动化 - 练习:CI/CD流程优化
前言 在DevOps实践中,持续集成与持续交付(CI/CD)是现代软件工程的核心支柱。作为业界使用最广泛的自动化服务器,Jenkins凭借其强大的插件生态和灵活的流水线配置能力,成为企业级CI/CD落地的首选工具。本文将深入解析J…...
SageAttention2
“SageAttention2: Efficient Attention with Thorough Outlier Smoothing and Per-thread INT4 Quantization”由Jintao Zhang等人撰写。文章提出SageAttention2,通过线程级INT4量化、Q矩阵平滑、两级累加策略等技术,在提升注意力计算效率的同时保持精度…...
.NET WPF 可视化树(Visual Tree)
.NET WPF 可视化树(Visual Tree) WPF 的可视化树(Visual Tree)是描述用户界面元素层级关系的核心概念之一,它与逻辑树(Logical Tree)共同构成了 WPF 的 UI 架构。以下是关于 WPF 可视化树的详细…...
磁盘存储下红黑树、B 树与 B + 树的原理、操作及对比
前置知识 磁盘 在计算机系统中,数据存储与检索效率深刻影响着整体性能。磁盘作为大容量数据的主要载体,其独特的 I/O 特性与树状数据结构的结合,催生出 B 树与 B 树这两种经典方案。了解它们如何适配磁盘存储,是揭开数据库、文…...
kubernetes》》k8s》》Volume 数据卷 PVC PV NFS
为啥需要数据卷 容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重…...
支持多格式且免费的图片转换工具推荐
软件介绍 今天要给大家推荐一款超好用的开源图片格式转换工具。这款工具完全免费,没有广告的干扰,让用户在使用过程中极为舒心。 ImageConverter图片格式转换 这款工具使用起来相当便捷,无需进行安装操作,只要轻轻双击图标&…...
DAPP实战篇:使用web3.js实现前端输入钱包地址查询该地址的USDT余额—操作篇
专栏:区块链入门到放弃查看目录-CSDN博客文章浏览阅读396次。为了方便查看将本专栏的所有内容列出目录,按照顺序查看即可。后续也会在此规划一下后续内容,因此如果遇到不能点击的,代表还没有更新。声明:文中所出观点大多数源于笔者多年开发经验所总结,如果你想要知道区块…...
K8S-证书更新时-误删除组件-
K8S 证书更新时-吴删除组件 [rootmaster ~] eth0 172.17.64.32 # docker rm -f docker ps | grep -E apiserver|scheduler|controller-manager| awk {print$1} 7856f2a3068e 2e1a6956d8a1 e9e3cb7870a9 31b19f4e2b22 c028146f88a5 abe4207808a3 [rootmaster ~] eth0 172.17.…...
第二章 Python爬虫篇—数据解析与提取
目录 一.数据解析概述 二.re解析和正则表达式 三.bs4解析-HTML语法 四.Xpath解析 此章节主要讲解:数据解析概述、re模块、bs4解析-html语法、xpath解析以及正则表达式。其中正则表达式我已经写过相关笔记,这里浅略叙述,如果不懂请看我笔记…...
数据仓库标准库模型架构相关概念浅讲
数据仓库与模型体系及相关概念 数据仓库与数据库的区别可参考:数据库与数据仓库的区别及关系_数据仓库和数据库-CSDN博客 总之,数据库是为捕获数据而设计,数据仓库是为分析数据而设计 数据仓库集成工具 在一些大厂中,其会有自…...
【区块链+ 人才服务】特范云区块链教学管理平台 | FISCO BCOS 应用案例
北京特范云科技有限公司利用大数据与人工智能等核心技术优势, 构建了“学、练、赛、评”一体化智慧体育课堂,促进教育技术、体育科学、IT 技术与体育教学的深度融合。公司首次提出了“体育动作积木”的教学概念, 通过区块链技术将学生的体测体…...
第一节:React 基础篇-React虚拟DOM原理及Diff算法优化策略
必考点:虚拟DOM树对比(同级比较、Key的作用、组件类型判断) 延伸:React 18中并发更新对Diff算法的影响 React虚拟DOM原理及Diff算法优化策略 虚拟DOM核心原理 概念: • 虚拟DOM(Virtual DOM)…...
MQTT的构成、使用场景、工作原理介绍
一、MQTT内容简介 MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布-订阅模式的消息传输协议【适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境】它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的…...
idea光标变成白色方块的解决方法
在使用 IDEA 进行编程时,你可能会遇到这样一个情况:原本纤细的光标突然变成了白色粗块,这不仅影响视觉体验,还可能在输入时带来困扰。别担心,本文将为你详细剖析该问题出现的原因,并提供有效的解决办法。…...
python manimgl数学动画演示_微积分_线性代数原理_ubuntu安装问题[已解决]
1.背景 最近调研python opencv, cuda加速矩阵/向量运算, 对于矩阵的线性变换, 秩, 转秩, 行列式变化等概概念模糊不清. 大概课本依旧是天书, 于是上B站搜索线性代数, 看到 3Blue1Brown 线性变换本质 视频, 点击观看. 惊为天人 --> 豁然开朗 --> 突然顿悟 --> 开心不已…...
如何为C++实习做准备?
博主介绍:程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章,首发gzh,见文末👇…...
Linux 安装 vscode
使用包管理器安装(推荐) 对于基于 Debian 的系统(如 Ubuntu): sudo apt update sudo apt install software-properties-common apt-transport-https wget -qO- https://packages.microsoft.com/keys/microsoft.asc …...
淘宝商品数据实时抓取 API 开发指南:从接口申请到数据解析实战
一、引言 在当今电商蓬勃发展的时代,淘宝作为国内电商巨头,其平台上汇聚了海量商品信息。对于电商从业者、数据分析爱好者以及众多依赖淘宝商品数据开展业务的企业而言,能够实时获取淘宝商品数据具有极高价值。例如,电商运营者…...
明远智睿SSD2351核心板在物联网领域的应用实践
物联网作为当今科技发展的热门领域,将无数设备连接在一起,实现数据的采集、传输与共享,构建起一个智能化的世界。在这庞大的物联网体系中,核心板扮演着至关重要的角色,明远智睿SSD2351核心板以其独特优势,在…...
这种情况是应为VScode的版本太新了,更新到1.86版本后要求远程连接服务器的内核版本不符合条件
这种情况是应为VScode的版本太新了,更新到1.86版本后要求远程连接服务器的内核版本不符合条件 解决方法 vscode降级,使用1.86以前的版本。亲测这种方法成功解决 首先关闭VSCode自动更新 Windows下载1.85版本链接:https://update.code.visua…...
996引擎-源码学习:PureMVC Lua 中的 Facade 类
996引擎-源码学习:PureMVC Lua 中的 Facade 类 1. 核心概念1.1 外观模式1.2 多例模式2. 关键组件NotificationController:ModelView3. 主要功能4. 初始化流程5. 通信机制6. 生命周期管理1. Facade 初始化流程图2. 发送通知时序图中介者 PlayerBestRingLayerMediatorOpenLayer …...
前端学习10—Ajax
1 AJAX 简介 AJAX 全称为 Asynchronous JavaScript And XML,就是异步的 JS 和 XML 通过 AJAX 可以在浏览器中向服务器发送异步请求,最大优势为:无刷新获取数据 AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方…...
python的多线程和多进程程序编程
CPU密集型使用多进程,IO密集型使用多线程 查看进程ID和线程ID的命令分别是os.getpid()和threading.current_thread() 多进程使用multiprocessing就可以了,通常使用进程池来完成操作,阻塞主进程使用join方法 多线程使用threading模块&#…...
Python代码解释
文章目录 代码解析执行过程等价写法其他类似操作 这段代码使用了 Python 的 map() 函数和 lambda 表达式来对列表中的每个元素进行平方运算。让我详细解释一下: 代码解析 numbers [1, 2, 3, 4] squared list(map(lambda x: x**2, numbers))numbers [1, 2, 3, …...
DNS正反向解析复习,DNS主从服务,转发服务及DNS和nginx联合案例(不断更新)
正向解析 1、配置主机名 [rootlocalhost ~]# dnf install bash-completion -y #一个按tap键补全的软件 [rootlocalhost ~]# hostnamectl hostname dns #改主机名为dns [rootlocalhost ~]# exit ssh root你的IP地址 要重启才会生效2、安装bind [rootdns ~]# dnf install b…...
甜心速达智慧潮流精选超市、即时零售新业态,打造可持续发展商业模式
四川甜心速达科技有限公司、现公司运营高管团队均为美团高级运营师,公司高管团队人均获得“全国工商联人才交流服务中心”创业指导师、市场营销师等、公司致力于优化线上店铺人效比和资源匹配等问题,已经实现了对即时零售行业的资源整合,并融…...
大白话聊MySQL覆盖索引
目录 一、什么是覆盖索引?二、使用了覆盖索引 vs 没使用覆盖索引的区别三、例子说明四、总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗! …...
C++ inline和define(宏)
文章目录 Inline 函数是什么意思?C中哪些函数不能声明为inline?内联(inline)函数和 #define(宏) Inline 函数是什么意思? inline是内联的意思,可以定义比较小的函数。因为函数频繁调用会占用很多的栈空间,…...
Python中的eval()函数详解
文章目录 Python中的eval()函数详解基本语法基本用法安全性问题安全使用建议实际应用场景与exec()的区别性能考虑总结 Python中的eval()函数详解 eval()是Python的一个内置函数,用于执行字符串形式的Python表达式并返回结果。它是一个强大但需要谨慎使用的函数。 …...
rancher 解决拉取dashboard-shell镜像失败的问题
问题背景 在 Kubernetes 集群中部署 Rancher 后,点击右上角的 "Shell" 按钮时,Rancher 会动态创建一个 dashboard-shell-xxxxx Pod,用于提供 Web 终端功能。然而,由于默认镜像 rancher/shell:v0.1.21 托管在 Docker Hu…...
在Ubuntu服务器上安装Docker(支持Ubuntu 20.04/22.04等版本):
1. 卸载旧版本(如有) 如果系统曾安装过旧版Docker,先清理残留: sudo apt remove docker docker-engine docker.io containerd runc2. 添加Docker官方仓库 安装依赖工具 sudo apt update sudo apt install -y ca-certificates …...
【Linux 进程控制】—— 进程亦生生不息:起于鸿蒙,守若空谷,归于太虚
欢迎来到一整颗红豆的博客✨,一个关于探索技术的角落,记录学习的点滴📖,分享实用的技巧🛠️,偶尔还有一些奇思妙想💡 本文由一整颗红豆原创✍️,感谢支持❤️!请尊重原创…...
K8s常用基础管理命令(一)
基础管理命令 基础命令kubectl get命令kubectl create命令kubectl apply命令kubectl delete命令kubectl describe命令kubectl explain命令kubectl run命令kubectl cp命令kubectl edit命令kubectl logs命令kubectl exec命令kubectl port-forward命令kubectl patch命令 集群管理命…...
WebChat 一款非常好用的浏览器侧边栏 AI 问答插件
文章目录 使用方法及效果展示划线引用自定义工具自定义模型设置 主要功能1. 划线引用功能2. 自定义划线工具3. 聊天功能4. 历史记录管理5. 界面特性 安装方法方法一:直接安装发布版本(推荐)方法二:从源码构建安装(开发…...
kubernetes入门篇之创建一个nginx容器
上几篇讲了部署master和worker node 及网络插件calico, 现在开始实际运行一个容器。 1. 新建nginx.yaml文件 方式1:直接创建一个pod 和一个 service,一般不直接这样创建,该方式仅适用于测试或学习 apiVersion: v1 kind: Pod …...
回顾 | 2025香港Web3嘉年华:CertiK以创新技术定义安全未来
4月6日至9日,Web3安全巨头CertiK亮相2025香港Web3嘉年华。活动期间,CertiK不仅设立独立展位与广大Web3生态参与者深入互动,更通过高层次的技术交流与前沿研究成果展示,成为本届盛会备受瞩目的焦点。 耶鲁大学计算机科学系教授、C…...
HTML5的笔记
文章目录 1.HTML的概念1.1HTML的基本骨架 2.标签语法2.1标签的关系 3.标签3.1双标签3.1.1标题标签<h1~h6>3.1.2段落标签<p>3.1.3文本格式化标签3.1.4超链接标签<a>3.1.5音频和视频标签audio和<vedio>3.1.6列表标签3.1.7表格标签 3.2单标签3.2.1换行标签…...
LeetCode.2843. 统计对称整数的数目
统计对称整数的数目 题目解题思路思路1.v1Code 思路优化1.v2Code 思路优化1.v3Code复杂度分析 题目 2843. 统计对称整数的数目 给你两个正整数 low 和 high 。 对于一个由 2 * n 位数字组成的整数 x ,如果其前 n 位数字之和与后 n 位数字之和相等,则认…...