微服务商城系统(五)lua、Canal 实现广告缓存

一、学习目标

  • Lua 简单使用
         Lua 语法 输出、变量定义、数据类型、流程控制 (if…)、循环操作、函数、表 (数组) 、模块。
        
  • Lua 用作 广告缓存操作
        广告缓存载入与读取。
        
  • OpenResty 理解配置
         OpenResty 封装了 Nginx,并且提供了 Lua 扩展,大大提升了 Nginx 对并发处理的能力,10K-1000K 。
        
  • Nginx
         限流操作:漏斗限流原理
    • 控制速率
    • 并发量控制
          
  • Canal
         理解 Canal 工作原理,并用它实现数据同步操作。

二、首页分析

     关于广告的表都在 tb_content 库中。
    
广告分类表:
在这里插入图片描述
广告表:
在这里插入图片描述
    整个广告缓存(使用缓存可以提高首页的加载速度)架构是这样的:
在这里插入图片描述
     可以看到,根据分类 id 访问广告内容时,先是去 Nginx 缓存中查找,如果找到了,就直接把缓存里的广告返回,如果没有找到的话,会通过 Lua 脚本到 Redis 缓存中查找。如果 Redis 中有数据,则会将数据存入到 Nginx 的缓存中,并返回查询到的数据。如果 Redis 中再没有找到的话,会通过 Lua 脚本到 MySQL 数据库中进行查找,如果 MySQL 中查到了数据,就会把数据存入到 Redis 中并返回查询数据。
    

三、Lua 简介

     Lua 是一个小巧的脚本语言。 其设计目的,是为了通过灵活嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua 由标准 C 编写而成,几乎在所有操作系统和平台上都可以编译、运行。Lua 并没有提供强大的库,这是由它的定位决定的。所以 Lua 不适合作为开发独立应用程序的语言。Lua 有一个同时进行的 JIT 项目,提供在特定平台上的即时编译功能。
     简单来说,Lua 是一种轻量小巧的脚本语言,用标准 C 语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
    

  • 特性
    (1)支持面向过程 (SQL 语句就是一种面向过程的喔 )编程和函数式编程
    (2)自动内存管理
    (3)只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
    (4)语言内置模式匹配
    (5)闭包
    (6)函数也可以看做一个值
    (7)提供多线程(协同进程,并非操作系统所支持的线程)支持
        通过 闭包 和 table 可以很方便地 支持面向对象编程 所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
        

  • 应用场景
    (1)游戏开发
    (2)独立应用脚本
    (3)Web 应用脚本
    (4)扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
    (5)安全系统,如入侵检测系统
        Redis 中嵌套调用实现类似事务的功能
        web 容器中应用处理一些过滤 缓存等等的逻辑,例如 nginx。
        

  • 安装
        在虚拟机器中查看本地服务,已经下载了 lua 啦:
    在这里插入图片描述
        接下来需要进行安装,进入 lua 目录下,然后使用 linux make test 命令:
    在这里插入图片描述最后执行 lua 测试查看 lua 是否安装成功:
    在这里插入图片描述

  • 入门程序
        lua 有 交互式编程 和 脚本式编程。
        脚本式编程需要编写脚本,然后再执行命令,执行脚本才可以。一般采用脚本式编程。
        
    新建目录并创建 hello.lua 文件:
    在这里插入图片描述
    内容为:
    在这里插入图片描述
    执行结果:
    在这里插入图片描述


交互式编程 就是直接输入语法,就能执行,Lua 交互式编程模式可以通过命令 lua -i 或 lua 来启用,比如:
在这里插入图片描述
    

  • 注释

(1)一行注释:两个减号是单行注释:

--

(2)多行注释:

--[[
 多行注释
 多行注释
 --]]

比如:
在这里插入图片描述
运行结果:
在这里插入图片描述
    

  • 定义变量
        默认情况下,定义的变量都是全局变量。如果使用局部变量,需要声明为 local ,比如:
 -- 全局变量赋值
 a=1
 -- 局部变量赋值
 local b=2

    如果变量没有初始化,则 它的值为 nil,表示是无效变量。 这和 Java 中的 null 不同。
