首页高并发架构的学习

Updated on with 0 views and 0 comments

首页高并发架构的学习

首页高并发架构的解决方案

传统实现思路

广告管理 图片 地址是 fastFDS

10009

如上图此种方式 简单,直接通过数据库查询数据展示给用户即可,但是通常情况下,首页(门户系统的流量一般非常的高)不适合直接通过mysql数据库直接访问的方式来获取展示。

高并发架构设计

1.首先访问nginx ,我们可以采用缓存的方式,先从nginx本地缓存中获取,获取到直接响应

2.如果nginx没有获取到,再次访问redis,我们可以从redis中获取数据,如果有 则返回,并缓存到nginx中

3.如果redis没有获取到,再次访问mysql,我们从mysql中获取数据,再将数据存储到redis中,返回。

而这里面,我们都可以使用LUA脚本嵌入到程序中执行这些查询相关的业务。

常用的高并发解决方案:

1. 缓存   nosql  ==> redis,  nginx 本地缓存  ==> 保护 数据库
2. 静态化   servlet,jsp 动态  ==》 tomcat     html+css+js ==> nginx

Nginx 抵抗高并发(Tomcat 并发500)(nginx 只能解析静态资源)

nginx 本地缓存、redis、mysql 二级缓存 保护数据库

image-20210909094634640

Lua 脚本语言

是什么

Lua [1] 是一个小巧的脚本语言。它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

简单来说:

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

特性

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

应用场景

  • 游戏开发
  • 独立应用脚本
  • Web 应用脚本
  • 安全系统,如入侵检测系统
  • redis中嵌套调用实现类似事务的功能
  • web容器中应用处理一些过滤 缓存等等的逻辑,例如nginx。

安装

linux 安装

安装步骤,在linux系统中执行下面的命令。

curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
tar zxf lua-5.3.5.tar.gz
cd lua-5.3.5
make linux test

注意:此时安装,有可能会出现如下错误:

1560739143890

此时需要安装lua相关依赖库的支持,执行如下命令即可:

yum install gcc libtermcap-devel ncurses-devel libevent-devel readline-devel -y

重新编译lua

make linux test

安装lua

make install

删除老版本lua,关联新版本lua

cd /usr/bin
rm -rf lua luac

ln -s /usr/local/bin/lua /usr/bin/lua
ln -s /usr/local/bin/luac /usr/bin/luac

此时再执行lua测试看lua是否安装成功

[root@localhost ~]# lua
Lua 5.3.5  Copyright (C) 1994-2018 Lua.org, PUC-Rio

退出

ctrl+c

入门程序

在线脚本测试:https://www.lua.org/cgi-bin/demo

进入 /root 目录 创建目录 lua_demo

创建hello.lua文件,内容为

编辑文件hello.lua

vi hello.lua

在文件中输入:

print("hello world");

保存并退出。

执行命令

lua hello.lua

输出为:

Hello

效果如下:

image-20210909193455261

Lua的基本语法

运行式开发、交互式开发

lua有交互式编程和脚本式编程。

交互式编程就是直接输入语法,就能执行。

脚本式编程需要编写脚本,然后再执行命令 执行脚本才可以。

一般采用脚本式编程。(例如:编写一个hello.lua的文件,输入文件内容,并执行lua hell.lua即可)

(1)交互式编程

Lua 提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果。

Lua 交互式编程模式可以通过命令 lua -i 或 lua 来启用:

lua -i

(2)脚本式编程

我们可以将 Lua 程序代码保持到一个以 lua 结尾的文件,并执行,该模式称为脚本式编程,例如上面入门程序中将lua语法写到hello.lua文件中。

注释

一行注释:两个减号是单行注释:

--

多行注释:

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

变量

全局变量,默认的情况下,定义一个变量都是全局变量,

如果要用局部变量 需要声明为local.例如:

-- 全局变量赋值
a=1
-- 局部变量赋值
local b=2 

如果变量没有初始化:则 它的值为nil 这和java中的null不同。

数据类型

Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。

Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

数据类型描述
nil这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
boolean包含两个值:false和true。
number表示双精度类型的实浮点数
string字符串由一对双引号或单引号来表示
function由 C 或 Lua 编写的函数
userdata表示任意存储在变量中的C数据结构
thread表示执行的独立线路,用于执行协同程序
tableLua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,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

