您的位置:首页 > 健康 > 美食 > 辽宁省造价信息网_杭州seo全网营销_广州今天刚刚发生的重大新闻_现在推广一般都用什么软件

辽宁省造价信息网_杭州seo全网营销_广州今天刚刚发生的重大新闻_现在推广一般都用什么软件

2024/12/31 4:53:16 来源:https://blog.csdn.net/weixin_64339548/article/details/142856114  浏览:    关键词:辽宁省造价信息网_杭州seo全网营销_广州今天刚刚发生的重大新闻_现在推广一般都用什么软件
辽宁省造价信息网_杭州seo全网营销_广州今天刚刚发生的重大新闻_现在推广一般都用什么软件

目录

JVM缓存(本地进程缓存)Caffeine技术栈基础介绍:

 OpenResty技术栈基础介绍

Canal技术栈介绍 

一、通过Caffeine实现进程缓存

1.1首先需要为你需要的数据构建Cache缓存对象,为了方便使用,可以将其声明为一个bean

1.2通过声明的Bean对象实现进程缓存

二、通过OpenResty扩展Nginx功能实现请求参数处理、查询tomcat

2.1安装OpenResty配置nginx的环境变量

2.1.1首先要安装OpenResty的依赖开发库,执行命令

2.1.2在 linux系统中添加 openresty 仓库,这样就可以便于未来安装或更新软件包,运行下面的命令添加仓库

2.1.3安装OpenResty

2.1.4安装opm工具

2.1.5查看安装目录

2.1.6配置nginx的环境变量

2.2启动和运行OpenResty

2.3配置windows端的nginx进行反向代理,路由请求到linux中的OpenResty中

2.3.1编辑windows的nginx.conf文件实现反向代理,监听前端发送的请求端口为80,并将请求路由到linux中,(为了方便,我关闭了windows的防火墙)

 2.4配置OpenResty中的nginx中的nginx.conf文件(在后面有总体配置文件),以及编写lua代码实现nginx的本地缓存查询和redis操作

2.4.1实现OpenResty对路由的路径为/api/item的监听,并且定义返回的数据格式以及定义响应数据文

 2.4.2在nginx.conf的http下面,添加对OpenResty的Lua模块的加载,这些数据库是为了之后操作redis和本地缓存所需要的依赖库

2.4.3最后一步配置反向代理,路由到tomcat服务器,这是因为,nginx本地缓存和redis缓存中都没有相关数据所以需要通过tomcat服务器查询自己的本地缓存或者数据库并数据返回过来,而要实现这一过程,就需要我们在指定的响应数据文件编写发送请求的代码,再由自己进行监听并路由到tomcat

2.4.5OpenResty的Nginx中的nginx.conf文件内容

2.5编写content_by_lua_file lua/item.lua中的item.lua实现对本地缓存和redis的操作

2.5.1创建对应文件:这个文件就是我们编写处理监听到访问请求后的处理逻辑(相当与service)

2.5.2对tomcat集群因为不同的tomcat服务器的本地缓存不同的解决方案

三.实现redis的热启动

四.缓存同步

4.1安装和配置Canal

4.1.1开启MySql主从

 4.1.2设置用户权限

4.1.3修改后重启容器

4.1.4检查配置是否成功

4.1.5docker拉取canal/canal-server

 4.1.6docker启动canal

4.2 配置canal集成到服务中

4.2.1引入依赖

4.2.2编写配置

4.2.3编写监听方法,监听Canal信息

4.2.4如果canal的logs日志发生以下错误(数据库的修改无法同步到redis和本地缓存)


前言:

多级缓存方案:

  • 第一级缓存:通过浏览器客户端缓存,将页面静态资源缓存到浏览器中
  • 第二级缓存:nginx的本地缓存,将部分动态数据存放到nginx本地
  • 第三级缓存:通过nginx编写lua代码实现查询redis中的缓存数据
  • 第四级缓存:查询tomcat的进程缓存,也称为JVM缓存,本篇使用Caffeine进行实现

多级缓存技术就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能,上面图例就是本篇文章实现多级缓存的整体结构,下面时缓存技术介绍,详情配置和使用可通过目录跳转

