查建设工程规划许可证在哪个网站,选一个网站做seo,装修设计费,seo整站优化的思路及步骤文章目录 一.多级缓存的引入二.JVM进程缓存三.Lua语法入门四.多级缓存1.OpenResty2.查询Tomcat3.Redis缓存预热4.查询Redis缓存5.Nginx本地缓存6.缓存同步 一.多级缓存的引入
传统缓存的问题
传统的缓存策略一般是请求到达Tomcat后#xff0c;先查询Redis#xff0c;如果未… 文章目录 一.多级缓存的引入二.JVM进程缓存三.Lua语法入门四.多级缓存1.OpenResty2.查询Tomcat3.Redis缓存预热4.查询Redis缓存5.Nginx本地缓存6.缓存同步 一.多级缓存的引入
传统缓存的问题
传统的缓存策略一般是请求到达Tomcat后先查询Redis如果未命中则查询数据库存在下面的问题 请求要经过Tomcat处理Tomcat的性能成为整个系统的瓶颈 Redis缓存失效时会对数据库产生冲击 多级缓存方案
多级缓存就是充分利用请求处理的每个环节分别添加缓存减轻Tomcat压力提升服务性能 用作缓存的Nginx是业务Nginx需要部署为集群再有专门的Nginx用来做反向代理 二.JVM进程缓存
本地进程缓存
缓存在日常开发中启动至关重要的作用由于是存储在内存中数据的读取速度是非常快的能大量减少对数据库的访问减少数据库的压力。我们把缓存分为两类 分布式缓存例如Redis(已经学习过了) 优点存储容量更大、可靠性更好、可以在集群间共享 缺点访问缓存有网络开销 场景缓存数据量较大、可靠性要求较高、需要在集群间共享 进程本地缓存例如HashMap、GuavaCache 优点读取本地内存没有网络开销速度更快 缺点存储容量有限、可靠性较低、无法共享 场景性能要求较高缓存数据量较小 Caffeine
Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:https://github.com/ben-manes/caffeine
Caffeine的读写速度: Caffeine示例
可以通过item-service项目中的单元测试来学习Caffeine的使用
Test
void testBasicOps() {// 创建缓存对象CacheString, String cache Caffeine.newBuilder().build();// 存数据cache.put(gf, 迪丽热巴);// 取数据不存在则返回nullString gf cache.getIfPresent(gf);System.out.println(gf gf); // 取数据不存在则去数据库查询String defaultGF cache.get(defaultGF, key - {// 这里可以去数据库根据 key查询value return 柳岩;});System.out.println(defaultGF defaultGF);
}Caffeine提供了四种缓存添加策略手动加载自动加载手动异步加载和自动异步加载。(可以在官网查看到)
推荐使用 cache.get(key, k - value) 操作来在缓存中不存在该key对应的缓存元素的时候进行计算生成(可以理解为直接查询mysql数据库等操作,当然,查询操作要写在get方法的第二个匿名内部类中)并直接写入至缓存内而当该key对应的缓存元素存在的时候将会直接返回存在的缓存值。一次 cache.put(key, value) 操作将会直接写入或者更新缓存里的缓存元素在缓存中已经存在的该key对应缓存值都会直接被覆盖。值得注意的是当缓存的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败cache.get 也许会返回 null 。
当然也可以使用Cache.asMap()所暴露出来的ConcurrentMap的方法对缓存进行操作。 Caffeine提供了三种缓存驱逐策略
1.基于容量设置缓存的数量上限
// 创建缓存对象
CacheString, String cache Caffeine.newBuilder().maximumSize(1) // 设置缓存大小上限为 1 .build();2.基于时间设置缓存的有效时间
// 创建缓存对象
CacheString, String cache Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(10)) // 设置缓存有效期为 10 秒从最后一次写入开始计时 .build();3.基于引用设置缓存为软引用或弱引用利用GC来回收缓存数据。性能较差不建议使用。
在默认情况下当一个缓存元素过期的时候Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后或者在空闲时间完成对失效数据的驱逐。 案例:实现进程缓存
针对实现商品的查询的本地进程缓存
利用Caffeine实现下列需求 给根据id查询商品的业务添加缓存缓存未命中时查询数据库 给根据id查询商品库存的业务添加缓存缓存未命中时查询数据库 缓存初始大小为100 缓存上限为10000
实现步骤:
1.初始化Bean:
Configuration
public class CaffeineConfig {Beanpublic CacheLong, Item itemCache(){return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();}Beanpublic CacheLong, ItemStock stockCache(){return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();}
}2.修改业务(查询)代码 GetMapping(/{id})public Item findById(PathVariable(id) Long id) {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(key));}三.Lua语法入门
初识Lua
Lua 是一种轻量小巧的脚本语言用标准C语言编写并以源代码形式开放 其设计目的是为了嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。官网https://www.lua.org/ 变量和循环
数据类型 其中,table数据类型可以是数组或者map集合,这取决于是如何声明的
可以利用type函数测试给定变量或者值的类型 变量
Lua声明变量的时候并不需要指定数据类型(很类似js) 访问table(下标从开始,不是从0开始) 循环
数组、table都可以利用for循环来遍历 遍历数组 遍历table 条件控制、函数
函数
定义函数的语法 例如定义一个函数用来打印数组 条件控制
类似Java的条件控制例如if、else语法 与java不同布尔表达式中的逻辑运算是基于英文单词 四.多级缓存
1.OpenResty
初识OpenResty
OpenResty® 是一个基于 Nginx的高性能 Web 平台用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。具备下列特点 具备Nginx的完整功能 基于Lua语言进行扩展集成了大量精良的 Lua 库、第三方模块 允许使用Lua自定义业务逻辑、自定义库
官方网站https://openresty.org/cn/
简单来说:OpenResty的目标是通过将Lua脚本语言嵌入Nginx中使开发人员能够使用Lua来扩展Nginx的功能。 OpenResty快速入门
案例:实现商品详情页数据查询
商品详情页面目前展示的是假数据在浏览器的控制台可以看到查询商品信息的请求 而这个请求最终被反向代理到虚拟机的OpenResty集群 需求在OpenResty中接收这个请求并返回一段商品的假数据。
实现步骤:
1.修改nginx.conf文件:
在nginx.conf的http下面添加对OpenResty的Lua模块的加载 # 加载lua 模块 lua_package_path /usr/local/openresty/lualib/?.lua;;; # 加载c模块 lua_package_cpath /usr/local/openresty/lualib/?.so;;;这两行代码主要是引入了Lua模块和Lua C模块,方便后面引入API来处理HTTP请求等
2.在nginx.conf的server下面添加对/api/item这个路径的监听
location /api/item {# 响应类型这里返回jsondefault_type application/json;# 响应数据由 lua/item.lua这个文件来决定content_by_lua_file lua/item.lua;}这段代码可以理解为SpringMVC框架中的表现层(Controller),监听一个/api/item路径,具体的响应数据为json,具体的业务逻辑代码在item.lua中编写(类似于业务层Service)
3.编写item.lua文件
-- 返回假数据这里的ngx.say()函数就是写数据到Response中
ngx.say({id:10001,name:SALSA AIR})这里先使用假数据,后面讲解动态返回数据 请求参数处理
OpenResty提供了各种API用来获取不同类型的请求参数 案例:获取请求路径中的商品id信息拼接到json结果中返回
在查询商品信息的请求中通过路径占位符的方式传递了商品id到后台 需求在OpenResty中接收这个请求并获取路径中的id信息拼接到结果的json字符串中返回
实现步骤:
1.在OpenResty的Nginx下的配置文件编写请求变量拦截 location ~ /api/item/(\d) {# 相应类型,这里返回jsondefault_type application/json;# 响应数据由 lua/item.lua这个文件来决定content_by_lua_file lua/item.lua;}2.在item.lua文件获取id变量并进行拼接返回
local id ngx.var[1]ngx.say({id:..id..,name:SALSA AIR,title:RIMOWA 26寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4,price:18888,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.00000:00,updateTime:2019-04-30T16:00:00.00000:00,stock:2999,sold:31290})2.查询Tomcat
实现多级缓存的步骤是,当OpenResty集群没有缓存时,查询Redis缓存,当Redis缓存未命中访问Tomcat进程缓存,进程缓存未命中时通知Tomcat服务器查询数据库返回数据并存入数据到OpenResty作为缓存,这里先忽略访问缓存,实现OpenResty请求访问Tomcat服务器查询数据
需要说明的是:当查询未命中多级缓存中任意一级时,当前所在的级就必须查询其他级(前一级只会查询后面的一级,因为前面的级包含的缓存数据都没有命中)来补充所需的缓存数据 案例:获取请求路径中的商品id信息根据id向Tomcat查询商品信息
这里要修改item.lua满足下面的需求(步骤) 1.获取请求参数中的id 2.根据id向Tomcat服务发送请求查询商品信息 3.根据id向Tomcat服务发送请求查询库存信息 4.组装商品信息、库存信息序列化为JSON格式并返回 前置知识1:nginx内部发送Http请求
nginx提供了内部API用以发送http请求
local resp ngx.location.capture(/path,{method ngx.HTTP_GET, -- 请求方式args {a1,b2}, -- get方式传参数body c3d4 -- post方式传参数
})返回的响应内容包括 resp.status响应状态码 resp.header响应头是一个table resp.body响应体就是响应数据
注意这里的path是路径并不包含IP和端口。这个请求会被nginx内部的server监听并处理。
但是我们希望这个请求发送到Tomcat服务器所以还需要编写一个server来对这个路径做反向代理
location /path {# 这里是windows电脑的ip和Java服务端口需要确保windows防火墙处于关闭状态proxy_pass http://192.168.150.1:8081; }案例实现在配置文件编写监听/item路径即可
前置知识2:封装http查询函数
由于和Tomcat通信在一个新项目是十分频繁的过程,这里可以封装http查询的函数,放到OpenResty函数库中方便后期使用。
步骤:
1.在/usr/local/openresty/lualib目录下创建common.lua文件
vi /usr/local/openresty/lualib/common.lua
2.在common.lua中封装http查询的函数
-- 封装函数发送http请求并解析响应
local function read_http(path, params)local resp ngx.location.capture(path,{method ngx.HTTP_GET,args params,})if not resp then-- 记录错误信息返回404ngx.log(ngx.ERR, http not found, path: , path , , args: , args)ngx.exit(404)endreturn resp.body
end
-- 将方法导出
local _M { read_http read_http
}
return _M这里指定在/usr/local/openresty/lualib目录是因为在OpenResty下的Nginx配置文件中添加了对模块的加载,接下来就可以直接使用这个库 # 加载lua 模块 lua_package_path /usr/local/openresty/lualib/?.lua;;; # 加载c模块 lua_package_cpath /usr/local/openresty/lualib/?.so;;;实现案例:
1.修改item.lua文件
-- 引入自定义工具模块
local common require(common)
local read_http common.read_http-- 获取路径参数
local id ngx.var[1]-- 根据id查询商品
local itemJSON read_http(/item/.. id, nil)
-- 根据id查询商品库存
local itemStockJSON read_http(/item/stock/.. id, nil)查询到的是商品、库存的json格式数据我们需要将两部分数据组装需要用到JSON处理函数库。
2.拼接JSON数据并进行返回
JSON结果处理
OpenResty提供了一个cjson模块用来处理JSON的序列化和反序列化。
官方地址https://github.com/openresty/lua-cjson/
步骤主要分为以下两个:
1.引入cjson模块
local cjson require cjson2.(1)序列化
local obj {name jack,age 21
}
local json cjson.encode(obj)2.(2)反序列化
local json {name: jack, age: 21}
-- 反序列化
local obj cjson.decode(json);
print(obj.name)简单来说:序列化就是encode,反序列化就是decode
了解JSON序列化和反序列化就能进行结果拼接返回了:
-- 反序列化JSON数据/JSON转换成lua的table
local item cjson.decode(itemJSON)
local stock cjson.decode(itemStockJSON)-- 组合数据
item.stock stock.stock
item.sold stock.sold-- 把item序列化为json且返回数据
ngx.say(cjson.encode(item))访问id为10002返回的数据(记得关闭防火墙): 总结案例:
当一个url为http://localhost/item.html?id10002访问时,执行的大致操作:
1.前端解析url拼接为http://localhost/api/item/10002
2.windows上的nginx服务器监控到/api的请求后,反向代理到OpenResty集群,并做了负载均衡到具体的ip端口
3.虚拟机上的OpenResty服务器监测到/api/item/(\d)的请求,执行item.lua内的业务逻辑
4.item.lua内使用函数接收到请求和路径变量id,请求路径/item/id或/item/stock/id并被自身监听到,反向代理url到windows上的Tomcat服务器并被controller监测到,执行查询业务返回JSON数据到OpenResty服务器,服务器使用resp变量接收到返回数据,并组合数据返回到页面
需要注意的是:导入模块使用的函数为require;操作序列化反序列化JSON数据使用的模块为cjson Tomcat集群的负载均衡
在实际业务中,OpenResty服务器请求Tomcat服务器,Tomcat服务器肯定是一个集群,这就涉及到了请求的分散(负载均衡)来减轻单个Tomcat服务器的压力,在OpenResty服务器请求Tomcat服务器时,Tomcat服务器不仅会查询数据返回给OpenResty服务器,还会保存查询数据到进程缓存中
但是,如果OpenResty服务器使用的是轮询的方式访问Tomcat集群,这就导致了如果是同一个id请求服务器,每一台Tomcat服务器都必须要查询数据库,进程缓存就显得没有用了(直到每一个Tomcat服务器都查询数据库才都有这个id的进程缓存,进程缓存才开始生效,这不是我们希望看到的)所以OpenResty服务器的负载均衡策略必须改变
这里采用的hash负载均衡策略(hash $request_uri),这种策略会对id进行hash运算,得到一个具体值会访问一台具体的Tomcat服务器,如果是同一个id,就会hash到同一个结果访问到同一台Tomcat服务器,从而导致请求会利用到线程缓存 3.Redis缓存预热
在多级缓存中,当OpenResty服务器中查询不到缓存时,应该查询下一级缓存即Redis缓存
冷启动与缓存预热
冷启动服务刚刚启动时Redis中并没有缓存如果所有商品数据都在第一次查询时添加缓存可能会给数据库带来较大压力。
缓存预热在实际开发中我们可以利用大数据统计用户访问的热点数据在项目启动时将这些热点数据提前查询并保存到Redis中。
案例实现步骤:
1.利用Docker安装Redis
docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes2.在item-service服务中引入Redis依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency3.配置Redis地址(直接配置即可)
4.编写初始化类
Component
public class RedisHandler implements InitializingBean {Autowiredprivate RedisTemplateString, String redisTemplate;Autowiredprivate IItemService itemService;Autowiredprivate IItemStockService itemStockService;private static final ObjectMapper MAPPERnew ObjectMapper();/*** 这个方法会在这个类注册成bean后执行** throws Exception*/Overridepublic void afterPropertiesSet() throws Exception {//查询数据库数据ListItem itemList itemService.list();ListItemStock itemStockList itemStockService.list();//保存到redis中for (Item item : itemList) {//转换数据为JSON对象String jsonItem MAPPER.writeValueAsString(item);redisTemplate.opsForValue().set(item:id: item.getId(), jsonItem);}for (ItemStock itemStock : itemStockList) {//转换数据为JSON对象String jsonItemStock MAPPER.writeValueAsString(itemStock);redisTemplate.opsForValue().set(item:stock:id: itemStock.getId(), jsonItemStock);}}
}需要注意的有三点:
1.缓存预热使用到接口类为InitializingBean
2.实现接口类中的方法后,这个方法会在整个RedisHandler注册成为bean实例后执行
3.可以用Spring原始的序列化工具ObjectMapper来将对象转换为JSON字符串(调用方法writeValueAsString) 4.查询Redis缓存
OpenResty的Redis模块
OpenResty提供了操作Redis的模块我们只要引入该模块就能直接使用
引入Redis模块并初始化Redis对象
-- 引入redis模块
local redis require(resty.redis)
-- 初始化redis对象
local red redis:new()
-- 设置redis超时时间
red:set_timeouts(1000,1000,1000)封装函数用来释放Redis连接其实是放入连接池
-- 关闭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 thenngx.log(ngx.ERR, 放入redis连接池失败: , err)end
end封装函数从Redis读数据并返回
-- 查询redis的方法 ip和port是redis地址key是查询的key
local function read_redis(ip, port, key)-- 获取一个连接local ok, err red:connect(ip, port)if not ok thenngx.log(ngx.ERR, 连接redis失败 : , err)return nilend-- 查询redislocal resp, err red:get(key)-- 查询失败处理if not resp thenngx.log(ngx.ERR, 查询Redis失败: , err, , key , key)end--得到的数据为空处理if resp ngx.null thenresp nilngx.log(ngx.ERR, 查询Redis数据为空, key , key)endclose_redis(red)return resp
end别忘了对外暴露调用函数:
local _M { read_http read_http,read_redis read_redis
} 案例:查询商品时优先Redis缓存查询
需求 修改item.lua封装一个函数read_data实现先查询Redis如果未命中再查询tomcat 修改item.lua查询商品和库存时都调用read_data这个函数
实现代码:
-- 引入cjson的模块用来处理JSON的序列化和反序列化
local cjson require(cjson)
-- 引入自定义工具模块
local common require(common)-- 获取模块中的函数
local read_http common.read_http
local read_redis common.read_redis-- 定义查询函数
local function read_data(key,path,params)-- 查询redislocal resp read_redis(127.0.0.1,6379,key)-- 判断redis是否命中if not resp then-- redis查询失败,查询httpresp read_http(path,params)endreturn resp
end-- 获取路径参数
local id ngx.var[1]-- 根据id查询商品
local itemJSON read_data(item:id:..id, /item/..id, nil)-- 根据id查询商品库存
local itemStockJSON read_data(item:stock:id:..id, /item/stock/..id, nil)-- 反序列化JSON数据/JSON转换成lua的table
local item cjson.decode(itemJSON)
local stock cjson.decode(itemStockJSON)-- 组合数据
item.stock stock.stock
item.sold stock.sold-- 把item序列化为json且返回数据
ngx.say(cjson.encode(item))需要解释的是:对于其中的代码:
-- 查询redislocal resp read_redis(127.0.0.1,6379,key)填写127.0.0.1是因为OpenResty服务器和Redis服务器都存在一台虚拟机上,使用回环地址即可访问 5.Nginx本地缓存
OpenResty为Nginx提供了shard dict的功能可以在一台nginx服务器的多个worker之间共享数据实现缓存功能。
开启共享字典在nginx.conf的http下添加配置
# 共享字典也就是本地缓存名称叫做item_cache大小150m
lua_shared_dict item_cache 150m; 操作共享字典
-- 获取本地缓存对象
local item_cache ngx.shared.item_cache
-- 存储, 指定key、value、过期时间单位s默认为0代表永不过期
item_cache:set(key, value, 1000)
-- 读取
local val item_cache:get(key)案例:在查询商品时优先查询OpenResty的本地缓存
需求 修改item.lua中的read_data函数优先查询本地缓存未命中时再查询Redis、Tomcat 查询Redis或Tomcat成功后将数据写入本地缓存并设置有效期 商品基本信息有效期30分钟 库存信息有效期1分钟
实现代码:
1.先在nginx.conf配置文件添加共享词典配置
# 共享字典也就是本地缓存名称叫做item_cache大小150mlua_shared_dict item_cache 150m;2.编写业务逻辑(.lua文件中)
-- 引入cjson的模块用来处理JSON的序列化和反序列化
local cjson require(cjson)
-- 引入自定义工具模块
local common require(common)-- 获取模块中的函数
local read_http common.read_http
local read_redis common.read_redis-- 导入共享词典,本地缓存
local item_cache ngx.shared.item_cache-- 定义查询函数
local function read_data(key,path,params,validTime)-- 先查询本地缓存local val item_cache:get(key)-- 判断本地缓存是否命中if not val thenngx.log(ngx.ERR, 本地缓存查询失败,尝试查询Redis, key:, key)-- 本地缓存查询失败,查询redisval read_redis(127.0.0.1,6379,key)-- 判断redis是否命中if not val thenngx.log(ngx.ERR, redis查询失败,尝试查询http, key:, key)-- redis查询失败,查询httpval read_http(path,params)endenditem_cache:set(key,val,validTime)return val
end-- 获取路径参数
local id ngx.var[1]-- 根据id查询商品
local itemJSON read_data(item:id:..id, /item/..id, nil, 1800)-- 根据id查询商品库存
local itemStockJSON read_data(item:stock:id:..id, /item/stock/..id, nil, 60)-- 反序列化JSON数据/JSON转换成lua的table
local item cjson.decode(itemJSON)
local stock cjson.decode(itemStockJSON)-- 组合数据
item.stock stock.stock
item.sold stock.sold-- 把item序列化为json且返回数据
ngx.say(cjson.encode(item))6.缓存同步
缓存同步策略
缓存数据同步的常见方式有三种 设置有效期给缓存设置有效期到期后自动删除。再次查询时更新 优势简单、方便 缺点时效性差缓存过期之前可能不一致 场景更新频率较低时效性要求低的业务 同步双写在修改数据库的同时直接修改缓存 优势时效性强缓存与数据库强一致 缺点有代码侵入耦合度高 场景对一致性、时效性要求较高的缓存数据 异步通知修改数据库时发送事件通知相关服务监听到通知后修改缓存数据 优势低耦合可以同时通知多个缓存服务 缺点时效性一般可能存在中间不一致状态 场景时效性要求一般有多个服务需要同步
对于异步通知,有以下两种:
1.基于MQ的异步通知 2.基于Canal的异步通知 初识Canal
Canal [kə’næl]译意为水道/管道/沟渠canal是阿里巴巴旗下的一款开源项目基于Java开发。基于数据库增量日志解析提供增量数据订阅消费。GitHub的地址https://github.com/alibaba/canal,Canal是基于mysql的主从同步来实现的MySQL主从同步的原理如下 MySQL master 将数据变更写入二进制日志( binary log其中记录的数据叫做binary log events MySQL slave 将 master 的 binary log events拷贝到它的中继日志(relay log) MySQL slave 重放 relay log 中事件将数据变更反映它自己的数据 Canal就是把自己伪装成MySQL的一个slave节点从而监听master的binary log变化。再把得到的变化信息通知给Canal的客户端进而完成对其它数据库的同步。 对于安装Canal需要注意的是:1.首先开启mysql的主从 2.安装canal,并使cancl和mysql处于同一网络下 监听Canal
Canal提供了各种语言的客户端当Canal监听到binlog变化时会通知Canal的客户端。 Canal提供了各种语言的客户端当Canal监听到binlog变化时会通知Canal的客户端。不过这里我们会使用GitHub上的第三方开源的canal-starter。(操作Canal会更加方便)地址链接: https://github.com/NormanGyllenhaal/canal-client
实现步骤:
引入依赖 dependencygroupIdtop.javatool/groupIdartifactIdcanal-spring-boot-starter/artifactIdversion1.2.1-RELEASE/version/dependency编写配置
canal:destination: heima # canal实例名称要跟canal-server运行时设置的destination一致server: 192.168.109.130:11111 # canal地址编写监听器监听Canal消息
这里就使用案例来演示了:
package com.heima.item.canal;import com.github.benmanes.caffeine.cache.Cache;
import com.heima.item.config.RedisHandler;
import com.heima.item.pojo.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.handler.EntryHandler;CanalTable(tb_item)
Component
public class ItemHandler implements EntryHandlerItem {Autowiredprivate RedisHandler redisHandler;Autowiredprivate CacheLong , Item itemCache;Overridepublic void insert(Item item) {itemCache.put(item.getId(), item);redisHandler.saveItem(item);}Overridepublic void update(Item before, Item after) {itemCache.put(after.getId(), after);redisHandler.saveItem(after);}Overridepublic void delete(Item item) {itemCache.invalidate(item.getId());redisHandler.deleteItemById(item.getId());}
}
具体来说: Canal推送给canal-client的是被修改的这一行数据row而我们引入的canal-client则会帮我们把行数据封装到Item实体类中。这个过程中需要知道数据库与实体的映射关系要用到JPA的几个注解
package com.heima.item.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;import java.util.Date;Data
TableName(tb_item)
public class Item {TableId(type IdType.AUTO)Idprivate Long id;//商品idprivate String name;//商品名称private String title;//商品标题private Long price;//价格分private String image;//商品图片private String category;//分类名称private String brand;//品牌名称private String spec;//规格private Integer status;//商品状态 1-正常2-下架private Date createTime;//创建时间private Date updateTime;//更新时间TableField(exist false)Transientprivate Integer stock;TableField(exist false)Transientprivate Integer sold;
}
具体来说: 到此为止,可以实现mysql数据库修改,同步对redis的数据进行变更,而对于OpenResty内的数据,推荐缓存不是更新速度很频繁的数据并且使用了设置过期时间进行数据更新