5. 暢購商城之Lua、Canal實現廣告緩存

第5章 Lua、Canal實現廣告緩存

1. 首頁分析

首頁門戶系統需要展示各種各樣的廣告數據。以京東爲例:
在這裏插入圖片描述
頁面中的廣告一般來說變變更頻率較低,對於這種數據該如何進行處理?

(1) 第一種方式
在這裏插入圖片描述
如圖所示,首頁訪問廣告服務,而廣告服務從數據庫中查詢數據,而後返回給首頁進行展示。這種方式最爲簡單。但是首頁的訪問量一般非常高,不適合直接通過MySQL數據庫直接訪問的方式來獲取展示。
(2) 第二種方式
在這裏插入圖片描述
1.首先訪問Nginx ,採用緩存的方式,先從Nginx本地緩存中獲取,獲取到直接響應

2.如果沒有獲取到則訪問Redis,從Redis中獲取數據,如果有數據則返回,並緩存到Nginx中

3.如果沒有獲取則訪問MySQL,從MySQL中獲取數據,再將數據存儲到Redis中,返回。

2. Lua介紹

2.1 Lua是什麼

Lua [1] 是一種輕量小巧的腳本語言。其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能的弱語言,不需要編譯可以直接運行。Lua是由標準C編寫而成,所有Lua腳本可以容易被C/C++代碼調用,其他所有操作系統和平臺也可以進行編譯、運行

Lua是一種功能強大、高效、輕量級、可嵌入的腳本語言,支持過程編程、面向對象編程、函數編程、數據驅動編程和數據描述;

2.2 優勢

  • 輕量級
    輕量級Lua語言的官方版本只包含一個簡潔的核心和最基本的庫,Lua體積小、啓動速度快,5.0.2版本Lua內核只有120kb,適合嵌入別的程序;

  • 可擴展
    Lua由標準C編寫,所以C/C++的功能都可以使用,而且還可以擴展Java、C#、Smalltalk、Fortran、Ada、Perl和Ruby;

  • 可移植
    Lua使用C編寫,所以適用所有操作系統和平臺(Windiows/Unix、IOS、Android、BREW、Symbian、WindowPhone、Rabbit等等);

  • 完全開源免費
    Lua是免費的開源軟件,可以用於任何目的,包括商業目的完全免費。

2.3 應用場景

  • 遊戲開發
  • 獨立應用腳本
  • Web 應用腳本
  • 擴展和數據庫插件如:MySQL Proxy 和 MySQL WorkBench
  • 安全系統,如入侵檢測系統
  • Redis中嵌套調用實現類似事務的功能
  • web容器中應用處理一些過濾 緩存等等的邏輯,例如Nginx。

2.4 阿里雲安裝Lua

安裝步驟,在服務器中執行下面的命令。

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

注意:此時安裝,有可能會出現如下錯誤:
在這裏插入圖片描述
此時需要安裝lua相關依賴庫的支持,執行如下命令即可:

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

此時再執行lua測試看lua是否安裝成功

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

2.5 入門程序

(1) 創建hello.lua文件並編輯

vi hello.lua

(2)在文件中輸入print(“hello lua”)後保存並退出。

(3)執行命令並查看輸出

lua hello.lua

效果如下:
在這裏插入圖片描述

2.6 Lua的基本語法

Lua有交互式編程和腳本式編程。

  • 交互式編程:是直接輸入語法,通過命令 lua -i 或 lua 來啓用

  • 腳本式編程:將Lua程序代碼保存到一個以.lua結尾的文件並執行

一般採用腳本式編程

2.6.1 註釋

-- 單行註釋

--[[
	多行註釋
	多行註釋
--]]

2.6.2 定義變量

默認的情況下,定義一個變量都是全局變量,如果要用局部變量需要聲明爲local。

-- 全局變量賦值
a=1
-- 局部變量賦值
local b=2 

如果變量沒有初始化:則 它的值爲nil 這和java中的null不同。

