1. 本地进程缓存
1.1 简述分布式缓存与本地缓存的优缺点各是什么?
分布式缓存,例如Redis:
优点:存储容量更大、可靠性更好、可以在集群间共享
缺点:访问缓存有网络开销
场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
进程本地缓存,例如HashMap、GuavaCache:
优点:读取本地内存,没有网络开销,速度更快
缺点:存储容量有限、可靠性较低、无法共享
场景:性能要求较高,缓存数据量较小
1.2 Caffeine的缓存驱逐策略有几种,分别是什么?
Caffeine提供了三种缓存驱逐策略:
1.基于容量:设置缓存的数量上限
2.基于时间:设置缓存的有效时间
3.基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。
2. Lua语言
2.1 字符串拼接
> local str='hello' .. ' world' .. '!' print(str)
hello world!
2.2 for语句
local arr={"java","python","lua"}
local map={name="tom",age="22",addr="JiNan"}
--遍历数组,ipairs表示递增成对的遍历,i:Incremental,递增,pairs:一对
for index,value in ipairs(arr) do
print(index,value)
end
--遍历集合
for key,value in pairs(map) do
print(key,value)
end
2.3 函数
--定义函数实现数组的遍历
local function printArr(arr)
--判断数组是否为空
if(not arr) then --nil也表示false,此时数组为空
print('数组不能为空')
return nil;
end
--正常情况
for index,value in ipairs(arr) do
print(index,value)
end
end
printArr(arr)
2.4 其他细节
1.数组的下标是从1开始的
2.变量范围
#local 修饰的变量是局部变量
#直接定义的变量是全局变量
> arr={10,'ABC',true,nil}
> print(arr)
table: 0x21ffd90
> print(arr[1])
10
> print(arr[2])
ABC
> print(arr[4])
nil
3. OpenResty
3.1 OpenResty是什么?有哪些特点?
概念:
OpenResty是一个基于Nginx的高性能Web平台,它结合了Nginx和Lua脚本语言的功能。OpenResty允许开发人员使用Lua脚本在Nginx服务器上编写自定义的动态网页应用程序,而无需将请求转发给后端应用服务器。
OpenResty具有以下特点:
①高性能
②可扩展性
③Lua脚本支持
④内置丰富的模块
⑤高度集成
3.2 使用OpenResty
1.Centos安装OpenResty后默认路径在:
/usr/local/openresty
2.修改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;
#lua 模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
#c模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
server {
listen 8081;
server_name localhost;
location /api/item {
#默认的响应类型
default_type application/json;
#响应结果由lua/item.lua这个文件决定
content_by_lua_file lua/item.lua;
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
3.在nginx目录中创建lua/item.lua文件
--向客户端发送响应
ngx.say('{"id":10001,"name":"SALSA AIR","title":"RIMOWA 22寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4","price":17900,"image":"https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp","category":"拉杆箱","brand":"RIMOWA","spec":"","status":1,"createTime":"2019-04-30T16:00:00.000+00:00","updateTime":"2019-04-30T16:00:00.000+00:00","stock":2999,"sold":31290}')
3.3 OpenResty获取请求参数
3.4 OpenResty查询Tomcat
3.4.1 编写请求响应的配置文件
路径:/usr/local/openresty/nginx/lua/item.lua
--导入common.lua函数库
local common=require('common')
local read_http=common.read_http
--导入cjson的函数库
local cjson=require("cjson")
--获取路径参数
local id=ngx.var[1]
--查询商品信息
local itemJSON=read_http("/item/" .. id,nil)
--查询库存信息
local stockJSON=read_http("/item/stock/" .. id,nil)
--JSON转化为lua的table
local item=cjson.decode(itemJSON)
local stock=cjson.decode(stockJSON)
--组合数据
item.stock=stock.stock
item.sold=stock.sold
--把item序列化为json,返回结果
ngx.say(cjson.encode(item))
3.4.2 编写nginx的配置文件,实现反向代理
#反向代理
location /item {
proxy_pass http://192.168.137.1:8081;
}
3.5 访问Tomcat集群
3.5.1 配置nginx的参数
#指定Tomcat集群
upstream tomcat-cluster {
#hash $request_uri;:这一行指定了请求分发的策略是使用请求URI的哈希值。这意味着相同的请求URI会被分发到同一个后端服务器上,有助于保持会话的连续性,特别是对于那些需要会话状态的应用。
hash $request_uri;
server 192.168.137.1:8081;
server 192.168.137.1:8082;
}
#-----------------------------------------------
#反向代理
location /item {
proxy_pass http://tomcat-cluster;
}
3.6 开启本地缓存
在nginx的配置文件中添加命令:
# 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
lua_shared_dict item_cache 150m;
4. Redis缓存
OpenResty发起请求后,不会直接访问tomcat,而是先访问Redis集群,若Redis集群中没有,再访问Tomcat。
4.1 编写Redis缓存的配置文件
package com.heima.item.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.heima.item.pojo.Item;
import com.heima.item.pojo.ItemStock;
import com.heima.item.service.impl.ItemService;
import com.heima.item.service.impl.ItemStockService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @ClassName RedisHandler
* @Description redis缓存配置类
* @Author 孙克旭
* @Date 2024/11/22 16:17
*/
@Component
public class RedisHandler implements InitializingBean {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ItemService itemService;
@Autowired
private ItemStockService stockService;
/**
* 序列化工具
*/
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void afterPropertiesSet() throws Exception {
//初始化缓存,缓存预热
//1.添加热点数据(目前添加全部的数据)
List<Item> itemList = itemService.list();
//2.放入缓存
for (Item item : itemList) {
//2.1 item序列化成JSON
String json = MAPPER.writeValueAsString(item);
//2.2 存入redis
redisTemplate.opsForValue().set("item:id:" + item.getId(), json);
}
//3.添加库存数据
List<ItemStock> stockList = stockService.list();
//4.放入缓存
for (ItemStock stock : stockList) {
//4.1 stock序列化成JSON
String json = MAPPER.writeValueAsString(stock);
//4.2 存入redis
redisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);
}
}
}
该配置类会在SpringBoot启动后,自动执行类中的参数,实现Redis集群缓存预热。原因是实现了 InitializingBean
接口,所以会自动执行。
4.2 实现openResty与Redis的多级缓存
4.2.1 编写通用配置文件
文件路径:/usr/local/openresty/lualib/common.lua
-- 导入redis
local redis=require('resty.redis')
--初始化redis
local red=redis:new()
red:set_timeouts(1000,1000,1000)
-- 关闭redis连接的工具方法,其实是放入连接池
local function close_redis(red)
local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.log(ngx.ERR, "放入redis连接池失败: ", err)
end
end
-- 查询redis的方法 ip和port是redis地址,key是查询的key
local function read_redis(ip, port, key)
-- 获取一个连接
local ok, err = red:connect(ip, port)
if not ok then
ngx.log(ngx.ERR, "连接redis失败 : ", err)
return nil
end
-- 查询redis
local resp, err = red:get(key)
-- 查询失败处理
if not resp then
ngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)
end
--得到的数据为空处理
if resp == ngx.null then
resp = nil
ngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)
end
close_redis(red)
return resp
end
-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
local resp = ngx.location.capture(path,{
method = ngx.HTTP_GET,
args = params,
})
if not resp then
-- 记录错误信息,返回404
ngx.log(ngx.ERR, "http not found, path: ", path , ", args: ", args)
ngx.exit(404)
end
return resp.body
end
-- 将方法导出
local _M = {
read_http = read_http,
read_redis=read_redis
}
return _M
4.2.2 编写OpenResty访问的数据源文件
路径:/usr/local/openresty/nginx/lua/item.lua
--导入common.lua函数库
local common=require('common')
local read_http=common.read_http --向Tomcat发送请求的方法
local read_redis=common.read_redis --查询redis的方法
--导入cjson的函数库,实现序列化
local cjson=require("cjson")
--获取前端请求的路径参数
local id=ngx.var[1]
--封装查询函数,先查询redis,若结果为空,再查询tomncat
function read_data(key,path,params)
--查询redis
local resp=read_redis("127.0.0.1",6379,key)
--判断查询结果
if not resp then
ngx.log(ngx.ERR,"redis查询失败,尝试查询http,key:",key)
--redis查询失败,查询http
resp=read_http(path,params)
end
return resp
end
--查询商品信息
local itemJSON=read_data("item:id:"..id,"/item/" .. id,nil)
--查询库存信息
local stockJSON=read_data("item:stock:id:"..id,"/item/stock/" .. id,nil)
--JSON转化为lua的table
local item=cjson.decode(itemJSON)
local stock=cjson.decode(stockJSON)
--组合数据
item.stock=stock.stock
item.sold=stock.sold
--把item序列化为json,返回结果
ngx.say(cjson.encode(item))
5. 缓存同步
5.1 缓存同步策略
5.2 Canal
canal:
译意为水道/管道/沟渠,是基于java开发,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。
原理:
canal是基于mysql的主从同步来实现的,canal把自己伪装成mysql的一个slave节点,从而监听 master的binary log的变化。再把得到的变化消息通知给canal的客户端,进而完成对其它数据库的同步。
5.2.1 安装Canal
1.Canal会伪装成mysql的一个slave,所以先明确mysql的binarylog的位置,修改mysql的my.conf文件:
log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=heima
log-bin=/var/lib/mysql/mysql-bin
:设置binary log文件的存放地址和文件名,叫做mysql-bin
binlog-do-db=heima
:指定对哪个database记录binary log events,这里记录heima这个库
2.将mysql和cancal放入同一网络
创建网络:docker network create heima
让mysql加入网络:docker network connect heima mysql
3.添加cancal到容器:
docker run -p 11111:11111 --name canal \
-e canal.destinations=heima \ #cancal集群名称
-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\\..* \ #监听mysql库的名称
--network heima \
-d canal/canal-server:v1.1.5
5.3 多级缓存架构
6. 踩坑记录
1.用Docker挂载mysql:
docker run \
-p 3306:3306 \
--name mysql \
-v $PWD/conf:/etc/mysql/conf.d \
-v $PWD/logs:/logs \
-v $PWD/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--privileged \
-d \
mysql:5.7.25
2.有请求体的请求协议:Post、Put、Patch
3.复制单词:
(1)方式一:在命令模式下,将光标移动到单词上,输入 yiw
即可,y
是复制,i
是内部,w
是单词
(2)方式二:进入可视模式,通过光标选择单词,使用 y
复制,p
粘贴
4.OpenResty设置本地缓存失败
原因是编写的配置文件有错误,原来是
--判断查询结果
if not val then
ngx.log("redis查询失败,尝试查询http,key:",key)
--redis查询失败,查询http
val=read_http(path,params)
end
后经老师发现,log方法使用错误,修改后:
--判断查询结果
if not val then
ngx.log(ngx.ERR,"redis查询失败,尝试查询http,key:",key)
--redis查询失败,查询http
val=read_http(path,params)
end
我也学会了查看nginx的日志,路径:
/usr/local/openresty/nginx/logs/error.log
5.cancal异常:
报错信息:com.alibaba.otter.canal.protocol.exception.CanalClientException: java.net.ConnectException: Connection refused: connect
重启canal容器时发现指定的端口是11111,而我的配置参数是1111🌝
看来需要更加认真一点。
6.canal添加的数据时间格式问题。
当我在mysql中添加一行数据时,redis也能同步增加数据,但是发现有关时间的字段只有年月日,时分秒全为0.
后来想找一下canal的配置注解,指定时间格式,发现用@JsonFormat就行:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;//更新时间