在这里插入图片描述
    可以看到,局部变量只能在同一个方法或流程中使用,换了个行就输出 “nil” 了。
    

  • 数据类型
         Lua 是动态类型语言,变量不需要定义类型,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
        Lua 中有 8 个基本类型,分别为:nil、boolean、number、string、userdata、function、thread 和 table。
    在这里插入图片描述

实例:

print(type("Hello world"))      --> string
print(type(10.4*3))             --> number
print(type(print))              --> function
print(type(type))               --> function
print(type(true))               --> boolean
print(type(nil))                --> nil

    

  • 流程控制

    Lua if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
    
语法:

if(布尔表达式)
then
   --[ 在布尔表达式为 true 时执行的语句 --]
end

比如:
在这里插入图片描述
    

  • 循环

(1)while 循环
    满足条件就循环。
    Lua 编程语言中 ,while 循环语句在判断条件为 true 时会重复执行循环体语句。 语法:

while(condition)
do
   statements
end

例:
在这里插入图片描述
(2)for 循环

     Lua 编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。
语法:

1->10 1:exp1 10:exp2 2:exp3:递增的数量
for var=exp1,exp2,exp3 
do  
    <执行体>  
end  

     var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 “执行体”。exp3 是可选的,如果不指定,默认为1。
例:
在这里插入图片描述
    i=1 从 1 开始循环,循环数据到 9 结束,每次递增 2 。

(3)repeat…until语句
    Lua 编程语言中 repeat…until 循环语句不同于 for 和 while循环,for 和 while 循环的条件语句在当前循环执行开始时判断,而 repeat…until 循环的条件语句在当前循环结束后判断。
语法:

repeat
   statements
until( condition )

例:
在这里插入图片描述

  • 函数(类似于 Java 中的方法)
         lua 中也可以定义函数,类似于 Java 中的方法。例如:
--[[ 函数返回两个值的最大值 --]]
function max(num1, num2)

   if (num1 > num2) then
      result = num1;
   else
      result = num2;
   end

   return result; 
end

调用函数:

print("两值比较最大值为 ",max(10,4))
print("两值比较最大值为 ",max(5,6))

执行之后的结果:
在这里插入图片描述
可以用 “…” 表示拼接:
在这里插入图片描述

     table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
     Lua 也是通过 table 来解决模块(module)、包(package)和对象(Object)的。
    
例:
在这里插入图片描述

  • 模块
    (1)模块定义
        模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
        创建一个文件叫 module.lua,在 module.lua 中创建一个独立的模块,如:
    在这里插入图片描述
        由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。
        上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用。
        
    (2)require 函数

    require 用于 引入其他的模块,类似于 java 中的类要引用别的类的效果。
    
用法:

require("<模块名>")
require "<模块名>"

    两种都可以。
    我们可以将上面定义的 module 模块引入使用,创建一个 test_module.lua 文件:
在这里插入图片描述
运行结果:
在这里插入图片描述
    

四、OpenResty 简介

    OpenResty (又称:ngx_openresty) 是一个基于 nginx 的可伸缩的 Web 平台,由中国人章亦春发起,提供了很多高质量的第三方模块。
    OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty 可以 快速构造出足以胜任 10K-1000k 并发连接响应的超高性能 Web 应用系统。
    360,UPYUN,阿里云,新浪,腾讯网,去哪儿网,酷狗音乐等都是 OpenResty 的深度用户。
    OpenResty 简单理解 就相当于封装了 nginx,并且集成了 LUA 脚本,开发人员只需要简单的其提供了模块就可以实现相关的逻辑,而不再像之前,还需要在 nginx 中自己编写 lua 的脚本,再进行调用了。
    

  • 安装openresty

(1)添加仓库执行命令

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

(2)执行安装

yum install openresty

(3)安装成功后 会在默认的目录如下:

/usr/local/openresty
  • 安装nginx

     默认已经安装好了 nginx,在目录:/usr/local/openresty/nginx 下。
     修改 /usr/local/openresty/nginx/conf/nginx.conf,将配置文件使用的根设置为 root,目的就是将来要使用 lua 脚本的时候 ,直接可以加载在 root 下的 lua 脚本。

cd /usr/local/openresty/nginx/conf
vi nginx.conf

修改代码如下:
在这里插入图片描述

  • 测试访问
        重启 centos 虚拟机,然后访问测试 Nginx
    访问地址:http://192.168.211.132/ ,可以看到:
    在这里插入图片描述

五、广告缓存的载入与读取

     需要在页面上显示广告的信息。
先来测试一下,把数据从 MySQL 中查出来,并写到 Redis 中。测试流程如下:
(1)Lua 脚本实现缓存操作。
     创建 /root/lua 目录,在该目录下创建 update_content.lua,目的就是连接 mysql 查询数据 并存储到 Redis 中。

💎 代码如下:

ngx.header.content_type="application/json;charset=utf8"
local cjson = require("cjson")
local mysql = require("resty.mysql")

-- 获取用户请求 URL 的参数
local uri_args = ngx.req.get_uri_args()

-- 获取请求参数中的id 即广告的分类id
local id = uri_args["id"]

-- 连接数据库
local db = mysql:new()

db:set_timeout(1000)
local props = {
    host = "192.168.211.132",
    port = 3306,
    database = "changgou_content",
    user = "root",
    password = "123456"
}

local res = db:connect(props)
local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order"
res = db:query(select_sql)
db:close()

local redis = require("resty.redis")
local red = redis:new()
red:set_timeout(2000)

-- 连接 Redis 端口
local ip ="192.168.211.132"
local port = 6379
red:connect(ip,port)
red:set("content_"..id,cjson.encode(res))
red:close()

ngx.say("{flag:true}")

(2)配置 Nginx 的 Server,拦截请求路径,拦截后交给 Lua 处理。
     修改 /usr/local/openresty/nginx/conf/nginx.conf 文件。
💎 代码如下:

server {
    listen       80;
    server_name  localhost;

    location /update_content {
        content_by_lua_file /root/lua/update_content.lua;
    }
}

修改完后需要进入管理员使用的系统程序文件,重启 nginx:
在这里插入图片描述
先去 Redis 中把原来的缓存清除,然后访问路径:
在这里插入图片描述
     再到 Redis 中看,增加缓存成功了。
    
再写一个 Lua 脚本,从 Redis 中获取数据,在 /root/lua 目录下创建 read_content.lua :

--设置响应头类型
ngx.header.content_type="application/json;charset=utf8"

--获取请求中的参数 ID
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];

--获取本地缓存
local cache_ngx=ngx.shared.dis_cache;
--根据ID 获取本地缓存数据
local contentCache=cache_ngx:get('content_cache_'..id);

-- 连接 Redis
if contentCache == "" or contentCache == nil then
    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(2000)
    red:connect("192.168.211.132", 6379)
    local rescontent=red:get("content_"..id);

    if ngx.null == rescontent then
        local cjson = require("cjson");
        local mysql = require("resty.mysql");
        local db = mysql:new();
        db:set_timeout(2000)
        local props = {
            host = "192.168.211.132",
            port = 3306,
            database = "changgou_content",
            user = "root",
            password = "123456"
        }
        local res = db:connect(props);
        local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order";
        res = db:query(select_sql);
        local responsejson = cjson.encode(res);
	
        red:set("content_"..id,responsejson);


        ngx.say(responsejson);
        db:close()
    else
        -- 将 Redis 中的缓存存入到 Nginx 中,缓存 10 分钟
        cache_ngx:set('content_cache_'..id, rescontent, 10*60);
        ngx.say(rescontent)
    end
    red:close()
else
    ngx.say(contentCache)