如下圖案例:
在這裏插入圖片描述

2.6.3 Lua中的數據類型

Lua 是動態類型語言,變量不要類型定義,只需要爲變量賦值。 值可以存儲在變量中,作爲參數傳遞或結果返回。

Lua 中基本類型如下:

數據類型 描述
nil 只有值nil屬於該類,表示一個無效值(在條件表達式中相當於false)。
boolean false和true
number 雙精度類型的實浮點數
string 字符串由一對雙引號或單引號來表示
function 由 C 或 Lua 編寫的函數
userdata 表示任意存儲在變量中的C數據結構
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

2.6.4 流程控制

(1)if語句

Lua中 if 語句 由一個布爾表達式作爲條件判斷,其後緊跟其他語句組成。

語法:

if(布爾表達式)
then
   --[ 在布爾表達式爲 true 時執行的語句 --]
end

實例:
在這裏插入圖片描述
(2)if…else語句

Lua中 if 語句可以與 else 語句搭配使用, 在 if 條件表達式爲 false 時執行 else 語句代碼塊。

語法:

if(布爾表達式)
then
   --[ 布爾表達式爲 true 時執行該語句塊 --]
else
   --[ 布爾表達式爲 false 時執行該語句塊 --]
end

實例:
在這裏插入圖片描述

2.6.5 循環

(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 語句中控制。

語法:

for var=exp1,exp2,exp3 
do  
    <執行體>  
end  

var 從 exp1 變化到 exp2,每次變化以 exp3 爲步長遞增 var,並執行一次 "執行體"。exp3 是可選的,如果不指定,默認爲1。若想執行遞減操作,exp3應爲負數

實例:

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

i從1開始循環,當循環到 i=9 時停止,每次 i 遞增 2
效果如下:在這裏插入圖片描述
(3)repeat…until語句[滿足條件結束]

Lua 編程語言中 repeat…until 循環語句不同於 for 和 while循環,for 和 while 循環的條件語句在當前循環執行開始時判斷,而 repeat…until 循環的條件語句在當前循環結束後判斷,相當於do while循環

語法:

repeat
   statements
until( condition )

實例:

num =5
repeat
print(num)
num=num-1
until (num==0)

效果如下:
在這裏插入圖片描述

2.6.6 函數

lua中也可以定義函數,類似於java中的方法。結束需要添加end

實例:

--[[ 函數返回兩個值的最大值 --]]
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))

效果如下:
在這裏插入圖片描述

2.6.7 表

table 是 Lua 的一種數據結構用來幫助我們創建不同的數據類型,如:數組、字典等。

Lua也是通過table來解決模塊(module)、包(package)和對象(Object)的。

實例:

-- 初始化表
mytable = {}

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

-- 移除引用
mytable = nil

2.6.7 模塊

(1) 模塊定義

模塊類似於一個封裝庫,從 Lua5.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 "<模塊名>"

(3) 應用
將上面定義的module模塊引入使用,創建一個test_module.lua文件

-- test_module.lua 文件
-- module 模塊爲上文提到到 module.lua
require("module")
print(module.constant)
module.func3()

效果如下:
在這裏插入圖片描述

3. OpenResty®

3.1 OpenResty介紹

OpenResty® 是一個基於Nginx與Lua的高性能 Web 平臺,其內部集成了大量精良的Lua庫、第三方模塊以及大多數的依賴項。用於方便地搭建能夠處理超高併發、擴展性極高的動態 Web 應用、Web 服務和動態網關。

OpenResty® 通過匯聚各種設計精良的Nginx模塊(主要由 OpenResty 團隊自主開發),從而將 Nginx 有效地變成一個強大的通用 Web 應用平臺。這樣,Web 開發人員和系統工程師可以使用Lua腳本語言調動Nginx支持的各種C以及Lua模塊,快速構造出足以勝任10K乃至1000K以上單機併發連接的高性能 Web 應用系統。

