之前的讲解,主要是在讲解redis如何支撑海量数据、高并发读写、高可用服务的架构
从这一讲开始,正式开始做业务系统的开发
商品详情页,缓存架构,90%是大量的业务(没有什么级数含量),10%最有级数含量的就是架构
1、上亿流量的商品详情页的多级缓存架构
采用三级缓存:nginx本地缓存+redis分布式缓存+tomcat 堆缓存的多级缓存架构
nginx 本地缓存
redis分布式缓存
tomcat web应用 堆缓存
实时性要求非常高的数据:库存(希望当库存变化的时候,尽可能更快将库存显示到页面上去,而不是等了很长时间,库存才反应到页面上去)
实时性要求不高的数据:商品的基本信息(名称、颜色、版本、规格参数、等等)
对于实时性较高的数据来说,采用MYSQL和redis缓存双写的方案,这样缓存的时效性最高
nginx+lua脚本
lua脚本作为代码,部署到nginx本地,做第一层的业务逻辑
2、多级缓存架构中每一层的意义
1)nginx本地缓存,扛的是热数据的高并发访问,大量的热数据的访问,即经常会访问的那些数据,就会被保留在nginx本地缓存内。
2)redis分布式缓存,扛的是很高的离散访问,支撑海量的数据,高并发的访问,高可用的服务
3)tomcat 堆缓存主要是扛redis大规模灾难的
3、最经典的缓存+数据库读写的模式:cache aside pattern
1)Cache Aside Pattern
读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据放入缓存
更新的时候,先删除缓存,再更新数据库
2)为什么是删除缓存,而不是更新缓存?
很多时候,复杂点的缓存场景,因为缓存有的时候,不简单是数据库中直接取出来的值,在更新操作较多的数据项上,访问到的几率还是比较少,没有必要每次进行更新的时候都对缓存进行修改
4、数据库与缓存双写,可能导致数据不一致的问题,围绕和结合实时性较高的库存服务,把数据库与缓存双写不一致的问题及其解决方案,给大家讲解一下
5、最初级的缓存不一致问题以及解决方案
问题:先修改数据库,再删除缓存,如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致
解决思路:先删除缓存,再修改数据库,如果修改数据库失败了,那么数据库中是旧数据,缓存中是空的,因为读的时候缓存没有,则度数据库中的旧数据,然后更新到缓存中
6、比较复杂的数据不一致问题
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改,一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中
然后数据变更的程序完成了数据库的修改,则数据库和缓存中的数据不一样了。。。
7、高并发情况下数据一致性的解决方案——数据库与缓存更新与读取操作进行异步串行化
更新数据库的时候,根据数据库的唯一标识,将操作路由之后,发送到一个jvm内部的队列中
一个队列对应一个工作线程(根据商品ID取hash值并对队列个数取模值,确定入队信息)
每个工作线程串行拿到对应的操作,然后一条一条执行
这样的话,一个数据变更的操作,先执行,删除缓存,然后再去更新数据库,但是还没完成更新
此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没有意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,
那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
8.高并发的场景下,该解决方案要注意的问题
1)读请求长时阻塞
如果缓存是空的,有两种情况,一是,数据库里本身就没有这条数据,这是可以判断一下该内存队列有没有数据库更新操作,如果没有数据库更新操作,说明这条数据可能压根就是空的,那么不用hang住,就返回空。
如果内存队列里,有这条商品的更新操作,hang一会儿,去等待那个操作快速完成,然后返回。
2)读请求并发量过高
3)多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过nginx服务器路由到相同的服务实例上
Service1 Service2 Service3
对同一个商品的读写请求,全部路由到同一台机器上
nginx,hash路由的功能
4)热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能造成某台机器的压力过大
就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是特别大
来源:https://www.cnblogs.com/lvjygogo/p/8661528.html