Mongodb 讀寫分離

最近有用到 mongo 實作讀寫分離,紀錄一下

mongo 的讀寫分離是用 replica set 實作,裡面的 node 分為 primary-secondary,不同於 mysql 的 master-slave。

每個 replica set 只能有一個 primary node,不敷使用時其他 secondary node 會進行投票決定誰才是下一個 primary,因此要架的話最好要使用單數個 node

node 差別

primary node

  • 接收所有 write 操作,搭配 write concern 設定可以確保各個 node 有無正確被寫入
  • 接收 read / write 操作

secondary node

  • 接收來自 primary 的 oplog 以進行 data replication
  • 接收 read 操作

arbiter node

  • 只出嘴不出力,跟大多數的一樣(只有投票功能的節點)

設定方法

這裡用 keyfile 的方式,省得建帳號

mongod.conf

systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log
storage:
  dbPath: /var/lib/mongo
  journal:
    enabled: true
net:
  port: 27017
  bindIp: 0.0.0.0
security:
  authorization: enabled
  keyFile: /etc/mongo.d/replica-keyfile ;同把 key 要 co 到 secondary 上
replication:
  replSetName: "rs0"

基本上 secondary 的設定是一樣的,可以搭配 mongo shell 設定修改進入後呈現的樣子讓操作時更清楚當前位置

readPreference

設定讀寫分離時有個需注意的東西叫readPreference,共有五種類型,參考下表

名稱 說明
primary 預設,所有讀操作都只會從 primary 進行,如果該 node 死掉則出現 error
primaryPreferred primary 死掉時會從 secondary 找,如有設定 tag 的話會從符合的 tag 中找最近的
secondary 只從 secondary 讀,secondary 全死就出錯
secondaryPreferred 如果只有兩個 node,且其中一個是 primary 的話就從 primary 讀
nearest 依 latency 選擇回應最快的節點接收讀操作

本地架設範例

  1. docker-compose.yml
version: '3.7'
services:
  mongo_primary:
    restart: always
    image: mongo:4
    volumes:
      - ./data:/data/db
    ports:
      - '27017:27017'
    entrypoint:
      ['mongod', '--port', '27017', '--bind_ip_all', '--replSet', 'rs0']
    container_name: local_persona_mongo_primary
  mongo_rep1:
    image: mongo:4
    restart: always
    ports:
      - '27027:27027'
    entrypoint:
      ['mongod', '--port', '27027', '--bind_ip_all', '--replSet', 'rs0']
    container_name: local_persona_mongo_rep1
  mongo_rep2:
    image: mongo:4
    restart: always
    ports:
      - '27037:27037'
    entrypoint:
      ['mongod', '--port', '27037', '--bind_ip_all', '--replSet', 'rs0']
    container_name: local_persona_mongo_rep2
  1. 進去啟動 replica
docker exec -ti local_persona_mongo_primary sh
# in container
mongo --eval 'rs.initiate( { _id : "rs0",members: [{ _id: 0, host: "mongo_primary:27017" },{ _id: 1, host: "mongo_rep1:27027" },{ _id: 2, host: "mongo_rep2:27037" }]})'
  1. 設定本地 hosts 讓本地的 request 可以連到機器(也可以把開發環境掛在跟docker-compose一起就不用這步)
sudo echo '127.0.0.1 mongo_primary mongo_rep1 mongo_rep2' >> /etc/hosts

接著就可以看你用的lib決定怎麼設定偏好,以 node 的 mongoose 舉例如下

model.aggregate().read('sp') // secondaryPreferced
model.findOne({cond},{ readPreference: 'secondaryPreferred' })

Reference

cmd + /