JVM缓存(本地进程缓存)Caffeine技术栈基础介绍:

缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类

  • 分布式缓存,例如Redis
  • 优点:存储容量更大、可靠性更好、可以在集群间共享
  • 缺点:访问缓存有网络开销
  • 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
  • 进程本地缓存,例如HashMapGuavaCache
  • 优点:读取本地内存,没有网络开销,速度更快
  • 缺点:存储容量有限、可靠性较低、无法共享

场景:性能要求较高,缓存数据量较小

这里小编将通过Caffeine实现本地缓存技术,因为Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine

Caffeine提供了三种缓存驱逐策略:

  • 基于容量:设置缓存的数量上限
  • // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder().maximumSize(1) // 设置缓存大小上限为 1.build();
  • 基于时间:设置缓存的有效时间
  • // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(10)) // 设置缓存有效期为 10 秒,从最后一次写入开始计时.build();

基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。

注意:

在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。

 OpenResty技术栈基础介绍

OpenResty®是一个基于 Nginx的高性能 Web 平台,底层是基于Nginx的,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

具备下列特点:

  • 具备Nginx的完整功能
  • 基于Lua语言进行扩展,集成了大量精良的 Lua 库、第三方模块
  • 允许使用Lua自定义业务逻辑、自定义库

OpenResty的基础实现:

  1. 小编将通过在windows中本地部署Nginx进行反向代理将前端的页面访问请求发送到Linux系统中并指定对应端口
  2. 再在linux系统中部署OpenResty服务进行监听指定端口,拦截发过来的访问请求,如果请求是查询请求,则返回OpenResty中Nginx的本地缓存
  3. 如果Nginx的本地缓存不存在对应数据,则编写lua代码和调用OpenResty提供的方法库对Redis进行数据查询操作,如果查询到数据,将数据保存到本地缓存中,并将数据返回
  4. 如果redis也没有对应缓存数据,则通过OpenResty的nginx中编写代码发起向tomcat服务端发起访问请求并由自己进行监听并发送
  5. 然后查询tomcat服务器的本地缓存或者数据库,并将查询到的数据保存到redis和nginx本地缓存中

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节点,从而监听masterbinary log变化。再把得到的变化信息通知给Canal的客户端,进而完成对其它数据库的同步。

一、通过Caffeine实现进程缓存

1.1首先需要为你需要的数据构建Cache缓存对象,为了方便使用,可以将其声明为一个bean

配置示例如下:这里我为我需要缓存的item和itemStock两个实例定义了对应的缓存对象,调用Caffeine的newbuilder方法,构造出缓存容器的大小和上限。

Cache<Long, Item>中的泛型数据类型(相当于Map中的键值对),Long指代的是我商品id,Item指代的是我的item对象,也就是说,我们可以调用Cache的方法通过指定泛型中的第一个参数查找出第二个参数的值,和Map存放和读取数据类似

package com.heima.item.config;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.heima.item.pojo.Item;
import com.heima.item.pojo.ItemStock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class CaffeineConfig {/*** 定义商品的本地缓存,初始容量为100,最大容量为10000* @return*/@Beanpublic Cache<Long, Item> itemCache() {return Caffeine.newBuilder().maximumSize(10000).initialCapacity(100).build();}/*** 定义商品库存的本地缓存,初始容量为100,最大容量为10000* @return*/@Beanpublic Cache<Long, ItemStock> itemStockCache() {return Caffeine.newBuilder().maximumSize(10000).build();}
}

1.2通过声明的Bean对象实现进程缓存

首先,注入声明的Cache的bean对象

    @Autowiredprivate Cache<Long, Item> itemCache;@Autowiredprivate Cache<Long, ItemStock> itemStockCache;

然后,通过bean对象实例实现进程缓存,这里调用了Cache自带的API接口"get",这个接口实现了,通过传递的key(如,我这里的id)先在本地缓存中查询对应的值,如果未查询到数据,就将执行自定义逻辑处理,也就是下面的lambda表达式,我定义的处理逻辑是调用service层定义的接口,查询本地数据库,并将查询到的数据保存到本地缓存中,然后再返回给前端,之后下次再执行此命令时,将直接读取本地缓存里面的数据

    @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 itemStockCache.get(id, key -> stockService.getById(id));}

