传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,存在下面的问题:




其中包含两张表:
项目结构如图所示:
其中的业务包括:
3.2.新增商品 在com.heima.item.web包的ItemController中可以看到接口定义:
3.3.修改商品 在com.heima.item.web包的ItemController中可以看到接口定义:
3.4.修改库存 在com.heima.item.web包的ItemController中可以看到接口定义:
3.5.删除商品 在com.heima.item.web包的ItemController中可以看到接口定义:
这里是采用了逻辑删除,将商品状态修改为3 3.6.根据id查询商品 在com.heima.item.web包的ItemController中可以看到接口定义:
这里只返回了商品信息,不包含库存 3.7.根据id查询库存 在com.heima.item.web包的ItemController中可以看到接口定义:
3.8.启动 注意修改application.yml文件中配置的mysql地址信息:
需要修改为自己的虚拟机地址信息、还有账号和密码。 修改后,启动服务,访问:http://localhost:8081/item/10001即可查询数据
我们需要准备一个反向代理的nginx服务器,如上图红框所示,将静态的商品页面放到nginx目录中。 页面需要的数据通过ajax向服务端(nginx业务集群)查询。 4.1.运行nginx服务 这里我已经给大家准备好了nginx反向代理服务器和静态资源。 我们找到课前资料的nginx目录:
将其拷贝到一个非中文目录下,运行这个nginx服务。 运行命令: start nginx.exe 然后访问 http://localhost/item.html?id=10001即可:
4.2.反向代理 现在,页面是假数据展示的。我们需要向服务器发送ajax请求,查询商品数据。 打开控制台,可以看到页面有发起ajax查询数据:
而这个请求地址同样是80端口,所以被当前的nginx反向代理了。 查看nginx的conf目录下的nginx.conf文件:
其中的关键配置如下:
其中的192.168.150.101是我的虚拟机IP,也就是我的Nginx业务集群要部署的地方:
完整内容如下: #user nobody; worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; #tcp_nopush on; keepalive_timeout 65; upstream nginx-cluster{ server 192.168.150.101:8081; } server { listen 80; server_name localhost; location /api { proxy_pass http://nginx-cluster; } location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
本地进程缓存
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:
分布式缓存,例如Redis:
进程本地缓存,例如HashMap、GuavaCache:
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。
GitHub地址GitHub - ben-manes/caffeine: A high performance caching library for Java

Caffeine示例
可以通过item-service项目中的单元测试来学习Caffeine的使用:

Caffeine提供了三种缓存驱逐策略:
基于容量:设置缓存的数量上限

基于时间:设置缓存的有效时间

基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。
在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。
案例:实现商品的查询的本地进程缓存
利用Caffeine实现下列需求:

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。官网:The Programming Language Lua

HelloWorld
1. 在Linux虚拟机的任意目录下,新建一个hello.lua文件

2.添加下面的内容

3.运行

数据类型

可以利用type函数测试给定变量或者值的类型:

变量
Lua声明变量的时候,并不需要指定数据类型:

访问table:

循环
数组、table都可以利用for循环来遍历:


函数
定义函数的语法:

例如,定义一个函数,用来打印数组:

条件控制
类似Java的条件控制,例如if、else语法:

与java不同,布尔表达式中的逻辑运算是基于英文单词:

案例:自定义函数,打印table
需求:自定义一个函数,可以打印table,当参数为nil时,打印错误信息
初识OpenResty
OpenResty® 是一个基于 Nginx的高性能 Web 平台,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。具备下列特点:
官方网站:OpenResty® - 中文官方站

看到里面的nginx目录了吗,OpenResty就是在Nginx基础上集成了一些Lua模块。 6)配置nginx的环境变量 打开配置文件: vi /etc/profile 在最下面加入两行: export NGINX_HOME=/usr/local/openresty/nginx export PATH=${NGINX_HOME}/sbin:$PATH NGINX_HOME:后面是OpenResty安装目录下的nginx的目录 然后让配置生效: source /etc/profile
所以运行方式与nginx基本一致: # 启动nginx nginx # 重新加载配置 nginx -s reload # 停止 nginx -s stop nginx的默认配置文件注释太多,影响后续我们的编辑,这里将nginx.conf中的注释部分删除,保留有效部分。 修改/usr/local/openresty/nginx/conf/nginx.conf文件,内容如下: #user nobody; worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 8081; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } 在Linux的控制台输入命令以启动nginx: nginx 然后访问页面:http://192.168.150.101:8081,注意ip地址替换为你自己的虚拟机IP:
案例:OpenResty快速入门,实现商品详情页数据查询
商品详情页面目前展示的是假数据,在浏览器的控制台可以看到查询商品信息的请求:
而这个请求最终被反向代理到虚拟机的OpenResty集群:

需求:在OpenResty中接收这个请求,并返回一段商品的假数据。
步骤 步骤一:修改nginx.conf文件
1.在nginx.conf的http下面,添加对OpenResty的Lua模块的加载:
2.在nginx.conf的server下面,添加对/api/item这个路径的监听

步骤 步骤二:编写item.lua文件
1.在nginx目录创建文件夹:lua

2.在lua文件夹下,新建文件:item.lua

3.内容如下:

4.重新加载配置

OpenResty获取请求参数
OpenResty提供了各种API用来获取不同类型的请求参数:

案例:获取请求路径中的商品id信息,拼接到json结果中返回
在查询商品信息的请求中,通过路径占位符的方式,传递了商品id到后台:

需求:在OpenResty中接收这个请求,并获取路径中的id信息,拼接到结果的json字符串中返回
多级缓存需求

案例:获取请求路径中的商品id信息,根据id向Tomcat查询商品信息
这里要修改item.lua,满足下面的需求:
1.获取请求参数中的id
2.根据id向Tomcat服务发送请求,查询商品信息
3.根据id向Tomcat服务发送请求,查询库存信息
4.组装商品信息、库存信息,序列化为JSON格式并返回
nginx内部发送Http请求
nginx提供了内部API用以发送http请求:

返回的响应内容包括:
注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。
但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:
封装http查询的函数
我们可以把http查询的请求封装为一个函数,放到OpenResty函数库中,方便后期使用。
1.在/usr/local/openresty/lualib目录下创建common.lua文件:

2.在common.lua中封装http查询的函数

使用Http函数查询数据
我们刚才已经把http查询的请求封装为一个函数,放到OpenResty函数库中,接下来就可以使用这个库了。

查询到的是商品、库存的json格式数据,我们需要将两部分数据组装,需要用到JSON处理函数库。
JSON结果处理
OpenResty提供了一个cjson的模块用来处理JSON的序列化和反序列化。
官方地址:https://github.com/openresty/lua-cjson/



Tomcat集群的负载均衡

添加redis缓存的需求

冷启动与缓存预热
冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力。
缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。
我们数据量较少,可以在启动时将所有数据都放入缓存中。
缓存预热
1.利用Docker安装Redis

2.在item-service服务中引入Redis依赖

3.配置Redis地址

4.编写初始化类

OpenResty的Redis模块
OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用:



案例:查询商品时,优先Redis缓存查询
需求:

Nginx本地缓存需求

nginx本地缓存
OpenResty为Nginx提供了shard dict的功能,可以在nginx的多个worker之间共享数据,实现缓存功能。


案例:在查询商品时,优先查询OpenResty的本地缓存
需求:
nginx本地缓存
修改后的查询逻辑:

缓存同步策略
缓存数据同步的常见方式有三种:
设置有效期:给缓存设置有效期,到期后自动删除。再次查询时更新
同步双写:在修改数据库的同时,直接修改缓存
异步通知:修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据
缓存同步策略
基于MQ的异步通知:


初识Canal
Canal [kə'næl],译意为水道/管道/沟渠,canal是阿里巴巴旗下的一款开源项目,基于Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。GitHub的地址:GitHub - alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅&消费组件
Canal是基于mysql的主从同步来实现的,MySQL主从同步的原理如下:

Canal就是把自己伪装成MySQL的一个slave节点,从而监听master的binary log变化。再把得到的变化信息通知给Canal的客户端,进而完成对其它数据库的同步。

安装和配置Canal
下面我们就开启mysql的主从同步机制,让Canal来模拟salve
修改文件: vi /tmp/mysql/conf/my.cnf 添加内容: log-bin=/var/lib/mysql/mysql-bin binlog-do-db=heima 配置解读:
大家可以上传到虚拟机,然后通过命令导入: docker load -i canal.tar 然后运行命令创建Canal容器: docker run -p 11111:11111 --name canal \ -e canal.destinations=heima \ -e canal.instance.master.address=mysql:3306 \ -e canal.instance.dbUsername=canal \ -e canal.instance.dbPassword=canal \ -e canal.instance.connectionCharset=UTF-8 \ -e canal.instance.tsdb.enable=true \ -e canal.instance.gtidon=false \ -e canal.instance.filter.regex=heima\\..* \ --network heima \ -d canal/canal-server:v1.1.5 说明:
Canal客户端
Canal提供了各种语言的客户端,当Canal监听到binlog变化时,会通知Canal的客户端。

Canal提供了各种语言的客户端,当Canal监听到binlog变化时,会通知Canal的客户端。不过这里我们会使用GitHub上的第三方开源的canal-starter。地址:GitHub - NormanGyllenhaal/canal-client: spring boot canal starter 易用的canal 客户端 canal client
引入依赖:

编写配置:

编写监听器,监听Canal消息:

Canal推送给canal-client的是被修改的这一行数据(row),而我们引入的canal-client则会帮我们把行数据封装到Item实体类中。这个过程中需要知道数据库与实体的映射关系,要用到JPA的几个注解:

多级缓存总结
