一、学习目标
- 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 正则表达式:
多个正则之间以逗号(,)分隔,转义符需要双斜杠(\)
常见例子:
- 所有表:.* or .\…
- canal schema下所有表: canal\…*
- canal下的以canal打头的表:canal\.canal.*
- canal schema下的一张表:canal.test1
- 多个规则组合使用: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 模式。