流程控制

(1)if语句

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

语法:

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

(2)if..else语句

Lua if 语句可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。

语法:

if(布尔表达式)
then
   --[ 布尔表达式为 true 时执行该语句块 --]
else
   --[ 布尔表达式为 false 时执行该语句块 --]
end

循环

(1)while循环[==满足条件就循环==]

Lua 编程语言中 while 循环语句在判断条件为 true 时会重复执行循环体语句。
语法:

while(condition)
do
   statements
end

实例:

a=10
while( a < 20 )
do
   print("a 的值为:", a)
   a = a+1
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。

例子:

for i=1,9,2
do
   print(i)
end

for i=1,9,2:i=1从1开始循环,9循环数据到9结束,2每次递增2

(3)repeat...until语句[==满足条件结束==]

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

语法:

repeat
   statements
until( condition )

函数

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))

执行之后的结果:

两值比较最大值为     10
两值比较最大值为     6

..:表示拼接

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。

Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。

案例:

-- 初始化表
mytable = {}

-- 指定值
mytable[1]= "Lua"

-- 移除引用
mytable = nil

模块

(1)模块定义

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

创建一个文件叫module.lua,在module.lua中创建一个独立的模块,代码如下:

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
 
-- 定义一个常量
module.constant = "这是一个常量"
 
-- 定义一个函数
function module.func1()
    print("这是一个公有函数")
end
 
local function func2()
    print("这是一个私有函数!")
end
 
function module.func3()
    func2()
end
 
return module

由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。

上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用.

(2)require 函数

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

用法:

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

两种都可以。

我们可以将上面定义的module模块引入使用,创建一个test_module.lua文件,代码如下:

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")

print(module.constant)

module.func3()

本地方法不用加模块

模块名 和 文件名一致

OpenResty

介绍

OpenResty(又称:ngx_openresty) 是一个基于 nginx的可伸缩的 Web 平台,由中国人章亦春发起,提供了很多高质量的第三方模块。

OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty可以 快速构造出足以胜任 10K 以上并发连接响应的超高性能 Web 应用系统。

360,UPYUN,华为,阿里云,新浪,腾讯网,去哪儿网,酷狗音乐等都是 OpenResty 的深度用户。

OpenResty 简单理解成 就相当于封装了nginx,并且集成了LUA脚本,开发人员只需要简单的其提供了模块就可以实现相关的逻辑,而不再像之前,还需要在nginx中自己编写lua的脚本,再进行调用了。

安装openresty

linux安装openresty:

1.添加仓库执行命令

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

2.执行安装

yum install openresty -y

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

cd /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

修改代码如下:

image-20210909204250746

启动openresty

cd /usr/local/openresty/nginx/sbin

./nginx

停止openresty

./nginx -s stop

重启openresty

./nginx -s reload

测试访问

重启下centos虚拟机,然后访问测试Nginx

访问地址:http://192.168.153.130:80/

image-20210909204428399

广告缓存的载入与读取

Lua+Nginx配置

(1)实现思路-查询数据放入redis中

实现思路:

定义请求:用于查询数据库中的数据更新到redis中。

a.连接mysql ,按照广告分类ID读取广告列表,转换为json字符串。

b.连接redis,将广告列表json字符串存入redis 。

定义请求:

请求:
	/update_content
参数:
	id  --指定广告分类的id
返回值:
	json

请求地址:<http://192.168.153.130/update_content?id=1>

修改/usr/local/openresty/nginx/conf/nginx.conf文件: 添加头信息,和 location信息

vi /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;
    }
}

虚拟机 安装mysql

 yum install mysql -y

通过 Linux 的 mysql 测试连接 主机的 mysql (主机 linux 进行如下两步操作)

image-20210909210655046

image-20210909210829365

  1. 生效 执行如下查询命令 刷新权限表 就可以任意位置访问

image-20210909210936417

  1. 尝试连接

    查看 ipconfig

    image-20210909211707822

    mysql -u root -h 192.168.109.1/192.168.153.1 -p
    输入密码 admin
    

    通过虚拟网卡 访问主机的 mysql

    image-20210909211730404