OpenResty® 的目標是讓你的Web服務直接跑在 Nginx 服務內部,充分利用 Nginx 的非阻塞 I/O 模型,不僅僅對 HTTP 客戶端請求,甚至於對遠程後端諸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都進行一致的高性能響應。

3.2 安裝OpenResty

OpenResty提供了各種服務器環境的安裝方式,因爲我是CentOS, 所以選擇了以下命令,其他類型可以到官網查看對應方法。

1.添加倉庫執行命令

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

2.執行安裝

yum install openresty

3.安裝成功後 默認在/usr/local/目錄,如下圖
在這裏插入圖片描述

3.3 配置Nginx

默認已經安裝好了Nginx,在目錄:/usr/local/openresty/nginx 下。

修改/usr/local/openresty/nginx/conf/nginx.conf,將配置文件使用的根設置爲root,目的就是將來要使用lua腳本的時候 ,直接可以加載在root下的lua腳本。

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

修改代碼如下:
在這裏插入圖片描述

3.4 測試訪問

(1) 修改nginx.conf配置文件

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

添加如下代碼
在這裏插入圖片描述
(2) 啓動OpenResty-Nginx服務

--啓動
/usr/local/openresty/nginx/sbin/nginx
--停止
/usr/local/openresty/nginx/sbin/nginx -s stop
--重啓
/usr/local/openresty/nginx/sbin/nginx -s reload
--檢驗nginx配置是否正確
/usr/local/openresty/nginx/sbin/nginx -t

(3) 瀏覽器訪問:
http://www.xiexun.top:9100/test
在這裏插入圖片描述
注意阿里雲服務器安全組需要開啓9100端口

4. 廣告緩存的載入與讀取

4.1 邏輯簡述

  • 定義請求名稱

  • 書寫Lua腳本,查詢Nginx緩存是否有數據,若無數據,依次查詢Redis與MySQL並對數據進行保存

  • 發起請求,獲取數據

4.2 功能實現

4.2.1 請求地址

http://www.xiexun.top:9100/read_advert?id=1

4.2.2 腳本編寫

在/root/lua目錄下創建update_advert.lua腳本

腳本如下:

ngx.header.content_type = "application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
--獲取本地緩存
local cache_ngx = ngx.shared.dis_cache;
--根據ID 獲取本地緩存數據
local advertCache = cache_ngx:get('advert_cache_' .. id);

if advertCache == "" or advertCache == nil then
    ngx.say("本地緩存中無數據");
    ngx.say("開始從Redis中獲取數據.....")
    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(10000)
    red:connect("39.105.162.100", 6379)
    local redisAdvert = red:get("advert_" .. id);

    if ngx.null==redisAdvert then
        ngx.say("Redis中無數據");
        ngx.say("開始從數據庫中獲取數據.....")
        local cjson = require("cjson");
        local mysql = require("resty.mysql");
        local db = mysql:new();
        db:set_timeout(10000)
        local props = {
            host = "39.105.162.100",
            port = 3306,
            database = "changgou_advert",
            user = "root",
            password = "root"
        }
        local res = db:connect(props);
        local select_sql = "select url,pic from tb_advert where status ='1' and category_id=" .. id .. " order by sort_order";
        res = db:query(select_sql);
        ngx.say("數據庫查詢成功");
        local responsejson = cjson.encode(res);
        red:set("advert_" .. id, responsejson);
        ngx.say(responsejson);
        db:close()
    else
        ngx.say("Redis查詢成功")
        cache_ngx:set('advert_cache_' .. id, redisAdvert, 10 * 60);
        ngx.say(redisAdvert)
    end
    red:close()
else
    ngx.say(advertCache)
end

4.2.3 Nginx配置

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

添加如下代碼

        location /read_advert {
                limit_req zone=advertRateLimit;
                content_by_lua_file  /root/lua/read_advert.lua;
        }

如下圖所示
在這裏插入圖片描述

4.3 測試

測試地址:http://www.xiexun.top:9100/read_advert?id=1
在這裏插入圖片描述

5. Nginx限流

