1. 程式人生 > 資料庫 >使用Haproxy+lua代理Mongodb副本集

使用Haproxy+lua代理Mongodb副本集

在這裡插入圖片描述
一般情況下,使用mongo客戶端,或者其他語言的mongo客戶端驅動程式連線mongodb副本集的時候,只需要指定副本集名稱,就可以實現當mongodb副本集主備切換時的高可用目標。

但是總有一些特殊的場合,連線到副本集的客戶端與副本集所在的網路是隔離的,只能通過副本集所在網路的代理訪問副本集,比如說,mongodb副本集被部署到k8s上,當k8s叢集外的客戶端想訪問副本集時,只能通過代理進行訪問,如通過haproxy訪問副本集。

那麼問題來了,客戶端只知道代理的ip地址或者url,當副本集主備切換時,客戶端通過副本集名稱是不會連線到副本集的主例項的,這裡提供一個haproxy+lua代理mongo副本集的方式可以解決這個問題。

關於haproxy和lua的使用方法和說明,請參考如下連結:
https://www.arpalert.org/haproxy-lua.html

首先,建立一個簡易的mongodb副本集(同一主機埠不同,沒有使用者名稱密碼等安全設定):

# 通過以下shell建立一個簡易的mongodb副本集
​
# 殺掉已存在的mongo例項
ps -ef | grep mongod | grep -v grep | awk '{print $2}' | xargs kill -9 ;
​
# 清理並建立目錄供副本集使用
rm -rf /data/47017/* &&
rm -rf /data/47018/* &&
rm -rf /data/47019/* &&
rm -f rm -rf /data/47017log &&
rm -f rm -rf /data/47018log &&
rm -f rm -rf /data/47019log &&
mkdir -p /data/47017/ &&
mkdir -p /data/47018/ &&
mkdir -p /data/47019/ &&
​
# 獲取本機IP地址
localip=`ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6| grep -v 172|awk '{print $2}'|tr -d "addr:"`
echo "ETH0 IP is ${localip}"
​
# 下面幾行註釋掉的命令是建立mongodb的使用者名稱和密碼,暫時不要
#/home/mongodb/bin/mongod --fork --bind_ip 0.0.0.0 --port 47017 -dbpath "/data/47017" -logpath /data/47017log &&
#/home/mongodb/bin/mongo --host ${localip} --port 47017 --eval "db.getSiblingDB('admin').createUser({user: 'aa',pwd: 'aa',roles: [ { role: 'root',db: 'admin' }]})" &&
#ps -ef | grep mongod | grep -v grep | awk '{print $2}' | xargs kill && sleep 5 &&
​
# 如mongodb的主目錄是/home/mongodb的話,直接使用如下命令建立副本集即可
/home/mongodb/bin/mongod --replSet myrep --fork --bind_ip 0.0.0.0 --port 47017 -dbpath "/data/47017" -logpath /data/47017log &&
/home/mongodb/bin/mongod --replSet myrep --fork --bind_ip 0.0.0.0 --port 47018 -dbpath "/data/47018" -logpath /data/47018log &&
/home/mongodb/bin/mongod --replSet myrep --fork --bind_ip 0.0.0.0 --port 47019 -dbpath "/data/47019" -logpath /data/47019log &&
/home/mongodb/bin/mongo --host ${localip} --port 47017 --eval "db.getSiblingDB('admin'); rs.initiate({'_id':'myrep','members':[{'_id':0,'host':'${localip}:47017',priority:3},{'_id':1,'host':'${localip}:47018',priority:2},{'_id':2,'host':'${localip}:47019',priority:0,slaveDelay:86400}]})"

然後,就可以著手安裝haproxy和lua的相關環境了,由於我們需要探測mongodb副本集主例項位於哪個節點,因此還需要安裝mongo-c-driver,這個過程比較費時間,可以直接使用docker容器簡化這一過程,參考:
https://hub.docker.com/r/pengtaoman/haproxy2.1.0-lua-mongodriver
https://github.com/pengtaoman/haproxy2.1.0-lua-mongodriver

github上有dockerfile原始碼,可以檢視環境安裝的過程,以及配置檔案的示例,這裡簡要說明如下:
haproxy.cfg

global
  log 127.0.0.1 local0 debug
  log 127.0.0.1 local1 notice
  stats timeout 30s
  user haproxy
  group haproxy
  # 這裡載入我們做成的lua檔案
  lua-load /etc/haproxy/conf/mongo-backend.lua
  daemon
​
defaults
  log global
  mode tcp
  option tcplog
  option dontlognull
  timeout connect 5000
  timeout client 50000
  timeout server 50000
# 通過frontend設定的埠訪問副本集
frontend mongo_front
  bind *:37017
  use_backend %[lua.backend_select]
# m1 m2 m3是副本集的三個例項,
# 我們將通過lua程式選擇使用哪一個例項,從而實現主備切換時的高可用
# 10.211.55.21即為上一步建立簡易副本集的主機的ip
backend m1
  balance roundrobin
  log 127.0.0.1 local0
  server mongorep0 10.211.55.21:47017 check
​
backend m2
  balance roundrobin
  log 127.0.0.1 local0
  server mongorep1 10.211.55.21:47018 check
​
backend m3
  balance roundrobin
  log 127.0.0.1 local0
  server mongorep2 10.211.55.21:47019 check
# 開啟haproxy自帶監控服務
listen admin_stats
  mode http
  bind 0.0.0.0:8888
  stats uri /stats
  stats realm Global\ statistics

mongo-backend.lua


# 向haproxy註冊一個fetch
core.register_fetches("backend_select",function(txn)
    for k,v in pairs(core.backends) do
      local servs = v.servers
      for sk,sv in pairs(servs) do
        core.Debug(sk)
        local svAddr = sv.get_addr(sv)
        local isMaster = checkMongo(svAddr)
        if (isMaster)
        then
      core.Debug("###### Now primary instance is:"..svAddr)
          return k
        end
      end
    end
  end)
​
  checkMongo = function(mongosvr)
    core.Debug("###### checkMongo mongo address ::"..mongosvr)
    local mongo = require 'mongo'
    local client = mongo.Client('mongodb://'..mongosvr)
    local isp = client:command('admin','{ "isMaster": "1" }')
    local bson = mongo.BSON{}
    pcall(function()
        bson:concat(isp)
      end)
    local ispri=bson:find('ismaster')
    if unexpected_condition then error() end
    return ispri

將haproxy.cfg和mongo-backend.lua放到自己指定的目錄下如/home/cfg下,就可以啟動容器了,如下:

docker run -d -p 37017:37017 -v /home/cfg:/etc/haproxy/conf pengtaoman/haproxy2.1.0-lua-mongodriver:0.0.1

通過mongo客戶端去訪問代理:

docker run -it --rm mongo:4.0.14 mongo --host 10.211.55.2 --port 37017

使用docker logs可以檢視我們在lua檔案中列印的日誌:

docker logs a1ced56a7444
[NOTICE] 029/131117 (1) : New worker #1 (7) forked
[debug] 029/131228 (7) : mongorep1
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47018
[debug] 029/131228 (7) : mongorep0
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47017
[debug] 029/131228 (7) : ###### Now primary instance is:10.211.55.21:47017

可以看到當前primary例項的埠是47017,我們把47017的例項停止後看看發生了什麼:

mongo --port 47017
​
use admin
db.shutdownServer();

再次使用mongo客戶端連線代理後,檢視docker logs如下:

docker logs a1ced56a7444
[NOTICE] 029/131117 (1) : New worker #1 (7) forked
[debug] 029/131228 (7) : mongorep1
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47018
[debug] 029/131228 (7) : mongorep0
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47017
[debug] 029/131228 (7) : ###### Now primary instance is:10.211.55.21:47017
[WARNING] 029/132201 (7) : Server m1/mongorep0 is DOWN,reason: Layer4 connection problem,info: "Connection refused",check duration: 101ms. 0 active and 0 backup servers left. 0 sessions active,0 requeued,0 remaining in queue.
[ALERT] 029/132201 (7) : backend 'm1' has no server available!
[debug] 029/132224 (7) : mongorep1
[debug] 029/132224 (7) : ###### checkMongo mongo address ::10.211.55.21:47018
[debug] 029/132224 (7) : ###### Now primary instance is:10.211.55.21:47018

副本集的primary節點已經切換到埠47018上了,則配置成功。
在這裡插入圖片描述