创建/root/lua目录,在该目录下创建update_content.lua: 目的就是连接mysql 查询数据 并存储到redis中。

代码如下:

---
--- 
--- Created by BH.
--- DateTime: 2021/9/9 22:38
---
ngx.header.content_type="application/json;charset=utf8"
local cjson = require("cjson")
local mysql = require("resty.mysql")
local uri_args = ngx.req.get_uri_args()
local categoryid = uri_args["id"]

local db = mysql:new()
db:set_timeout(1000)
local props = {
    host = "192.168.153.1",
    port = 3306,
    database = "dongyimaidb",
    user = "root",
    password = "admin"
}
local res = db:connect(props)
local select_sql = "select url,pic from tb_content where status ='1' and category_id='"..categoryid.."'"
res = db:query(select_sql)
db:close()

ngx.say("{\"res\":cjson.encode(res)}")

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

local ip = "8.142.87.27"
local port = 6379
local pass = 123
red:connect(ip,port)
red:auth(pass)
red:set("content_"..categoryid,cjson.encode(res))
red:close()

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

我的 redis 容器用的我的阿里云服务器的 地址

也可以 docker 创建redis容器:

docker run -d --name redis1 --net=host redis:4.0.8

重启Nginx

/usr/local/openresty/nginx/sbin/nginx -s reload

测试地址:http://192.168.153.130/update_content?id=1

此时会将分类ID=1的所有广告查询出来,并存入到Redis缓存。

image-20210909224124665

(2)实现思路-从redis中获取数据

实现思路:

定义请求,用户根据广告分类的ID 获取广告的列表。通过lua脚本直接从redis中获取数据即可。

定义请求:

请求:/read_content
参数:id
返回值:json

在/root/lua目录下创建read_content.lua:

---
--- 
--- Created by BH.
--- DateTime: 2021/9/9 22:54
---
--设置响应头类型
ngx.header.content_type="application/json;charset=utf8"
--获取请求中的参数ID
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
--引入redis库
local redis = require("resty.redis");
--创建redis对象
local red = redis:new()
--设置超时时间
red:set_timeout(2000)
--连接
local ok, err = red:connect("8.142.87.27", 6379)
red:auth("123")
--获取key的值
local rescontent=red:get("content_"..id)
--输出到返回响应中
ngx.say(rescontent)
--关闭连接
red:close()

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

代码:

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

重启Nginx

测试地址:http://192.168.153.130/read_content?id=1

此时会将分类ID=1的所有广告查询出来。

(3)加入openresty本地缓存

如上的方式没有问题,但是如果请求都到redis,redis压力也很大,所以我们一般采用多级缓存的方式来减少下游系统的服务压力。参考基本思路图的实现。

先查询openresty本地缓存 如果 没有

再查询redis中的数据,如果没有

再查询mysql中的数据,但凡有数据 则返回即可。

修改read_content.lua文件,代码如下:

代码如下:

---
--- 
--- Created by BH.
--- DateTime: 2021/9/9 23:05
---
ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local categoryid = uri_args["id"];
--获取本地缓存
local cache_ngx = ngx.shared.dis_cache;
--根据ID 获取本地缓存数据
local contentCache = cache_ngx:get('content_cache_'..categoryid);

if contentCache == "" or contentCache == nil
   then
    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(2000)
    red:connect("8.142.87.27", 6379)
    red:auth("123")
    local rescontent=red:get("content_"..categoryid);
    if ngx.null == rescontent or rescontent == ""
      then
        local cjson = require("cjson");
        local mysql = require("resty.mysql");
        local db = mysql:new();
        db:set_timeout(2000)
        local props = {
            host = "192.168.153.1",
            port = 3306,
            database = "dongyimaidb",
            user = "root",
            password = "admin"
        }
        local res = db:connect(props);
        local select_sql = "select url,pic from tb_content where status ='1' and category_id='"..categoryid.."'";
        res = db:query(select_sql);
        local responsejson = cjson.encode(res);
        red:set("content_"..categoryid,responsejson);
        ngx.say(responsejson);
        ngx.say("{\"from\":\"mysql\"}")
        db:close()
    else
        cache_ngx:set('content_cache_'..categoryid, rescontent, 10*60);
        ngx.say(rescontent)
        ngx.say("{\"from\":\"redis\"}")
    end
    red:close()