这样,我们就实现基本的本地缓存

二、通过OpenResty扩展Nginx功能实现请求参数处理、查询tomcat

前言:

执行yum安装发生错误可以查看我的另一篇文章:CentOS7--IP地址初始化失败、配置静态ip地址后dns解析错误、yum无法使用,yum镜像问题---超详细解决方法

2.1安装OpenResty配置nginx的环境变量

2.1.1首先要安装OpenResty的依赖开发库,执行命令

yum install -y pcre-devel openssl-devel gcc --skip-broken

2.1.2在 linux系统中添加 openresty 仓库,这样就可以便于未来安装或更新软件包,运行下面的命令添加仓库

yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

如果提示说命令不存在,则运行:

yum install -y yum-utils 

然后再重复上面的命令

2.1.3安装OpenResty

yum install -y openresty

2.1.4安装opm工具

opm是OpenResty的一个管理工具,可以帮助我们安装一个第三方的Lua模块。 如果你想安装命令行工具 opm,那么可以像下面这样安装 openresty-opm 包:

yum install -y openresty-opm

2.1.5查看安装目录

默认情况下,OpenResty安装的目录是:/usr/local/openresty

2.1.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.2启动和运行OpenResty

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`文件,内容如下:

#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;
        }
    }
}

这里我监听的服务窗口时本地的8081,读者请自行根据需要改变自己的IP地址和端口号

启动nginx以查看nginx界面进行检查配置是否成功(通过windos访问linux的ip和端口需要开发linux端口,或者关闭防火墙)

2.3配置windows端的nginx进行反向代理,路由请求到linux中的OpenResty中

我们已经在linux中安装好了OpenResty,接下来就是将前端的请求处理转发到linux的OpenResty中,进行缓存数据查询

2.3.1编辑windows的nginx.conf文件实现反向代理,监听前端发送的请求端口为80,并将请求路由到linux中,(为了方便,我关闭了windows的防火墙)

 2.4配置OpenResty中的nginx中的nginx.conf文件(在后面有总体配置文件),以及编写lua代码实现nginx的本地缓存查询和redis操作

2.4.1实现OpenResty对路由的路径为/api/item的监听,并且定义返回的数据格式以及定义响应数据文

    server {

        listen       8081;

        server_name  localhost;

        location ~ /api/item/(\d+) {

        # 响应类型,这里返回json

        default_type application/json;

        # 响应数据由 lua/item.lua这个文件来决定

        content_by_lua_file lua/item.lua;

        }

解析:

  • location ~ /api/item/(\d+) : 表示监听路径为/api/item/(至少一个数字)的路径如(192.168.8.1:8080/api/item/10001)并且获取10001这个路径参数,为了以后进行数据查询
  • default_type application/json: 定义返回的数据格式,将查询到的缓存数据,根据自己前端需要数据的需求进行返回
  • content_by_lua_file lua/item.lua:定义响应数据内容,和实现对本地缓存数据和redis的查询,相当于我们SpringMVC的service层,完成具体业务,只不过一个的开发语言是java,而这里使用的lua

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

参数格式

参数示例

参数解析代码示例

路径占位符

/item/1001

# 1.正则表达式匹配: 

location ~ /item/(\d+) {

 content_by_lua_file lua/item.lua;

}

-- 2. 匹配到的参数会存入ngx.var数组中,

-- 可以用角标获取

local id = ngx.var[1]

请求头

id1001

-- 获取请求头,返回值是table类型

local headers = ngx.req.get_headers()

Get请求参数

?id=1001

-- 获取GET请求参数,返回值是table类型

local getParams = ngx.req.get_uri_args()

Post表单参数

id=1001

-- 读取请求体

ngx.req.read_body()

-- 获取POST表单参数,返回值是table类型

local postParams = ngx.req.get_post_args()

JSON参数

{"id": 1001}

-- 读取请求体

ngx.req.read_body()

-- 获取body中的json参数,返回值是string类型

local jsonBody = ngx.req.get_body_data()

 2.4.2在nginx.confhttp下面,添加对OpenRestyLua模块的加载,这些数据库是为了之后操作redis和本地缓存所需要的依赖库

代码:

    #lua 模块

    lua_package_path "/usr/local/openresty/lualib/?.lua;;";

    #c模块    

    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";

    # 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m

    lua_shared_dict item_cache 150m;

位置:

2.4.3最后一步配置反向代理,路由到tomcat服务器,这是因为,nginx本地缓存和redis缓存中都没有相关数据所以需要通过tomcat服务器查询自己的本地缓存或者数据库并数据返回过来,而要实现这一过程,就需要我们在指定的响应数据文件编写发送请求的代码,再由自己进行监听并路由到tomcat

    # 声明tomcat服务集群

    upstream tomcat-cluster {

        hash $request_uri;

        server 192.168.8.1:8081;

        server 192.168.8.1:8082;

    }

       #路径为

location /item {

            proxy_pass http://tomcat-cluster;

        }

2.4.5OpenResty的Nginx中的nginx.conf文件内容

注意:这里重新加载配置文件会报错,你需要将对应的文件(如item.lua)创建完成后再进行加载

#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;;";

    # 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m

    lua_shared_dict item_cache 150m;

    # 声明tomcat服务集群

    upstream tomcat-cluster {

        hash $request_uri;

        server 192.168.8.1:8081;

        server 192.168.8.1:8082;

    }

    server {

        listen       8081;

        server_name  localhost;

        location /item {

            proxy_pass http://tomcat-cluster;

        }

        location ~ /api/item/(\d+) {

        # 响应类型,这里返回json

        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;

        }

    }

}

2.5编写content_by_lua_file lua/item.lua中的item.lua实现对本地缓存和redis的操作

2.5.1创建对应文件:这个文件就是我们编写处理监听到访问请求后的处理逻辑(相当与service)

具体处理代码:这里我封装了一些方法(read_http,read_redis)到一个自定义的common 包中(在后面),同时我的redis部署linux本地的docker容器,开放的端口为6380

--导入函数库,获取请求发送方法
local common = require('common')
local read_http = common.read_http
--获取redis操作函数
local read_redis = common.read_redis--导入共享词典,本地缓存local item_cache = ngx.shared.item_cache--封装查询函数
function read_data(key, expire, path, params)--查询本地缓存local resp = item_cache:get(key)if not resp then ngx.log(ngx.ERR, "本地缓存查询失败,尝试查询redis获取数据, key", (key or nil))--查询redisresp = read_redis("127.0.0.1",6380,key)--判断查询结果if not resp thenngx.log(ngx.ERR, "redis 查询失败,尝试查询服务端获取数据, key:", (key or nil))--redis 查询失败,尝试查询服务端获取数据resp = read_http(path, params)end--查询成功,把数据写入本地缓存中item_cache:set(key, resp, expire)endreturn resp
end--导入cjson函数库,获取反序列化与序列化方法
local cjson = require('cjson')-- 获取路径参数
local id = ngx.var[1]
-- 查询商品信息
local itemJSON = read_data("item:id:" .. id, 1800, "/item/" .. id, nil)
--查询库存商品信息
local itemStockJSON = read_data("item:stock:id:" .. id, 60, "/item/stock/" .. id, nil)
--将得到的商品JSON数据转化为table数据
local item = cjson.decode(itemJSON)
--将得到的库存商品JSON数据转化为table数据
local itemStock = cjson.decode(itemStockJSON)
--组合数据
item.stock = itemStock.stock
item.sold = itemStock.sold
--再将组合数据序列化为JSON格式,再返回结果
ngx.say(cjson.encode(item))

nginx提供了内部API用以发送http请求:

demo:

local resp = ngx.location.capture("/path",{

    method = ngx.HTTP_GET,   -- 请求方式

    args = {a=1,b=2},  -- get   方式传参数,和下面任选一个

    body = "c=3&d=4" -- post方式传参数

})

返回的响应内容包括:

  • resp.status:响应状态码
  • resp.header:响应头,是一个table
  • resp.body:响应体,就是响应数据

注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。

但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理

common包文件地址:注意,自定义的包需要放在/lualib中否则无法扫描到

 common文件的代码:

--导入redis
local redis = require('resty.redis')
--释放Redis链接API
-- 关闭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数据的API
-- 查询redis的方法 ip和port是redis地址,key是查询的key
local function read_redis(ip, port, key)--初始化redislocal red = redis:new()--设置超时时间,建立连接时间,发送请求时间,等待响应时间red:set_timeout(1000,1000,1000)-- 获取一个连接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
-- 封装函数,发送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,read_redis = read_redis
}  
return _M

2.5.2对tomcat集群因为不同的tomcat服务器的本地缓存不同的解决方案

通过配置nginx的hash负载均衡,实现发送的某些请求定向访问某一个tomcat服务器,这样解决问题了,比如我的/item/10001进行hash算法只会路由到tomcat8081

三.实现redis的热启动

冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力。

缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。

1.导入依赖:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

 2.配置application进行服务发现

3.代码实现:这里继承了InitializingBean的接口并实现了afterPropertiesSet()方法,这个方法会在服务启动一开始就执行方法内的代码,实现将数据库的数据读取到redis中

package com.heima.item.utils;import com.fasterxml.jackson.core.JsonProcessingException;
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.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.List;@Component
public class RedisHandler implements InitializingBean {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate ItemService itemService;@Autowiredprivate ItemStockService itemStockService;//JSON处理工具private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic void afterPropertiesSet() throws Exception {//获取产品列表List<Item> itemList = itemService.list();//放入缓存for (Item item : itemList) {//将商品对象转换为JSON对象String itemJson = MAPPER.writeValueAsString(item);//将JSON数据放入缓存redisTemplate.opsForValue().set("item:id:" + item.getId(), itemJson);}//获取产品库存列表List<ItemStock> itemStockList = itemStockService.list();//放入缓存for (ItemStock itemStock : itemStockList) {//将商品库存对象转换为JSON对象String itemStockJson = MAPPER.writeValueAsString(itemStock);//将JSON数据放入缓存redisTemplate.opsForValue().set("item:stock:id:" + itemStock.getId(), itemStockJson);}}/*** 商品信息新增修改redis的方法* @param item*/public void saveItem(Item item)  {try {//将商品对象转换为JSON对象String itemJson = MAPPER.writeValueAsString(item);//将JSON数据放入缓存redisTemplate.opsForValue().set("item:id:" + item.getId(), itemJson);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}/*** 通过商品id进行删除* @param id* @throws Exception*/public void deleteItem(Long id) {try {redisTemplate.delete("item:id:" + id);} catch (Exception e) {throw new RuntimeException(e);}}
}

四.缓存同步

4.1安装和配置Canal

4.1.1开启MySql主从

Canal是基于MySQL的主从同步功能,因此必须先开启MySQL的主从功能才可以。这里我采用的是docker的Mysql容器,docker具体操作可以查看我的另一篇文章:Docker应用详解篇——docker的安装、配置docker镜像加速、docker常见命令详解、数据卷挂载,使用DockerCompose部署微服务集群

 启动我的Mysql容器

docker run \
 -p 3307:3306 \
 --name mysql \
 -v $PWD/conf:/etc/mysql/conf.d \
 -v $PWD/logs:/logs \
 -v $PWD/data:/var/lib/mysql \
 -e MYSQL_ROOT_PASSWORD=1234 \
 --privileged \
 -d \
 mysql:5.7.25

打开mysql容器挂载的日志文件,我的在/res/mysql/conf目录:

修改文件,再末尾添加

#配置binlog的存放位置/var/lib/mysql/以及名称mysql-bin

log-bin=/var/lib/mysql/mysql-bin

#配置binlog监听的数据库我这里的是demo
binlog-do-db=demo

全部文件内容:

[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=demo

 4.1.2设置用户权限

接下来添加一个仅用于数据同步的账户,出于安全考虑,这里仅提供对demo这个库的操作权限。

create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal';
FLUSH PRIVILEGES;

4.1.3修改后重启容器

docker restart mysql

4.1.4检查配置是否成功

测试设置是否成功:在mysql控制台,或者Navicat中,输入命令:

show master status;

4.1.5docker拉取canal/canal-server

docker pull canal/canal-server

拉取失败可重新配置镜像源

vi /etc/docker/daemon.json

更改为以下内容: 

{

    "registry-mirrors": [

        "https://2a6bf1988cb6428c877f723ec7530dbc.mirror.swr.myhuaweicloud.com",

        "https://docker.m.daocloud.io",

        "https://hub-mirror.c.163.com",

        "https://mirror.baidubce.com",

        "https://your_preferred_mirror",

        "https://dockerhub.icu",

        "https://docker.registry.cyou",

        "https://docker-cf.registry.cyou",

        "https://dockercf.jsdelivr.fyi",

        "https://docker.jsdelivr.fyi",

        "https://dockertest.jsdelivr.fyi",

        "https://mirror.aliyuncs.com",

        "https://dockerproxy.com",

        "https://mirror.baidubce.com",

        "https://docker.m.daocloud.io",

        "https://docker.nju.edu.cn",

        "https://docker.mirrors.sjtug.sjtu.edu.cn",

        "https://docker.mirrors.ustc.edu.cn",

        "https://mirror.iscas.ac.cn",

        "https://docker.rainbond.cc"

    ]

}

 4.1.6docker启动canal

docker run -p 11111:11111 --name canal3 \
-e canal.destinations=cache \
-e canal.instance.master.address=192.168.8.128:3307  \
-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=demo\\..* \
--network cache-net \
-d canal/canal-server

解析:

  • canal.destinations=cache:canal集群名为cache(可自定义) 
  • canal.instance.master.address:数据库地址
  • canal.instance.dbUsername=canal : 配置canal的用户名需要与前面配置主从数据库的用户名相同
  • canal.instance.filter.regex=demo\\..*:主从同步canal监听的数据库为demo中的全部表

 表名称监听支持的语法:

mysql 数据解析关注的表,Perl正则表达式.
多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\) 
常见例子:
1.  所有表:.*   or  .*\\..*
2.  canal schema下所有表: canal\\..*
3.  canal下的以canal打头的表:canal\\.canal.*
4.  canal schema下的一张表:canal.test1
5.  多个规则组合使用然后以逗号隔开:canal\\..*,mysql.test1,mysql.test2 

4.2 配置canal集成到服务中

4.2.1引入依赖

        <dependency><groupId>top.javatool</groupId><artifactId>canal-spring-boot-starter</artifactId><version>1.2.1-RELEASE</version></dependency>

4.2.2编写配置

canal:destination: cacheserver: 192.168.8.128:11111

4.2.3编写监听方法,监听Canal信息

数据库发送增删改操作时就会被ItemHandler监听到,然后执行对应的方法体,完成对本地缓存和redis的操作

package com.heima.item.canal;import com.github.benmanes.caffeine.cache.Cache;
import com.heima.item.pojo.Item;
import com.heima.item.utils.RedisHandler;
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 EntryHandler<Item> {@Autowiredprivate RedisHandler redisHandler;@Autowiredprivate Cache<Long, Item> itemCache;@Overridepublic void insert(Item item) {//写数据到JVM缓存中itemCache.put(item.getId(), item);//写数据到redis中redisHandler.saveItem(item);}@Overridepublic void update(Item before, Item after) {//写数据到JVM缓存中itemCache.put(after.getId(), after);//写数据到redis中redisHandler.saveItem(after);}@Overridepublic void delete(Item item) {//删除本地缓存数据itemCache.invalidate(item.getId());//删除redis中的数据redisHandler.deleteItem(item.getId());}
}

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

4.2.4如果canal的logs日志发生以下错误(数据库的修改无法同步到redis和本地缓存)

 Received error packet: errno = 1236, sqlstate = HY000 errmsg = unknown error reading log event on the master; the first event 'mysql-bin.000003' at 4, the last event read from '/var/lib/mysql/mysql-bin.000003' at 154, the last byte read from '/var/lib/mysql/mysql-bin.000003' at 154.

 执行下面指令:

set global binlog_checksum='NONE'

然后重启Mysql容器,就解决问题了!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com