end

     可以看到,这段逻辑是这样的,先去 Nginx 缓存中获取, Nginx 本地中就有缓存的话,会直接进行响应;如果 Nginx 中没有的话,再去 Redis 中,如果 Redis 中有缓存,会把 数据存到 Nginx 中,并进行响应;如果 Redis 中没有的话去 MySQL 中去找,把查询结果存到 Redis 中,并进行响应。
     这里 Redis 缓存是以 key、value 的形式的,key 是 广告分类 ID,value 是根据 ID 查到的记录。
     上述  local cache_ngx=ngx.shared.dis_cache;  是需要在 nginx.conf 中进行配置的:
在这里插入图片描述

在 /usr/local/openresty/nginx/conf/nginx.conf 中配置如下:

location /read_content {
     content_by_lua_file /root/lua/read_content.lua;
}

看看效果:
在这里插入图片描述
    

六、Nginx 限流

     一般情况下,首页的并发量是比较大的,即使 有了多级缓存,当用户不停的刷新页面,或者 有恶意的请求 大量达到,也会对系统造成影响。限流就是保护措施之一。

  • 生活中限流对比
         水坝泄洪,通过闸口限制洪水流量(控制流量速度)。
         办理银行业务:所有人先领号,各窗口叫号处理。每个窗口处理速度根据客户具体业务而定,所有人排队等待叫号即可。若快下班时,告知客户明日再来(拒绝流量)。
         火车站排队买票安检,通过排队 的方式依次放入。(缓存带处理任务)
        
  • Nginx的限流
         Nginx提供两种限流的方式:一是控制速率,二是控制并发连接数。
1、控制速率

     控制速率的方式之一就是采用漏桶算法。
(1)漏桶算法实现控制速率限流
     漏桶 (Leaky Bucket) 算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。示意图如下:

在这里插入图片描述
(限流是解决雪崩的有效手段之一。)

(2)Nginx 的配置

修改 /usr/local/openresty/nginx/conf/nginx.conf :

user  root root;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #cache
    lua_shared_dict dis_cache 128m;

    #限流设置 binary_remote_addr:根据请求 IP 进行限流
    #contentRateLimit 是缓存空间的名称
    limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=2r/s;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        location /update_content {
            content_by_lua_file /root/lua/update_content.lua;
        }

        location /read_content {
            #使用限流配置
            limit_req zone=contentRateLimit;
            content_by_lua_file /root/lua/read_content.lua;
        }
    }
}

配置说明:

  • binary_remote_addr :是一种 key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
  • zone:定义共享内存区来存储访问信息, contentRateLimit:10m 表示一个大小为 10M,名字为 contentRateLimit 的内存区域。1M 能存储 16000 IP 地址的访问信息,10M 可以存储 16W IP 地址访问信息。
  • rate :用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每 100 毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续 100 毫秒内又有请求到达,将拒绝处理该请求。我们这里设置成 2 方便测试。
        
    (3)处理突发流量
        上面例子限制 2r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。
server {
    listen       80;
    server_name  localhost;
    location /update_content {
        content_by_lua_file /root/lua/update_content.lua;
    }
    location /read_content {
        limit_req zone=contentRateLimit burst=4;
        content_by_lua_file /root/lua/read_content.lua;
    }
}

     burst 译为 突发、爆发,表示在超过设定的处理速率后能额外处理的请求数,当 rate=10r/s 时,将 1s 拆成 10 份,即每 100ms 可处理 1 个请求。
    此处,burst=4 ,若同时有 4 个请求到达,Nginx 会处理第一个请求,剩余 3 个请求将放入队列,然后每隔 500ms 从队列中获取一个请求进行处理。若请求数大于 4,将拒绝处理多余的请求,直接返回 503.
    不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate 依然为10r/s,排队中的 50 个请求虽然每 100ms 会处理一个,但第 50 个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。
    因此,burst 往往结合 nodelay 一起使用。nodelay 用于不延迟,并行处理。

如:

server {
    listen       80;
    server_name  localhost;
    location /update_content {
        content_by_lua_file /root/lua/update_content.lua;
    }
    location /read_content {
        limit_req zone=contentRateLimit burst=4 nodelay;
        content_by_lua_file /root/lua/read_content.lua;
    }
}

表示:
     平均每秒允许不超过 2 个请求,突发不超过 4 个请求,并且处理突发 4 个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。
    如上两种配置结合就达到了速率稳定,但突然流量也能正常处理的效果。完整配置代码如下:

user  root root;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #cache
    lua_shared_dict dis_cache 128m;

    #限流设置
    limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=2r/s;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        location /update_content {
            content_by_lua_file /root/lua/update_content.lua;
        }

        location /read_content {
            limit_req zone=contentRateLimit burst=4 nodelay;
            content_by_lua_file /root/lua/read_content.lua;
        }
    }
}

     进行测试,发现 在1秒钟之内可以刷新 4 次,正常处理;但是超过之后,连续刷新 5 次,抛出异常。
    

2、控制并发量(连接数)

     ngx_http_limit_conn_module 提供了限制连接数的能力。主要是利用limit_conn_zone 和 limit_conn 两个指令。
     利用连接数限制 某一个用户的 ip 连接的数量 来控制流量。
     注意:并非所有连接都被计算在内 只有当服务器正在处理请求并且已经读取了整个请求头时,才会计算有效连接。
    
配置语法:

Syntax:	limit_conn zone number;
Default:;
Context: http, server, location;