else
    ngx.say(contentCache)
    ngx.say("{\"from\":\"nginx_cache\"}")
end

定义lua缓存命名空间,修改nginx.conf,添加如下代码即可:

代码如下: 添加到 server 上 http 中

lua_shared_dict dis_cache 128m;

测试地址:http://192.168.153.130/read_content?id=1

此时会获取分类ID=1的所有广告信息。

此时手动清空redis中的数据,发现依然可以访问到信息。

(4)清除缓存

clear_cache.lua

ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local categoryid = uri_args["id"];
--获取本地缓存
local cache_ngx = ngx.shared.dis_cache;
--清除本地缓存数据
cache_ngx:set('content_cache_'..categoryid,nil);

local redis = require("resty.redis");
local red = redis:new();
red:set_timeout(2000)
red:connect("8.142.87.27",6379)
red:auth("123")
red:set("content_"..categoryid,"");

ngx.say("{\"msg\":\"缓存被清除\"}")

在 nginx.conf配置

location /clear_cache {
            content_by_lua_file /root/lua/clear_nginx_cache.lua;
        }

网站首页部署

1、创建静态web工程

2、拷贝网站首页静态资源

3、把用到的vue\axios类库拷贝过来

编辑index.html

(1)、引入vue、axios类库到当前页面

<script src="js/vue.js"></script>
<script src="js/axios.min.js"></script>

(2)、在页面添加vue识别div

<div id="app">

......网页轮播内容,注意里面不能包含javascript
</div>

(3)、编写vue代码,当页面加载的时候,从服务器端获取广告轮播图数据

<script type="text/javascript">
	//生成一个Vue实例
	var app=new Vue({
		el:"#app",//el ,即是element。要渲染的页面元素
		data:{//数据	
			contentList:[]
		},
		created(){
			let th=this;
			axios.get('/read_content',{       // 还可以直接把参数拼接在url后边
				params:{
					id:1
				}
			}).then(function(res){
				th.contentList = res.data;
				//console.log(th.contentList)
			}).catch(function (error) {
				console.log(error);
			});
		}
	})
</script>

(4)、修改index.html循环显示轮播图

<div class="yui3-u Center banerArea">
<!--banner轮播-->
<div id="myCarousel" data-ride="carousel" data-interval="4000" class="sui-carousel slide">
		<ol class="carousel-indicators">
				<li data-target="#myCarousel" v-for="(content,index) in contentList" :data-slide-to="index" :class="index==0?'active':''" ></li>
		</ol>
		<div class="carousel-inner">
				<div v-for="(content,index) in contentList" :class="index==0?'active item':'item'" >
						<a :href="content.url">
								<img :src="content.pic" style="width:730px;height:454px" />
						</a>
				</div>					  
		</div>
		<a href="#myCarousel" data-slide="prev" class="carousel-control left">‹</a>
  	<a href="#myCarousel" data-slide="next" class="carousel-control right">›</a>
</div>
</div>

部署页面

上传首页相关文件到nginx目录:/usr/local/openresty/nginx/html

访问测试

访问地址: http://192.168.153.130/

canal同步广告

canal可以用来监控数据库数据的变化,从而获得新增数据,或者修改的数据。

canal是应阿里巴巴存在杭州和美国的双机房部署,存在跨机房同步的业务需求而提出的。

阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务。

image-20210910002516233

原理相对比较简单:

  1. canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
  2. mysql master收到dump请求,开始推送binary log给slave(也就是canal)
  3. canal解析binary log对象(原始为byte流)

canal需要使用到mysql,我们需要先安装mysql,给大家发的虚拟机中已经安装了mysql容器,但canal是基于mysql的主从模式实现的,所以必须先开启binlog.

创建数据库容器

默认我的 /root/images 下已经配置好了 配置文件

