目录
3)初识Caffeine(就是在springboot学过的注解方式的cache)。
一、传统缓存的问题、多级缓存方案。
二、JVM进程缓存。
1)进程缓存和远程缓存。
在Java中,进程缓存和缓存也是两个不同的概念。
-
进程缓存:在Java中,进程缓存通常指JVM的堆内存,它是Java虚拟机为每个Java进程分配的内存空间。Java进程可以使用堆内存来存储对象、数组等数据结构,以及执行方法时所需的局部变量、方法参数等。Java程序可以通过调整JVM的参数来控制堆内存的大小,从而影响程序的性能和内存占用。
-
远程缓存:不存放在程序里面,而是放到远程缓存软件。
缓存又分进程内缓存和远程缓存两种:远程缓存如redis、memcached等,还有本地(进程内)缓存如ehcache、GuavaCache、Caffeine等。
需要注意的是,Caffeine是一个进程级别的缓存,它只在单个Java进程内生效。
2)导入商品案例。
为了演示多级缓存,我们先导入一个商品管理的案例,其中包含商品的CRUD功能。我们将来会给查询商品添加多级缓存。
1.安装MySQL
后期做数据同步需要用到MySQL的主从功能,所以需要大家在虚拟机中,利用Docker来运行一个MySQL容器。
1.1.准备目录
为了方便后期配置MySQL,我们先准备两个目录,用于挂载容器的数据和配置文件目录:
# 进入/tmp目录 cd /tmp # 创建文件夹 mkdir mysql # 进入mysql目录 cd mysql
1.2.运行命令
进入mysql目录后,执行下面的Docker命令:
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=123 \ --privileged \ -d \ mysql:5.7.25
1.3.修改配置
在/tmp/mysql/conf目录添加一个my.cnf文件,作为mysql的配置文件:
# 创建文件 touch /tmp/mysql/conf/my.cnf
文件的内容如下:
[mysqld] skip-name-resolve character_set_server=utf8 datadir=/var/lib/mysql server-id=1000
1.4.重启
配置修改后,必须重启容器:
docker restart mysql
2.导入SQL
接下来,利用Navicat客户端连接MySQL,然后导入课前资料提供的sql文件:
其中包含两张表:
tb_item:商品表,包含商品的基本信息
tb_item_stock:商品库存表,包含商品的库存信息
之所以将库存分离出来,是因为库存是更新比较频繁的信息,写操作较多。而其他信息修改的频率非常低。
3.导入Demo工程
下面导入课前资料提供的工程:
项目结构如图所示:
其中的业务包括:
分页查询商品
新增商品
修改商品
修改库存
删除商品
根据id查询商品
根据id查询库存
业务全部使用mybatis-plus来实现,如有需要请自行修改业务逻辑。
3.1.分页查询商品
在
com.heima.item.web
包的ItemController
中可以看到接口定义: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即可查询数据
4.导入商品查询页面
商品查询是购物页面,与商品管理的页面是分离的。
部署方式如图:
我们需要准备一个反向代理的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业务集群要部署的地方:
完整内容如下:
Lua#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; } } }
3)初识Caffeine(就是在springboot学过的注解方式的cache)。
这里是使用代码方式写的。
使用案例:
javapublic class CaffeineTest { /* 基本用法测试 */ @Test void testBasicOps() throws UnsupportedEncodingException { // 创建缓存对象 Cache<String, String> cache = Caffeine.newBuilder().build(); // 存数据 // cache.put("gf", "aaa"); // 取数据,不存在则返回null String gf = cache.getIfPresent("gf"); System.out.println("gf = " + gf); // 取数据,不存在则去数据库查询 String defaultGF = cache.get("defaultGF", key -> { // 这里可以去数据库根据 key查询value return "lll"; }); System.out.println("defaultGF = " + defaultGF); /** * 输出结果为: * gf = null * defaultGF = lll */ } /* 基于大小设置驱逐策略: */ @Test void testEvictByNum() throws InterruptedException { // 创建缓存对象 Cache<String, String> cache = Caffeine.newBuilder() // 设置缓存大小上限为 1 .maximumSize(1) .build(); // 存数据 cache.put("gf1", "柳岩"); cache.put("gf2", "范冰冰"); cache.put("gf3", "迪丽热巴"); // 延迟10ms,给清理线程一点时间 // Thread.sleep(10L);//打印三个都有数据,因为还没来得及清理(偶尔也是清理掉的,即前两个为null)。打开这个后,前两个为null,最后一个有数据 // 获取数据 System.out.println("gf1: " + cache.getIfPresent("gf1"));//gf1: null System.out.println("gf2: " + cache.getIfPresent("gf2"));//gf2: null System.out.println("gf3: " + cache.getIfPresent("gf3"));//gf3: 迪丽热巴 } /* 基于时间设置驱逐策略: */ @Test void testEvictByTime() throws InterruptedException { // 创建缓存对象 Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(Duration.ofSeconds(1)) // 设置缓存有效期为 10 秒 .build(); // 存数据 cache.put("gf", "柳岩"); // 获取数据 System.out.println("gf: " + cache.getIfPresent("gf"));//gf: 柳岩 // 休眠一会儿 Thread.sleep(1200L); System.out.println("gf: " + cache.getIfPresent("gf"));//gf: null } }
4)实现进程缓存。
加载Cache成为Bean:
java@Configuration public class CaffeineConfig { @Bean public Cache<Long, Item> itemCache(){ return Caffeine.newBuilder() .initialCapacity(100)//初始化100个key容量 .maximumSize(10_000)//上限是10000个key容量 .build(); } @Bean public Cache<Long, ItemStock> itemStockCache(){ return Caffeine.newBuilder() .initialCapacity(100)//初始化100个key容量 .maximumSize(10_000)//上限是10000个key容量 .build(); } }
使用Caffeine缓存:
java@RestController @RequestMapping("item") public class ItemController { @Autowired private IItemService itemService; @Autowired private IItemStockService stockService; @Autowired private Cache<Long,Item> itemCache; @Autowired private Cache<Long,ItemStock> stockCache; ......省略 @GetMapping("/{id}") public Item findById(@PathVariable("id") Long id){ //itemCache.get()方法的第二个参数是一个lambda表达式,它接受一个类型为Long的键(即id),然后返回一个类型为Item的值。 return itemCache.get(id,key -> itemService.query() .ne("status", 3).eq("id", key) .one()); } @GetMapping("/stock/{id}") public ItemStock findStockById(@PathVariable("id") Long id){ return stockCache.get(id,key -> stockService.getById(id)); } }
我们这里实现的就是Tomcat+java里面的进程缓存:
三、Lua语法入门。
1)初识Lua。
CentOS中自带Lua环境。
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。官网:https://www.lua.org/
简单写一个lua脚本:
2)数据类型、变量和循环。
lua中字符串拼接是使用..连接的,如local str = 'hello' .. 'world' #打印出来是helloworld
3)函数、条件控制。
四、多级缓存。
1)安装OpenResty。
官方网站: https://openresty.org/cn/
1.安装
首先你的Linux虚拟机必须联网
1)安装开发库
首先要安装OpenResty的依赖开发库,执行命令:
yum install -y pcre-devel openssl-devel gcc --skip-broken
2)安装OpenResty仓库
你可以在你的 CentOS 系统中添加
openresty
仓库,这样就可以便于未来安装或更新我们的软件包(通过yum check-update
命令)。运行下面的命令就可以添加我们的仓库:
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
如果提示说命令不存在,则运行:
yum install -y yum-utils
然后再重复上面的命令
3)安装OpenResty
然后就可以像下面这样安装软件包,比如
openresty
:
yum install -y openresty
4)安装opm工具
opm是OpenResty的一个管理工具,可以帮助我们安装一个第三方的Lua模块。
如果你想安装命令行工具
opm
,那么可以像下面这样安装openresty-opm
包:
yum install -y openresty-opm
5)目录结构
默认情况下,OpenResty安装的目录是:/usr/local/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
2.启动和运行
OpenResty底层是基于Nginx的,查看OpenResty目录的nginx目录,结构与windows中安装的nginx基本一致:
所以运行方式与nginx基本一致:
# 启动nginx nginx # 重新加载配置 nginx -s reload # 停止 nginx -s stop
nginx的默认配置文件注释太多,影响后续我们的编辑,这里将nginx.conf中的注释部分删除,保留有效部分。
修改
/usr/local/openresty/nginx/conf/nginx.conf
文件,以下内容覆盖原本内容:
bash#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:
3.备注
下面的这些是OpenResty快速入门时要使用的东西。
加载OpenResty的lua模块:
bash#lua 模块 lua_package_path "/usr/local/openresty/lualib/?.lua;;"; #c模块 lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
common.lua(这个是写一个函数,方便后面调用,可根据自己需求编写)
bash-- 封装函数,发送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 } return _M
释放Redis连接API:
bash-- 关闭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数据的API:
bash-- 查询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
开启共享词典:
# 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m lua_shared_dict item_cache 150m;
2)OpenResty快速入门。
1.该展示的是windows下的nginx的反向代理服务器的nginx.conf文件。
**2.**该展示的是linux下的openResty里的nginx的nginx.conf文件。
这个是添加到openResty中的nginx的nginx.conf里面的html标签中。
lua_package_path "/usr/local/openresty/lualib/?.lua;;"; 表示在lualib目录下以lua后缀名的模块文件都加载进来。
lua_package_cpath "/usr/local/openresty/lualib/?.so;;"; 表示在lualib目录下以so后缀名的模块文件都加载进来。
3.编写item.lua文件。
注意:写好文件后,linux的openResty中的nginx要重新加载,windows中的nginx也要重新加载,否则的话是访问失败(还是原来的样子,没有变化)的。
3)请求参数处理。
~:波浪线表示后面跟着正则表达式匹配。
案例:
修改openResty中的nginx的nginx.conf文件。
修改openResty中的nginx目录下的lua目录下的item.lua文件。
都改完后执行nginx -s reload重新加载,然后访问。
4)查询Tomcat。
适用于所有虚拟机连接windows系统的便捷方式:虚拟机的IP地址前三个数字不变,第四个数字替换为1,、就一定能得到wdows地址。(前提是windows防火墙关闭)
例如:
虚拟机IP地址:192.168.203.129
连接windows系统使用:192.168.203.1
lua文件的语句结束不用";",但我发现使用了";"也没有报错,要使用英文分号。将函数导出:意思就是加载这个模块(类似java中的导包)的文件可以使用该函数。这里的发送请求会被反向代理拦截,然后发到指定IP地址。
5)Tomcat集群的负载均衡。
在Nginx中,使用
hash $request_uri;
可以实现基于请求URI的负载均衡策略。这个策略会根据请求的URI对后端服务器进行哈希计算,并将同一URI的请求始终分发到同一台后端服务器上。$
符号表示引用变量的开始。在这种上下文中,$request_uri
代表了请求的URI变量。计算请求路径的哈希值,根据哈希值取余tomcat服务器数量,保证同一个请求路径只会发给同一个tomcat处理,保证进程缓存的可用性。
操作如下:
6)Redis缓存预热。
初始化redis缓存:
java@Component public class RedisHandler implements InitializingBean { @Autowired private StringRedisTemplate redisTemplate; @Autowired private IItemService itemService; @Autowired private IItemStockService stockService; @Autowired 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) { //2.1 item序列化为json String json = MAPPER.writeValueAsString(stock); //2.2 存入redis redisTemplate.opsForValue().set("item:stock:id:"+stock.getId(),json); } } }
7)查询Redis缓存。
common.lua文件:
java-- 引入redis模块 local redis = require('resty.redis') -- 初始化redis local red = redis:new() -- 设置redis超时时间 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
item.lua文件:
java-- 导入common函数库 local common = require('common') local read_http = common.read_http local read_redis = common.read_redis -- 导入cjson库 local cjson = require('cjson') -- 封装查询函数 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查询失败 resp = read_http(path,params) end return resp end --获取路径参数 local id = ngx.var[1] -- 查询商品信息 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))
改完文件后保存,并查询加载nginx。
注意:如果没有其效果,那就查lua后缀名的文件内容是否有写错。(我都是因为写错导致没有效果,可以查nginx日志,一般会告诉你因何错位)
8)Nginx本地缓存。
在 Nginx 中,"worker" 是指工作进程(worker process)。Nginx 的主进程负责管理整个服务器,而工作进程则负责处理实际的客户端请求。每个工作进程相互独立,它们可以同时处理多个客户端连接和请求。
linux的openResty里的nginx的nginx.conf文件:
item.lua文件:
java-- 导入common函数库 local common = require('common') local read_http = common.read_http local read_redis = common.read_redis -- 导入cjson库 local cjson = require('cjson') -- 导入共享词典,本地缓存 **************************************************************************************** local item_cache = ngx.shared.item_cache -- 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 -- 封装查询函数 function read_data(key,expire,path,params) -- 查询本地缓存 local val = item_cache:get(key) if not val then ngx.log(ngx.ERR,"本地缓存查询失败,尝试查询redis,key:",key) -- 查询redis val = read_redis('127.0.0.1',6379,key) -- 判断查询结果 if not val then ngx.log(ngx.ERR,"redis查询失败,尝试查询http,key:",key) -- redis查询失败 val = read_http(path,params) end end -- 查询成功,把数据写入本地缓存 item_cache:set(key,val,expire) -- 返回数据 return val end -- 888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 --获取路径参数 local id = ngx.var[1] -- 查询商品信息 local itemJSON = read_data("item:id:"..id, 1800 , "/item/"..id,nil) -- 查询库存信息 local stockJSON = read_data("item:stock:id:"..id,60,"/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))
五、缓存同步策略。
1)数据同步策略。
使用MQ还是有一些代码侵入。
我们使用下面这种:下面这种代码侵入更少。
2)安装Canal。
2.1)初识Canal。
2.2)安装和配置Canal 。
下面我们就开启mysql的主从同步机制,让Canal来模拟salve
1.开启MySQL主从
Canal是基于MySQL的主从同步功能,因此必须先开启MySQL的主从功能才可以。
这里以之前用Docker运行的mysql为例:
1.1.开启binlog
打开mysql容器挂载的日志文件,我的在
/tmp/mysql/conf
目录:这里是因为创建mysql容器的时候已经把mysql容器目录挂载到主机了,所以可以直接在主机修改对应文件。
修改文件:
vi /tmp/mysql/conf/my.cnf
添加内容:
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这个库最终效果:
[mysqld] skip-name-resolve character_set_server=utf8 datadir=/var/lib/mysql server-id=1000 log-bin=/var/lib/mysql/mysql-bin binlog-do-db=heima 在配置文件中,[mysqld]是一个段(section)的名称,表示 MySQL 服务器的配置部分。
然后重启mysql容器:
1.2.设置用户权限
接下来添加一个仅用于数据同步的账户,出于安全考虑,这里仅提供对heima这个库的操作权限。(这里是在mysql里面执行,使用mysql客户端登录执行即可)
create user canal@'%' IDENTIFIED by 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal'; FLUSH PRIVILEGES;
重启mysql容器即可
docker restart mysql
测试设置是否成功:在mysql控制台,或者Navicat中,输入命令:
show master status;
2.安装Canal
2.1.创建网络
我们需要创建一个网络,将MySQL、Canal、MQ放到同一个Docker网络中:
docker network create heima
让mysql加入这个网络:
docker network connect heima mysql
2.3.安装Canal
课前资料中提供了canal的镜像压缩包:
大家可以上传到虚拟机,然后通过命令导入:
docker load -i canal.tar
然后运行命令创建Canal容器:
在docker中,容器在同一个网络中可以使用容器名连接。
docker run -p 11111:11111 --name canal \ -e canal.destinations=heima \ -e canal.instance.master.address=mymysql: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
说明:
-p 11111:11111
:这是canal的默认监听端口
-e canal.instance.master.address=mysql:3306
:数据库地址和端口,如果不知道mysql容器地址,可以通过docker inspect 容器id
来查看
-e canal.instance.dbUsername=canal
:数据库用户名
-e canal.instance.dbPassword=canal
:数据库密码
-e canal.instance.filter.regex=
:要监听的表名称表名称监听支持的语法:
mysql 数据解析关注的表,Perl正则表达式. 多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\) 常见例子: 1. 所有表:.* or .*\\..* 2. canal schema下所有表: canal\\..* 3. canal下的以canal打头的表:canal\\.canal.* 4. canal schema下的一张表:canal.test1 5. 多个规则组合使用然后以逗号隔开:canal\\..*,mysql.test1,mysql.test2
3)监听Canal。
Canal框架 概念: canal是用java开发的基于数据库增量日志解析,提供增量数据订阅&消费的中间件。目前,canal主要支持了MySQL的binlog解析,解析完成后才利用canal client 用来处理获得的相关数据。
Canal 是阿里巴巴开源的数据库变更数据抓取和同步框架,用于监听数据库的变更,并将这些变更事件传输到消息中间件或者其他存储介质中。
RedisHandler implements InitializingBean类:
java@Component public class RedisHandler implements InitializingBean { @Autowired private StringRedisTemplate redisTemplate; @Autowired private IItemService itemService; @Autowired private IItemStockService stockService; @Autowired 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) { //2.1 item序列化为json String json = MAPPER.writeValueAsString(stock); //2.2 存入redis redisTemplate.opsForValue().set("item:stock:id:"+stock.getId(),json); } } public void saveItem(Item item) { try { //1 item序列化为json String json = MAPPER.writeValueAsString(item); //2 存入redis redisTemplate.opsForValue().set("item:id:"+item.getId(),json); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } public void deleteItemById(Long id){ redisTemplate.delete("item:id:"+id); } }
ItemHandler implements EntryHandler<Item>类:
java@CanalTable("tb_item") @Component public class ItemHandler implements EntryHandler<Item> { @Autowired private RedisHandler redisHandler; @Autowired private Cache<Long, Item> itemCache; @Override public void insert(Item item) { //写数据到jvm进程缓存 itemCache.put(item.getId(),item); //写数据到redis redisHandler.saveItem(item); } @Override public void update(Item before, Item after) { //修改数据到jvm进程缓存 itemCache.put(after.getId(),after); //修改数据到redis redisHandler.saveItem(after); } @Override public void delete(Item item) { //删除数据到jvm进程缓存 itemCache.invalidate(item.getId()); //删除数据到redis redisHandler.deleteItemById(item.getId()); } }