(1)配置限制固定连接数
在 http{… 中添加:

    #个人IP显示
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    #针对整个服务所有的并发量控制
    limit_conn_zone $server_name zone=perserver:10m;

在 server{… 中添加:

  location /brand {
            limit_conn perip 3;      #单个客户端ip与服务器的连接数.
            limit_conn perserver 5;  #限制与服务器的总连接数
            #同一个IP只允许有2个并发连接
            #limit_conn addr 2;
            #所有以/brand的请求,都将交给  192.168.211.1服务器的18081程序处理.
            proxy_pass http://192.168.211.1:18081;
        }

参数说明:

  • limit_conn_zone $binary_remote_addr zone=addr:10m; 表示限制根据用户的 IP 地址来显示,设置存储地址为的内存大小 10M
  • limit_conn addr 2; 表示 同一个地址只允许连接 2 次。

     此时开 3 个线程,测试的时候会发生异常,开 2 个就不会有异常。
    
(2)限制 每个客户端IP 与服务器的连接数,同时限制与虚拟服务器的连接总数。

 location / {
        limit_conn perip 10;#单个客户端ip与服务器的连接数.
        limit_conn perserver 100; #限制与服务器的总连接数
        root   html;
        index  index.html index.htm;
    }

七、Canal 同步广告

     Canal 可以用来监控数据库数据的变化,从而获得新增数据,或者修改的数据,(只)适用于 MySQL 同步 (比如上文是要查询的时候才去读缓存,读到 MySQL 的数据时才会也写入 Redis 缓存,但是如果不读数据的时候,MySQL 数据更新了,是没有同步到 Redis 中去的)。 它是应 阿里巴巴 存在 杭州 和 美国 的 双机房部署,存在跨机房同步的业务需求而提出的。     阿里系公司 开始逐步尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务。
    

  • Canal 工作原理
    在这里插入图片描述

    原理相对比较简单:
(1)Canal 模拟 mysql slave 的交互协议,伪装自己为 mysql slave,向 mysql master 发送dump 协议
(2)mysql master收到 dump 请求,开始推送 binary log 给 slave(也就是canal,就是说 canal 相当于 mysql 的从节点)
(3)canal 解析 binary log 对象 (原始为 byte 流)
(4)canal 需要使用到 mysql,我们需要先安装 mysql,虚拟机中已经安装了 mysql 容器,但 canal 是基于 mysql 的主从模式实现的,所以必须先开启 binlog。
     可以看到,cannal 监听到 MySQL 的数据,然后我们新建 canal 微服务去对数据进行操作。
    

  • 开启 binlog模式

     先使用 docker 创建 mysql 容器。

(1)连接到 mysql 中,并修改 /etc/mysql/mysql.conf.d/mysqld.cnf 需要开启主 从模式,开启 binlog 模式。

执行如下命令,编辑 mysql 配置文件:

docker exec -it mysql /bin/bash
cd /etc/mysql/mysql.conf.d
vi mysqld.cnf

修改 mysqld.cnf 配置文件,添加如下配置:

log-bin/var/lib/mysql/mysql-bin
server-id=12345

(2) 创建账号, 用于 canal 读取数据,测试使用:
使用 root 账号创建用户并授予权限

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

(3)重启 mysql 容器

docker restart mysql

    

  • canal容器安装

下载镜像:

docker pull docker.io/canal/canal-server

容器安装:

docker run -p 11111:11111 --name canal -d docker.io/canal/canal-server

     进入容器,修改核心配置 canal.properties 和instance.properties,canal.properties 是 canal 自身的配置,instance.properties 是需要同步数据的数据库连接配置。

canal.properties 是 canal 本身的配置,修改 canal.properties 里的 id,不能和 mysql 的 server-id 重复,比如改成 1001。:

docker exec -it canal /bin/bash
cd canal-server/conf/
vi canal.properties

instance.properties 是关于要同步数据库的配置:

cd example/
vi instance.properties

修改 instance.properties,配置数据库连接地址:
在这里插入图片描述

这里的 canal.instance.filter.regex 有多种配置,比如:
在这里插入图片描述
     * 表示所有数据库,.\\..* 表示所有表,可以看到,上图中 \… 表示对所有数据库中的所有表 都进行监听。
     可以参考: https://github.com/alibaba/canal/wiki/AdminGuide


mysql 数据解析关注的表,Perl 正则表达式:
多个正则之间以逗号(,)分隔,转义符需要双斜杠(\)
常见例子:

  1. 所有表:.* or .\…
  2. canal schema下所有表: canal\…*
  3. canal下的以canal打头的表:canal\.canal.*
  4. canal schema下的一张表:canal.test1
  5. 多个规则组合使用:canal\…*,mysql.test1,mysql.test2 (逗号分隔)

注意:此过滤条件只针对 row 模式的数据有效 ( mixed/statement 因为不解析 sql,所以无法准确提取 tableName 进行过滤)


配置完成后,设置开机启动,并记得重启 canal。

docker update --restart=always canal
docker restart canal
  • 搭建 Canal 微服务
         在 changgou-service 中搭建 changgou-service-content 微服务.
    (1)导入依赖
 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!--canal依赖-->
        <dependency>
            <groupId>com.xpand</groupId>
            <artifactId>starter-canal</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou-service-content-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

(2)提供 application.yml

server:
  port: 18082
spring:
  application:
    name: canal
  redis:
    host: 192.168.211.132
    port: 6379
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE
#canal配置
canal:
  client:
    instances:
      # exmaple
      example:
        host: 192.168.211.132
        port: 11111

(3)提供启动类

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableCanalClient
public class CanalApplication {
    public static void main(String[] args) {
        SpringApplication.run(CanalApplication.class,args);
    }
}

(4)提供监听类,实现对表增删改操作的监听

@CanalEventListener
public class CanalDataEventListener {


    /**
     * 增加操作监听
     *
     * @param eventType 当前操作的类型
     * @param rowData   发生变更的数据
     */
    @InsertListenPoint
    public void onEventInsert(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        for (CanalEntry.Column column : rowData.getAfterColumnsList()) {
            System.out.println("列名:" + column.getName() + "-----变更的数据:" + column.getValue());
        }
    }

    /**
     * 更新操作监听
     * @param eventType
     * @param rowData
     */
    @UpdateListenPoint
    public void onEventUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
            System.out.println("修改前列名:" + column.getName() + "-----修改前数据:" + column.getValue());
        }
        for (CanalEntry.Column column : rowData.getAfterColumnsList()) {
            System.out.println("修改后列名:" + column.getName() + "-----修改后数据:" + column.getValue());
        }
    }

    /**
     * 删除操作监听
     * @param eventType
     * @param rowData
     */
    @DeleteListenPoint
    public void onEventDelete(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
            System.out.println("删除前列名:" + column.getName() + "-----变更的数据:" + column.getValue());
        }
    }

}

