面试官:你好,请问你做过的项目中,服务的最该qps是多少? 候选人:我们的服务高峰访问量非常大,在双十一活动的时候QPS大概是10万左右 面试官:这么大的访问量,服务面临的压力一定非常高,你们是怎么设计的呢? 候选人:我们的服务设计师采用了二级缓存,把一些热点数据放入到本地缓存,比如活动的数据,把一些非热点的数据放入到redis缓存,比如活动-礼品数据, 接口优先查询一级缓存,如果一级缓存没有数据,接着会查询二级缓存,二级缓存不存在,才会查询数据库,这样设计可以减少数据库的访问压力,加快查询效率。 面试官:从你的设计来看,你的数据存储到三个地方,如果涉及到数据更新,你是怎么保证他们三折中间的数据一致性呢?
什么是数据一致性
我们通常说的数据一致性值得是在程序运行过程中本地缓存,分布式缓存redis,mysql数据库数据三者质检的数据一致性。
这里本地缓存常见的有 hashmap, currenthashmap, guava cache , caffeinc 分布式缓存常见的有redis, memcache.
常见的数据不一致的场景有:
1 本地缓存与mysql不一致
2 redis缓存与mysql不一致
引入本地缓存的目的是增加服务的吞吐量,但同时也让架构变得复杂。所以要谨慎使用本地缓存。一般我们习惯把热点数据放入本地缓存中,非热点数据放入分布式缓存reids中 。
本地缓存与DB一致性解决方案
1 MQ方案(推荐 通用方案)
当服务有多个节点时,每个节点之间的本地缓存如何保持一致性
方案是 当一个服务的本地缓存更新时,更新完DB和缓存后,发送更新mq的广播消息
其他节点消费mq,读取db数据更新本地缓存。保持节点之间本地缓存一致
2 redis缓存增加版本
问答: 我们有类似的场景用到了两个方案 一. 通过zk实现一个缓存集群,后台运营进行了了写操作,只允许集群主节点去进行DB的数据更新,更新完毕后,主节点会通知从节点向自己拉数据,这时读的是缓存的数据,不再走DB了,向从节点取数时会有数据版本或者叫数据摘要来保证数据一致性. 二.不分主从,谁更新了数据,谁向redis里对应的key写数,并通过发布订阅的方式,通知其他机器更新本地缓存.
第一种:zk几乎是秒级通知,但是要考虑到项目是否有zk中间件。 第二种:redis发布订阅也可以,可以当轻量级mq用,小项目用这个没问题,但大项目或者头部大厂都是用单独的mq中间件实现通知的。
问题一:节点一更新数据后发给mq广播,其他两个节点还没来得及更新就有新的请求分配给他们处理,此时这两个节点是先判断消息队列里有没有消息再处理请求吗? 问题二:没有引入mq的方案里,版本号是存在redis中,每次处理请求还是要访问redid版本号和本地匹配,那本地缓存的性能提升提现在哪里?是查询redis的数据量变小(以前要查全部信息,现在只查版本号)是这样理解吗
第一个问题:对于互联网项来说,一般都是弱一致性,所以业务一般是允许其它两个节点出现极短暂的不一致性的,不用判断消息是否存在。 第二个问题:高并发下redis判断版本号非常快,基本毫秒级别,没性能问题,不用担心
那请问如果是转账交易等要求强一致性的高并发场景,我们该如何考虑引入缓存以及保证一致性呢
强一致性,一般就是要保证事务的一致性了,金融交易类,可以集成seeta这类事务框架,利用tcc两阶段提交实现
redis缓存与数据库双写不一致的问题
在高并发写的情况下,会出现数据库缓存不一致的问题,会出现在以下场景中 1 修改DB更新缓存的场景
2 修改DB删除缓存的场景
写入redis一般是不会失败的
一般是写入db失败,我们可以先写入db
解决问题 1 延迟双删: 先清楚缓存,再执行update sql,最后(延迟N秒) 再执行一次删除缓存 延迟时间一般为3-5s 优点:方案相对比较简单,对于非高并发业务比较合适 缺点: 1 有等待缓解,如果系统要求低延时,这种场景就不合适 2 不适合秒杀这种频繁修改数据和要求数据强一致的场景 3 延时时间是一个预估值,不能确保
2 基于定时任务的方案(先写db再写redis):
启动一个定时任务定时将db数据同步到redis
1 更新db数据,同时写入数据到redis
2 启动一个定时任务定时将db数据同步到redis
3 前端发起接口查询请求,先从redis查询数据,如果redis时间失效,查不到数据会穿透到db,增加定时任务同步数据可以增加redis命中率。
, 4 redis没有数据,加一个分布式锁,再从reids数据查询 5 查询redis数据返回,并写入到redis 优点: 方案相对简单,对于高并发业务比较合适,相对是一个比较高可用的方案。 通过定时任务更新db数据到redis,保持数据一致性。 缺点:暂无
3 分布式锁:
使用分布式锁可以再不影响并发性能的前提下,协调各处理线程间的关系,使数据库与缓存数据一致。
只需要对数据库中的这个共享数据的访问通过分布式锁来协调对其的操作访问即可。
4 基于binlog方案(推荐,比较解耦)
a 更新db数据
b 通过canal中间件监听mysql binlog,同时将数据同步到mq
c 启动一个数据处理应用,消费mq数据并进行数据加工
d 将加工后的数据写入redis 和es
e 查询redis返回
优点:方案比较解耦,比较适合大公司的高并发业务
缺点:引用了多个中间件,比如canal, kafka 还引入了数据处理程序,比较复杂
5 自动或手动补偿方案(先写redis再写db)
1 先写入数据到redis
2 其他用户读取时先都redis
3 读取到redis数据进行下单时,先不写数据库,而是发送mq,让mq去做订单处理更新db(在mq处理的期间处理好redis和db的同步)
优点:
适合秒杀类业务,另外通过定时任务自动补偿和手动补偿写入到db,这种方案高可用方面比较好
缺点:
需要开发额外的定时任务
自动或手动补偿方案方案Redis压力非常大,所以对它要求比较高,得集群加哨兵吧
是的,这类超高并发,使用独立的redis集群跟业务分开,避免影响主业务。另外,redis哨兵写入有性能瓶颈,一般都用cluster集群 你可能理解错了,我说的哨兵不是redis的模式,是 Redis Sentinel,是维护redis高可用的一个进程 redis高可用哨兵方案因为只有主节点能写入,高并发写的场景,它的写入能力会有瓶颈,所以要换成redis cluster高可用方案
考虑2级缓存,第一级抗压力抗流量,第二级作为第一级的热点数据,后面才是db。 作为增删改而言,直接面相db要看是否需要强事物,就目前4个模型而言都是若事物性,扛流量为先,不适合交易系统,适合浏览性系统,最终一致即可。 基于定时任务方案再面临1e级别以上的数据量的时候是灾难,估计500w可能都难以支持,如果强用这种方式会加大开发难度和设计难度。
comments powered by Disqus