电子科技企业网站建设,网站建设逻辑,wordpress插件 飘雪,wordpress企业主题二次开发下载前言
一般的小项目#xff0c;比如几百人左右访问的项目#xff0c;访问量几万的项目#xff0c;如果想用缓存#xff0c;单机实例完全够用。小黄图就是用的阿里云256MB配置的Redis缓存#xff0c;日几千的访问量是妥妥够用的了。Redis号称可以支撑10wqps#xff0c;当然…
前言
一般的小项目比如几百人左右访问的项目访问量几万的项目如果想用缓存单机实例完全够用。小黄图就是用的阿里云256MB配置的Redis缓存日几千的访问量是妥妥够用的了。Redis号称可以支撑10wqps当然这也给机器配置有一定的关系如果单实例满足不了需求想追求更高的性能和稳定性可以选择主从、哨兵已经更好的解决方案Redis-Cluster 集群。
面试题
如何保证缓存与数据库的双写一致性
面试官心理分析
你只要用缓存就可能会涉及到缓存与数据库双存储双写你只要是双写就一定会有数据一致性的问题那么你如何解决一致性问题
面试题剖析
一般来说如果允许缓存可以稍微的跟数据库偶尔有不一致的情况也就是说如果你的系统不是严格要求 “缓存数据库” 必须保持一致性的话最好不要做这个方案即读请求和写请求串行化串到一个内存队列里去。
串行化可以保证一定不会出现不一致的情况但是它也会导致系统的吞吐量大幅度降低用比正常情况下多几倍的机器去支撑线上的一个请求。
Cache Aside Pattern
最经典的缓存数据库读写的模式就是 Cache Aside Pattern。
读的时候先读缓存缓存没有的话就读数据库然后取出数据后放入缓存同时返回响应。更新的时候先更新数据库然后再删除缓存。
为什么是删除缓存而不是更新缓存
原因很简单很多时候在复杂点的缓存场景缓存不单单是数据库中直接取出来的值。
比如可能更新了某个表的一个字段然后其对应的缓存是需要查询另外两个表的数据并进行运算才能计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是不是说每次修改数据库的时候都一定要将其对应的缓存更新一份也许有的场景是这样但是对于比较复杂的缓存数据计算的场景就不是这样了。如果你频繁修改一个缓存涉及的多个表缓存也频繁更新。但是问题在于这个缓存到底会不会被频繁访问到
举个栗子一个缓存涉及的表的字段在 1 分钟内就修改了 20 次或者是 100 次那么缓存更新 20 次、100 次但是这个缓存在 1 分钟内只被读取了 1 次有大量的冷数据。实际上如果你只是删除缓存的话那么在 1 分钟内这个缓存不过就重新计算一次而已开销大幅度降低。用到缓存才去算缓存。
其实删除缓存而不是更新缓存就是一个 lazy 计算的思想不要每次都重新做复杂的计算不管它会不会用到而是让它到需要被使用的时候再重新计算。像 mybatishibernate都有懒加载思想。查询一个部门部门带了一个员工的 list没有必要说每次查询部门都把里面的 1000 个员工的数据也同时查出来啊。80% 的情况查这个部门就只是要访问这个部门的信息就可以了。先查部门同时要访问里面的员工那么这个时候只有在你要访问里面的员工的时候才会去数据库里面查询 1000 个员工。
最初级的缓存不一致问题及解决方案
问题先更新数据库再删除缓存。如果删除缓存失败了那么会导致数据库中是新数据缓存中是旧数据数据就出现了不一致。 解决思路先删除缓存再更新数据库。如果数据库更新失败了那么数据库中是旧数据缓存中是空的那么数据不会不一致。因为读的时候缓存没有所以去读了数据库中的旧数据然后更新到缓存中。
比较复杂的数据不一致问题分析
数据发生了变更先删除了缓存然后要去修改数据库此时还没修改。一个请求过来去读缓存发现缓存空了去查询数据库查到了修改前的旧数据放到了缓存中。随后数据变更的程序完成了数据库的修改。完了数据库和缓存中的数据不一样了…
为什么上亿流量高并发场景下缓存会出现这个问题
只有在对一个数据在并发的进行读写的时候才可能会出现这种问题。其实如果说你的并发量很低的话特别是读并发很低每天访问量就 1 万次那么很少的情况下会出现刚才描述的那种不一致的场景。但是问题是如果每天的是上亿的流量每秒并发读是几万每秒只要有数据更新的请求就可能会出现上述的数据库缓存不一致的情况。
解决方案如下
更新数据的时候根据数据的唯一标识将操作路由之后发送到一个 jvm 内部队列中。读取数据的时候如果发现数据不在缓存中那么将重新读取数据更新缓存的操作根据唯一标识路由之后也发送同一个 jvm 内部队列中。
一个队列对应一个工作线程每个工作线程串行拿到对应的操作然后一条一条的执行。这样的话一个数据变更的操作先删除缓存然后再去更新数据库但是还没完成更新。此时如果一个读请求过来没有读到缓存那么可以先将缓存更新的请求发送到队列中此时会在队列中积压然后同步等待缓存更新完成。
这里有一个优化点一个队列中其实多个更新缓存请求串在一起是没意义的因此可以做过滤如果发现队列中已经有一个更新缓存的请求了那么就不用再放个更新请求操作进去了直接等待前面的更新操作请求完成即可。
待那个队列对应的工作线程完成了上一个操作的数据库的修改之后才会去执行下一个操作也就是缓存更新的操作此时会从数据库中读取最新的值然后写入缓存中。
如果请求还在等待时间范围内不断轮询发现可以取到值了那么就直接返回如果请求等待的时间超过一定时长那么这一次直接从数据库中读取当前的旧值。
高并发的场景下该解决方案要注意的问题
读请求长时阻塞
由于读请求进行了非常轻度的异步化所以一定要注意读超时的问题每个读请求必须在超时时间范围内返回。
该解决方案最大的风险点在于说可能数据更新很频繁导致队列中积压了大量更新操作在里面然后读请求会发生大量的超时最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试看看更新数据的频率是怎样的。
另外一点因为一个队列中可能会积压针对多个数据项的更新操作因此需要根据自己的业务情况进行测试可能需要部署多个服务每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作每个库存修改操作要耗费 10ms 去完成那么最后一个商品的读请求可能等待 10 * 100 1000ms 1s 后才能得到数据这个时候就导致读请求的长时阻塞。
一定要做根据实际业务系统的运行情况去进行一些压力测试和模拟线上环境去看看最繁忙的时候内存队列可能会挤压多少更新操作可能会导致最后一个更新操作对应的读请求会 hang 多少时间如果读请求在 200ms 返回如果你计算过后哪怕是最繁忙的时候积压 10 个更新操作最多等待 200ms那还可以的。
如果一个内存队列中可能积压的更新操作特别多那么你就要加机器让每个机器上部署的服务实例处理更少的数据那么每个内存队列中积压的更新操作就会越少。
其实根据之前的项目经验一般来说数据的写频率是很低的因此实际上正常来说在队列中积压的更新操作应该是很少的。像这种针对读高并发、读缓存架构的项目一般来说写请求是非常少的每秒的 QPS 能到几百就不错了。
我们来实际粗略测算一下。
如果一秒有 500 的写操作如果分成 5 个时间片每 200ms 就 100 个写操作放到 20 个内存队列中每个内存队列可能就积压 5 个写操作。每个写操作性能测试后一般是在 20ms 左右就完成那么针对每个内存队列的数据的读请求也就最多 hang 一会儿200ms 以内肯定能返回了。
经过刚才简单的测算我们知道单机支撑的写 QPS 在几百是没问题的如果写 QPS 扩大了 10 倍那么就扩容机器扩容 10 倍的机器每个机器 20 个队列。
读请求并发量过高
这里还必须做好压力测试确保恰巧碰上上述情况的时候还有一个风险就是突然间大量读请求会在几十毫秒的延时 hang 在服务上看服务能不能扛的住需要多少机器才能扛住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新缓存也不会同一时间失效所以每次可能也就是少数数据的缓存失效了然后那些数据对应的读请求过来并发量应该也不会特别大。
多服务实例部署的请求路由
可能这个服务部署了多个实例那么必须保证说执行数据更新操作以及执行缓存更新操作的请求都通过 Nginx 服务器路由到相同的服务实例上。
比如说对同一个商品的读写请求全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由也可以用 Nginx 的 hash 路由功能等等。
热点商品的路由问题导致请求的倾斜
万一某个商品的读写请求特别高全部打到相同的机器的相同的队列里面去了可能会造成某台机器的压力过大。就是说因为只有在商品数据更新的时候才会清空缓存然后才会导致读写并发所以其实要根据业务系统去看如果更新频率不是太高的话这个问题的影响并不是特别大但是的确可能某些机器的负载会高一些。
最后
需要的朋友可以点击戳这里免费领取。
还有Java核心知识点全套架构师学习资料和视频一线大厂面试宝典面试简历模板可以领取阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题Spring源码合集Java架构实战电子书2021年最新大厂面试题。 AswB)。
还有Java核心知识点全套架构师学习资料和视频一线大厂面试宝典面试简历模板可以领取阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题Spring源码合集Java架构实战电子书2021年最新大厂面试题。