运行起来,并对表进行更新操作, 结果:
在这里插入图片描述
     可以看到,对于修改的那一条记录,程序一监控到有更新操作,就会将它被修改前所有的属性进行输出,再把修改后的所有属性输出。程序遍历的是一条记录的各个属性。
     也可以自定义监听操作,比如同时监听 删除 和 更新,还可以指定监听的数据库、表、实例地址:

 @ListenPoint(
            // 监听的操作类型
            eventType = {CanalEntry.EventType.DELETE, CanalEntry.EventType.UPDATE},

            // 指定监听的数据库
            schema = {"changgou_content"},

            // 指定监控的表
            table = {"tb_content"},

            // 指定实例的地址
            destination = "example"
    )
    public void onEventCustomUpdate(CanalEntry.EventType eventType,CanalEntry.RowData rowData){
        for(CanalEntry.Column column:rowData.getBeforeColumnsList()){
            System.out.println("自定义操作前列名:" + column.getName() + "-----变更的数据:" + column.getValue());
        }
        for(CanalEntry.Column column:rowData.getAfterColumnsList()){
            System.out.println("自定义操作后列名:" + column.getName() + "-----变更的数据:" + column.getValue());
        }
    }

     这时去删除一条记录,运行结果:
在这里插入图片描述

     可以看到,之前的删除监听 和 这个自定义监听都起了作用。
    这样,我们就实现了对操作的监听,只是示范一下监听的效果而已。接下来就需要实战一下,自己搭建 广告 微服务,实现数据的同步。
    

八、总结

(1)广告是在商城首页展示的,使用缓存可以提升首页的加载速度。
    
(2)Lua 是一种轻量小巧的脚本语言,是动态类型语言,支持面向过程(SQL 语句就是面向过程的。)可以定义变量(默认情况下是全局变量)、进行流程控制、定义函数、定义模块等。
    
(3)OpenResty 相当于封装了 nginx,并且集成了 LUA 脚本,可以进行配置,比如,将根目录设置为 root,就会直接加载在 root 下的 lua 脚本。
     还通过修改 nginx.conf 可以对路径进行拦截,拦截后交给指定 Lua 处理。
Lua 可以实现:
     先获取用户请求 URL 里的参数中的 分类 ID 信息,然后连接 MySQL 数据库,执行 SQL 语句,根据分类 ID 查询广告信息,再连接 Redis,把广告信息集作为 value,content_xxx(xxx 是 id 具体值)作为 key 进行存储。这样就相当于把数据从 MySQL 中查出来,并缓存到 Redis 中。
Lua 还可以实现:
     先去 Nginx 缓存中获取, Nginx 本地中就有缓存的话,会直接进行响应。
     如果 Nginx 中没有的话,再去 Redis 中,
     如果 Redis 中有缓存,会把 数据存到 Nginx 中,并进行响应;
     如果 Redis 中没有的话去 MySQL 中去找,把查询结果存到 Redis 中,并进行响应;
     以上两个逻辑分别对应 /update_content 和 /read_content 路径。(第二个逻辑就是本文开头的缓存架构示意图的实现,可能后文中项目会调用到 /read_content 路径叭,需要留意一下)。
    
(4)并发量比较大的情况下,可能多级缓存还是不够用的,限流是保护措施之一。Nginx 提供两种限流的方式:控制速率(漏桶算法)和 控制并发连接数。

(5)Canal 可以用来监控数据库数据的变化,只适用于监控 MySQL 数据库,它的原理是把自己伪装为 slave 节点,这样 master 节点会推送给它 binary log。所以使用前需要开启主从 和 binlog 模式。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值