一般情況下,首頁的併發量是比較大的。當用戶不停的刷新首頁時,即使是多級緩存,也會產生一定壓力。所以需要引入限流來進行保護

5.1 生活中限流對比

  • 水壩泄洪:通過閘口限制洪水流量(控制流量速度)

  • 辦理銀行業務:所有人先領號,各窗口叫號處理。每個窗口處理速度根據客戶具體業務而定,所有人排隊等待叫號即可。若快下班時,告知客戶明日再來(拒絕流量)

  • 火車站排隊買票安檢:通過排隊的方式依次放入。(緩存帶處理任務)

5.2 nginx的限流

(1) Nginx限流的方式:

  • 控制速率:limit_req_zone

  • 控制併發連接數:limit_conn_zone

(2) Nginx限流算法

令牌桶算法

  • 令牌以固定速率產生,並緩存到令牌桶中;

  • 令牌桶放滿時,多餘的令牌被丟棄;

  • 請求要消耗等比例的令牌才能被處理;

  • 令牌不夠時,請求被緩存。

漏桶算法

  • 水(請求)從上方倒入水桶,從水桶下方流出(被處理)

  • 來不及流出的水存在水桶中(緩衝),以固定速率流出

  • 水桶滿後水溢出(丟棄)

  • 這個算法的核心是:緩存請求、勻速處理、多餘的請求直接丟棄

相比漏桶算法,令牌桶算法不同之處在於它不但有一隻“桶”,還有個隊列,這個桶是用來存放令牌的,隊列纔是用來存放請求的。

從作用上來說,漏桶和令牌桶算法最明顯的區別就是是否允許突發流量(burst)的處理,漏桶算法能夠強行限制數據的實時傳輸(處理)速率,對突發流量不做額外處理;而令牌桶算法能夠在限制數據的平均傳輸速率的同時允許某種程度的突發傳輸。

Nginx按請求速率限速模塊使用的是漏桶算法,即能夠強行保證請求的實時處理速度不會超過設置的閾值。

5.2.1 控制速率

(1) limit_req_zone 參數配置講解

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
  • $binary_remote_addr:表示通過remote_addr這個標識來做限制,“binary_”的目的是縮寫內存佔用量,是限制同一客戶端IP地址

  • zone=one:10m:表示生成一個大小爲10M,名字爲one的內存區域,用來存儲訪問的頻次信息

  • rate=1r/s:表示允許相同標識的客戶端的訪問頻次,這裏限制的是每秒1次,還可以30r/m

limit_req zone=one burst=5 nodelay;
  • zone=one:表示設置使用哪個配置區域來做限制,與上面limit_req_zone 裏的name對應

  • burst=5:表示設置一個大小爲5的緩衝區,當有大量請求(爆發)過來時,超過了訪問頻次限制的請求可以先放到這個緩衝區內

  • nodelay:如果設置,超過訪問頻次而且緩衝區也滿了的時候就會直接返回503,如果沒有設置,則所有請求會等待排隊。

(2) 修改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;

    #限流設置
    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;
        }
    }
}

(3) 測試

重新加載配置文件

訪問頁面:http://www.xiexun.top:9100/read_content?id=1,連續刷新會直接報錯。
在這裏插入圖片描述
(4) 處理突發流量

上面例子限制 2r/s,如果有時正常流量突然增大,超出的請求將被拒絕,無法處理突發流量,可以結合 burst 參數使用來解決該問題。

設置**burst=4 **,若同時有4個請求到達,Nginx 會處理第一個請求,剩餘3個請求將放入隊列,然後每隔500ms從隊列中獲取一個請求進行處理。若請求數大於4,將拒絕處理多餘的請求,直接返回503.

不過,單獨使用 burst 參數並不實用。假設 burst=50 ,rate依然爲10r/s,排隊中的50個請求雖然每100ms會處理一個,但第50個請求卻需要等待 50 * 100ms即 5s,這麼長的處理時間自然難以接受。

