多级缓存详解
多级缓存
传统缓存: 传统缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库。
这种模式下请求一定要经过Tomcat处理,Tomcat的性能就成为了整个系统的瓶颈;并且Redis的缓存也有过期时间,一旦过期,请求依旧会直接打到数据库,对数据库带来冲击。
多级缓存: 多级缓存就是充分利用请求处理的每个环节,分别添加上缓存,减轻tomcat压力,提升服务器性能。例如:浏览器客户端缓存 、 Nginx本地缓存 、 Redis缓存 、 Tomcat进程缓存 。(缓存从左到右依次向下,最后到达数据库,注意这里的Redis缓存在Tomcat前面了,因为Nginx里也可以编程)
垂直分表
同一个商品的信息可以考虑保存在两张表里面,关于这个商品的不同类型的信息各自保存在一个表里面。例如可以是商品库存和售出信息保存在一张表,商品的基本信息保存在另外一张表里面,这样当商品库存发生变化的时候,缓存中的商品基本信息可以不用改动。
Nginx
nginx是 反向代理服务器(接收客户端请求,转发给后端服务) , 负载均衡器(多个后端服务之间分发请求,提高系统吞吐量) , 静态资源服务器(提供HTML、CSS、JS、图片等静态资源) 等。 用户的请求会先来到Nginx,静态资源在nginx中获取,页面上的数据通过ajax异步地从后端服务中获取。
Caffeine
分布式缓存: 例如Redis,可以在集群中 共享 ,容量更大,可靠性更高。但是会有网络IO地开销。
进程本地缓存: 例如HashMap、GuavaCache等,缓存在本地,没有网络IO开销,速度更快。但是存储容量有限,且集群间无法共享。
Caffeine是目前性能最高,更强大的缓存库。它可以设置缓存的 容量 、过期策略(按时间过期、按容量过期) 、超强的并发性能 。
Lua脚本
由于我们将redis层放在的tomcat进程层的上一层,所以操作redis的事情就由nginx来干了,而Lua是操作nginx的语言。
lua是一种轻量小巧的脚本语言,用C语言编写,设计目的是嵌入应用程序中。
OpenResty
OpenResty是一个基于Nginx的高性能Web平台,通过Lua扩展Nginx实现的可伸缩的Web平台。OpenResty内置了Nginx,在nginx的基础上进行了功能的扩展,可以直接把这个当成nginx来使用,只是可以有更多的功能。
本来nginx是拦截请求之后转发给后端的tomcat服务器来处理,现在是交给lua脚本来处理,在lua脚本中填写业务逻辑。
原生的Nginx不支持lua模块,我们就没办法再nginx中做业务处理。有了OpenResty之后,我们就可以在Nginx中引入lua脚本模块,然后通过编写lua脚本在nginx做业务逻辑的处理,可以在nginx层就做一层缓存,浏览器请求先被nginx捕捉到然后执行lua脚本,脚本中封装了http请求,再将该请求发送出去由nginx转发给后续业务(redis层或者tomcat层)。
Redis缓存预热
服务刚刚启动时,redis中并没有缓存,如果所有商品数据都在第一次查询的时候添加缓存,那么数据库就会面临很大的压力。所以就要在实际开发中做缓存的预热,利用大数据统计用户访问的热点数据,在项目启动的时候将这些热点数据提前放到缓存中。
通过配置一个RedisHandler的Bean实现Initializing接口并且实现afterPropertieSet()方法,在其中实现缓存预热。这里涉及到Bean的生命周期,这个afterPropertieSet方法是在Bean的依赖注入之后自动调用。
Nginx层查询Redis
OpenResty也为Nginx提供了操作Redis的模块,只要引入该模块就能直接使用,直接操作Redis了。这里和redis建立连接使用的是长连接,使用完后会放进连接池,规定时间没用该连接才会被释放。
Nginx本地缓存
OpenResty为Nginx提供了shard dict功能,可以在nginx的多个worker之间共享数据,实现nginx本地缓存功能。
修改item.lua中的read_data函数,优先查询本地缓存,未命中的时候再查询Redis、Tomcat,查询Redis和Tomcat成功后,将数据写入本地缓存,并设置有效期。
数据同步
数据库(真实数据)与缓存数据的一致性是我们要解决的问题,也就是数据同步问题。
常见的数据同步方法有3种:
设置有效期: 缓存设置有效期,到期自动删除,再次查询的时候更新。
同步双写: 更新数据库的时候,直接修改缓存。
异步通知: 更新数据库的时候发送事件通知,使用MQ或者其它机制(监听数据库binlog)来接收通知后修改缓存数据。
Canal监听binlog
camal是通过mysql的主从同步使用的binlog来实现了,canal把自己伪装成了一个slave节点,然后需要监听的mysql作为master生成binlog,canal不断去读取master的binlog,如果binlog发生变化了,那么canal立刻就知道了,然后根据变化来进行后续的处理。所以要想使用canal,还要去实现mysql的 主从同步 。