docker run -id --name=mysql_mast -v /root/images/mysqld.cnf:/etc/mysql/mysql.conf.d/mysql.cnf -v /root/dongyimaidb.sql:/docker-entrypoint-initdb.d/mysql.sql -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=dongyimaidb -p 3309:3306 mysql:5.7

开启binlog模式

先使用docker 创建mysql容器

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

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

命令行如下:

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

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

log-bin=/var/lib/mysql/mysql-bin  #二进制的存储目录
server-id=12345  #当前数据库的唯一编号

由于不能 vi 所以得复制

docker cp mysqld.cnf mysql_mast:/etc/mysql/mysql.conf.d/mysqld.cnf

(2) 通过客户端创建账号 用于测试使用,

使用root账号创建用户并授予权限

在 mysql 数据库执行如下

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

(3)重启mysql容器

docker restart mysql_mast

注意mysql数据库容器的ip地址,使用如下命令获取:

docker inspect mysql 

canal容器安装

下载镜像:

docker pull docker.io/canal/canal-server

查询 mysql 地址

docker inspect mysql_mast

image-20210910010514395

容器安装

docker run -id --name=canal -p 11111:11111 canal/canal-server

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

执行代码如下:

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

修改instance.properties,配置数据库连接地址:刚查看的172.17.0.2

修改canal.properties,增加canal.id:

这里的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模式的数据有效(ps. mixed/statement因为不解析sql,所以无法准确提取tableName进行过滤)

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

docker update --restart=always canal
docker restart canal

canal微服务搭建

当用户执行 数据库的操作的时候,binlog 日志会被canal捕获到,并解析出数据。我们就可以将解析出来的数据进行同步到redis中即可。

思路:创建一个独立的程序,并监控canal服务器,获取binlog日志,解析数据,将数据更新到redis中。这样广告的数据就更新了。

(1)安装辅助jar包

工程starter-canal,它主要提供了SpringBoot环境下canal的支持,我们需要先安装该工程,在starter-canal目录下执行mvn install,如下图:

image-20210910012521796

(2)canal微服务工程搭建

在dongyimai-service下创建dongyimai-canal-service工程,并引入相关配置。

pom.xml

<dependencies>
    <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>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.bh</groupId>
        <artifactId>dongyimai-common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

application.yml配置

server:
  port: 9003
spring:
  application:
    name: canal
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/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:
      example:
        host: 192.168.188.128
        port: 11111

(3)监听创建

在 com.bh.listener 创建一个CanalDataEventListener类,实现对表增删改操作的监听,代码如下:

package com.offcn.canal.listener;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.xpand.starter.canal.annotation.*;
@CanalEventListener
public class CanalDataEventListener {

    /***
     * 增加数据监听
     * @param eventType
     * @param rowData
     */
    @InsertListenPoint
    public void onEventInsert(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }

    /***
     * 修改数据监听
     * @param rowData
     */
    @UpdateListenPoint
    public void onEventUpdate(CanalEntry.RowData rowData) {
        System.out.println("UpdateListenPoint");
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }

    /***
     * 删除数据监听
     * @param eventType
     */
    @DeleteListenPoint
    public void onEventDelete(CanalEntry.EventType eventType) {
        System.out.println("DeleteListenPoint");
    }

    /***
     * 自定义数据修改监听
     * @param eventType
     * @param rowData
     */
    @ListenPoint(destination = "example", schema = "dongyimaidb", table = {"tb_content_category", "tb_content"}, eventType = CanalEntry.EventType.UPDATE)
    public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        System.err.println("DeleteListenPoint");
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }
}

(4)启动类创建

在com.bh中创建启动类,代码如下:

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableCanalClient
public class CanalApp {

    public static void main(String[] args) {
        SpringApplication.run(CanalApp.class,args);
    }
}

(5)测试

启动canal微服务,然后修改任意数据库的表数据,canal微服务后台输出如下:

数据库监听解决了多级缓存中 数据库发生改变(删除、新增、修改)

多级缓存(如 nginx 缓存 和 redis 缓存) 不发生更新的情况

使用 canal作为 server 端 来监听微服务对数据库的相应改变 对 nginx 和 redis 更新


标题:首页高并发架构的学习
作者:HB2Ocean
地址:http://future-hb.top:8080/articles/2021/09/12/1631457542913.html