因此,burst 往往結合 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;
        }
    }
}

5.2.2 控制併發量

(1) ngx_http_limit_conn_module 參數配置講解

這個模塊用來限制單個IP的請求數。並非所有的連接都被計數。只有在服務器處理了請求並且已經讀取了整個請求頭時,連接才被計數。

(2) 配置限制固定連接數

配置如下:

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;

    #根據IP地址來限制,存儲內存大小10M
    limit_conn_zone $binary_remote_addr zone=addr:1m;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;
        #所有以brand開始的請求,訪問本地changgou-service-goods微服務
        location /brand {
            limit_conn addr 2;
            proxy_pass http://192.168.211.1:18081;
        }

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

其中:

limit_conn_zone $binary_remote_addr zone=addr:10m  表示限制根據用戶的IP地址來顯示,設置存儲地址爲的內存大小10M

limit_conn addr 2  表示 同一個地址只允許連接2次。

(3) 限制每個客戶端IP與服務器的連接數,同時限制與虛擬服務器的連接總數

如下配置:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m; 
server {  
    listen       80;
    server_name  localhost;
    charset utf-8;
    location / {
        limit_conn perip 10;#單個客戶端ip與服務器的連接數.
        limit_conn perserver 100; #限制與服務器的總連接數
        root   html;
        index  index.html index.htm;
    }
}

6. Canal介紹

Canal是阿里巴巴旗下的一款開源項目,純Java開發。基於數據庫增量日誌解析,提供增量數據訂閱&消費,目前主要支持了MySQL(也支持mariaDB)

6.1 工作原理

6.1.1 MySQL主從複製實現

在這裏插入圖片描述

  • master將改變記錄到二進制日誌(binary log)中。這些記錄叫做二進制日誌事件,binary log events,可以通過show binlog events進行查看

  • slave將master的binary log events拷貝到它的中繼日誌(relay log)

  • slave重做中繼日誌中的事件,將改變反映它自己的數據

6.1.2 工作原理

在這裏插入圖片描述

  • canal模擬mysql slave的交互協議,僞裝自己爲mysql slave,向mysql master發送dump協議

  • mysql master收到dump請求,開始推送binary log給slave(也就是canal)

  • canal解析binary log對象(原始爲byte流)

6.2 安裝canal

6.2.1 下載鏡像

docker canal/canal-server:v1.1.4

6.2.2 啓動容器

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

6.2.3 配置instance.properties

vi canal-server/conf/example/instance.properties

在這裏插入圖片描述
詳細配置可參考Canal AdminGuide

6.2.4 重啓canal

docker update --restart=always canal

docker restart canal

6.3 開啓binlog模式

在使用canal時,需要開啓MySQL的binlog模式。同時要求MySQL的版本爲5.7及以下。

6.3.1 修改mysqld.cnf

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

添加如下配置:

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

6.3.2 創建賬號

使用root賬號創建用戶並授予權限

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

6.3.3 重啓MySQL

docker restart mysql

7. 廣告同步實現

當用戶執行數據庫的操作的時候,binlog日誌會被canal捕獲到,並解析出數據。然後將解析出來的數據進行同步到Redis中即可。

7.1 canal微服務

7.1.1 搭建canal微服務

thankson-springcloud-provider下創建thankson-springcloud-canal工程,並引入相關配置。

項目結構如下
在這裏插入圖片描述

7.1.2 pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>thankson-springcloud-provider</artifactId>
        <groupId>com.thankson.springcloud</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>thankson-springcloud-canal</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.thankson.springcloud</groupId>
            <artifactId>thankson-springcloud-mall-api-advert</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
</project>

7.1.3 application.yml配置

server:
  port: 9100
spring:
  application:
    name: changgou-canal
  redis:
    host: www.xiexun.top
    port: 6379
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true

#canal配置
canal:
  client:
    instances:
      example:
        host: www.xiexun.top
        port: 11111
        clusterEnabled: false
        retryCount: 5

7.1.4 創建監聽器

在包com.thankson.canal.listener下創建CanalDataEventListener類,實現對錶增刪改操作的監聽,代碼如下:

@CanalEventListener
public class CanalDataEventListener {

    @Autowired
    private AdvertFeign advertFeign;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @ListenPoint(destination = "example",
            schema = "changgou_advert",
            table = {"tb_advert", "tb_advert_category"},
            eventType = {
                    CanalEntry.EventType.UPDATE,
                    CanalEntry.EventType.DELETE,
                    CanalEntry.EventType.INSERT})
    public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        //1.獲取列名 爲category_id的值
        String categoryId = getColumnValue(eventType, rowData);
        //2.調用feign 獲取該分類下的所有的廣告集合
        Result<List<Advert>> categoryResult = advertFeign.findByCategory(Integer.valueOf(categoryId));
        List<Advert> data = categoryResult.getData();
        for (Advert datum : data) {
            System.out.println(datum);
        }
        //3.使用redisTemplate存儲到redis中
        stringRedisTemplate.boundValueOps("advert_" + categoryId).set(JSON.toJSONString(data));
    }

    private String getColumnValue(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        String categoryId = "";
        //判斷 如果是刪除  則獲取BeforeColumnsList
        if (eventType == CanalEntry.EventType.DELETE) {
            for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
                if (column.getName().equalsIgnoreCase("category_id")) {
                    categoryId = column.getValue();
                    return categoryId;
                }
            }
        } else {
            //判斷 如果是添加 或者是更新 獲取AfterColumnsList
            for (CanalEntry.Column column : rowData.getAfterColumnsList()) {
                if (column.getName().equalsIgnoreCase("category_id")) {
                    categoryId = column.getValue();
                    return categoryId;
                }
            }
        }
        return categoryId;
    }
}

7.1.5 啓動類創建

com.thankson.canal包下創建啓動類CanalApplication,代碼如下:

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableCanalClient
@EnableFeignClients(basePackages = {"com.thankson.mall.advert.feign"})
public class CanalApplication {
    public static void main(String[] args) {
        SpringApplication.run(CanalApplication.class,args);
    }
}

7.2 advert微服務

7.2.1 搭建advert微服務

  • thankson-springcloud-mall-service模塊下創建thankson-springcloud-mall-service-advert微服務

  • thankson-springcloud-mall-api模塊下創建thankson-springcloud-mall-api-advert微服務

項目結構如下
在這裏插入圖片描述

7.2.2 pom.xml配置

  • api-advert
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>thankson-springcloud-mall-api</artifactId>
        <groupId>com.thankson.springcloud</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>thankson-springcloud-mall-api-advert</artifactId>
</project>
  • service-advert
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>thankson-springcloud-mall-service</artifactId>
        <groupId>com.thankson.springcloud</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>thankson-springcloud-mall-service-advert</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.thankson.springcloud</groupId>
            <artifactId>thankson-springcloud-mall-api-advert</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

</project>

7.2.3 application.yml配置

  • service-advert
server:
  port: 10102
spring:
  application:
    name: mall-advert
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://www.xiexun.top:3306/changgou_advert?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: canal
    password: canal
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true

mybatis:
  configuration:
    map-underscore-to-camel-case: true  #開啓駝峯功能
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled設置爲false,則請求超時交給ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE

7.2.4 業務代碼

因爲採用腳本生成代碼,所以不進行過多的介紹。具體代碼可以參考項目源碼。

7.2.5 啓動類創建

@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {"com.thankson.mall.advert.dao"})
public class AdvertApplication {

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

7.2.6 測試

1、啓動eureka、canal、advert微服務,效果如下
在這裏插入圖片描述
2、查看Redis數據
在這裏插入圖片描述
3、修改表tb_advert中廣告分類爲1的數據後查看redis
在這裏插入圖片描述
由上圖可見 代碼已正確執行

8. 結束語

至此,廣告同步功能已經完成。至於其他功能在後續使用時會進行開發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章