mongoDB的讀寫分離


一、讀寫分離相關的理論

  1.1、ReadPreference讀偏好

  1.2臟數據

  1.3復制集的缺點

  1.4讀隔離 Read Concern

  1.5寫確認 Write Concern

二、springboot中實現讀寫分離

  2.1 MongoDB連接池指定讀模式

  2.2、在代碼層面動態切換

一、讀寫分離相關的理論

1.1、ReadPreference讀偏好

在副本集Replica Set中才涉及到ReadPreference的設置,默認情況下,讀寫都是分發都Primary節點執行,但是對於寫少讀多的情況,我們希望進行讀寫分離來分攤壓力,所以希望使用Secondary節點來進行讀取,Primary只承擔寫的責任(實際上寫只能分發到Primary節點,不可修改)。

MongoDB有5種ReadPreference模式:

  • primary: 主節點,默認模式,讀操作只在主節點,如果主節點不可用,報錯或者拋出異常。

  • primaryPreferred:首選主節點,大多情況下讀操作在主節點,如果主節點不可用,如故障轉移,讀操作在從節點。

  • secondary:從節點,讀操作只在從節點, 如果從節點不可用,報錯或者拋出異常。

  • secondaryPreferred:首選從節點,大多情況下讀操作在從節點,特殊情況(如單主節點架構)讀操作在主節點。

  • nearest:最鄰近節點,讀操作在最鄰近的成員,可能是主節點或者從節點。

1.2臟數據

其實說的就是 MongoDB 的數據持久化,在一個數據寫到 journal 並 flush 到磁盤上之前,數據都是臟的,而在復制集內,數據會通過 Oplog 傳播到其它節點上,然后重復寫入的步驟。

假如這個過程中,主節點掛掉了,之前的某一個 Secondary 提升成為了 Primary,由於數據沒有寫到大部分節點上,於是新的 Primary 看不到之前的應該寫入的新數據,即使這時候舊的 Primary 回來了,它也只能是 Secondary,它之前的那些新數據就會丟失,從而導致數據的回滾。

1.3復制集的缺點

說了優點之后,再說說它的缺點,畢竟 CAP 原理還是統治着分布式領域。在 CAP 原理中,C 表示一致性,A 表示一致性,P 表示分區容忍性。

MongoDB 的默認復制集配置是顯然的 CP,因為 ReadPreference 默認為 Primary;如果換成 Secondary 或者 SecondaryPreferred,就相當於 AP 了,C 用了業界默認的最終一致性,因為它的復制是基於 Oplog 的異步方案。

但是,AP 方案容易導致的問題有復制延遲導致的:

注意:這些的例子只是隨便舉例,不一定會是真實情況。

  1. 寫后讀,或者說是讀己寫問題:即從 Primary 寫入數據后,然后馬上從 Secondary 讀,這時候由於延遲問題而有可能在 Secondary 讀不到最新數據,於是我剛發了個微博,刷新了下反而消失了,過一會兒又出現了;
  2. 單調讀問題,或者說是時光倒流問題:這時候由於多次從不同的 Secondary 讀取數據,比如微博的評論下面,如果兩次讀到的數據不一致后,容易導致先看到了回復,刷新后卻消失了,再過一會兒又出現了;
  3. 因果讀寫不一致問題:與上面的微博例子相似,即出現在一個微博下面,評論的回復比評論先到達的現象;

解決的辦法顯然是有的,MongoDB 分別從讀與寫提供了解決方案,讓你能夠調整配置來取舍復制集中的 C 與 A。

1.4讀隔離 Read Concern

目前一共有五種讀隔離的設置:

  1. local:不保證數據都被寫入了大部分節點,我們在使用的時候基本默認的選項;
  2. available:3.6 版本引入,與 因果一致性會話 有關,也是不保證數據都被寫入了大部分節點,暫時還沒用過;
  3. majority:保證數據都被寫入了大部分節點,但是必須使用 WiredTiger 存儲引擎;
  4. linearizable:這個也沒有用過,意思也不是很清楚,文檔大致意思理解為對文檔所有的讀寫都是順序,或者說線性執行的,會導致花費時間超過 majority,建議與 maxTimeMS 一起食用;
  5. snapshot:4.0 版本引入,與多文檔的事務有關,也是沒用過;

所以除了 local 與 majority,我都不能保證敘述的准確性,畢竟與實際用還是有區別的。但是基本上可以了解到:讀隔離的效果是需要用時間去交換的,或者說降低可用性去交換的。

另外特別提一下這句文檔中的話:

Regardless of the read concern level, the most recent data on a node may not reflect the most recent version of the data in the system.
不管 Read concern 的具體配置,節點上最新的數據,不一定意味着它也是系統中最新的數據。

因為不管 Read concern 如何配置,它始終是從單個節點讀的,這個設計的初衷只能保證不讀到臟數據。

1.5寫確認 Write Concern

{ w: <value>, j: <boolean>, wtimeout: <number> }

對於 w 參數,則有三種,表示寫入后得到多少個 Secondary 的確認后再返回:這三個參數,在進行寫操作的時候非常有用,常見的設置便是將 j 設置為 true,表示等數據已經寫入了磁盤上的 journal 后再返回,這時候即便數據庫掛掉,也是能從 journal 中恢復的,注意這不是 oplog 它是高層次的日志,而 journal 是低層次的日志,是可以用來故障恢復后重建當前節點數據的日志5

  1. 數字:那就是確切的個數了;
  2. majority:自動幫你計算 n/2 + 1;
  3. tag set,標簽組:即制定哪幾個 tag 的 Secondary;

最后一個 wtimeout,則是在制定 w 參數的時候,推薦一並設置,防止超時,畢竟這種確認是犧牲性能的,很可能導致超時。

看到這里,大致可以得出結論,MongoDB 將讀隔離與寫確認交給客戶端去取舍,一定程度上解決了復制延遲導致的業務問題,而本質上,這種解決方案的原理就在於用事務6

------------------------------------------------------------------------------------------------------

readConcern 的是為了在於解決臟讀問題,用戶從 MongoDB 的 primary 上讀的數據並沒有同步到大多數節點,然后 primary 宕機恢復, primary節點會將未同步到大多數節點的數據回滾,導致用戶讀到了臟數據。

當指定 readConcern 級別為majority ,能保證用戶讀到的數據已經寫入到大多數節點,而這樣的數據肯定不會發生回滾,避免了臟讀的問題。

需要注意的是,readConcern 只是保證讀到的數據不會發生回滾,但並不能保證讀到的數據最新。

參考官網:

誤區: majority並非從多節點讀取,依然是單節點讀取。

readConcern 原理

snapshot 0,1,2,3......N的狀態是committed/uncommitted

同步到大多數節點時,對應的snapshot會標記為commmited。

用戶讀取:讀最新的 commited 狀態的 snapshot,這樣就保證了讀到的數據是已經同步到大多數節點。

secondary節點在自身oplog發生變化會同步信息到primary。

primary節點統計超過半數的節點的同步信息就修改該snapshot為uncommitted->commited。

同時secondary拉取oplog的同時從primary節點得到最新一條已經同步到大多數節點的oplog,更新自身的 snapshot 狀態。

 ------------------------------------------------------------------------------------------------------

-----------------------------------------------------

mongodb 的讀寫一致性由 WriteConcern 和 ReadConcern 兩個參數保證。

writeConcern

readConcern

兩者組合可以得到不同的一致性等級。

指定 writeConcern:majority 可以保證寫入數據不丟失,不會因選舉新主節點而被回滾掉。

readConcern:majority + writeConcern:majority 可以保證強一致性的讀

readConcern:local + writeConcern:majority 可以保證最終一致性的讀

mongodb 對configServer全部指定writeConcern:majority 的寫入方式,因此元數據可以保證不丟失。

對 configServer 的讀指定了 ReadPreference:PrimaryOnly 的方式,在 CAP 中舍棄了A與P得到了元數據的強一致性讀。

---------------------------------------------------

二、springboot中的MongoDB讀寫分離實現

2.1 MongoDB連接池指定讀模式

再重申下在副本集Replica Set中才涉及到ReadPreference的設置才有意義。

連接池的配置中主要注意幾個參數:

// 客戶端配置(連接數、副本集群驗證)
MongoClientOptions.Builder builder = new MongoClientOptions.Builder();
//...
builder.readPreference(ReadPreference.secondaryPreferred());
builder.readConcern(ReadConcern.MAJORITY);
//...
MongoClientOptions mongoClientOptions = builder.build();

 

xml示例(沒有測試過):

<!-- mongodb配置 -->
<mongo:mongo id="mongo"  host="${mongo.host}" port="${mongo.port}" write-concern="NORMAL" >
    <mongo:options 
        connections-per-host="${mongo.connectionsPerHost}"
        threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
        connect-timeout="${mongo.connectTimeout}" 
        max-wait-time="${mongo.maxWaitTime}"
        auto-connect-retry="${mongo.autoConnectRetry}" 
        socket-keep-alive="${mongo.socketKeepAlive}"
        socket-timeout="${mongo.socketTimeout}" 
        slave-ok="${mongo.slaveOk}"
        write-number="1" 
        write-timeout="0" 
        write-fsync="false"
    />
</mongo:mongo>

<!-- mongo的工廠,通過它來取得mongo實例,dbname為mongodb的數據庫名,沒有的話會自動創建 -->
<mongo:db-factory id="mongoDbFactory" dbname="uba" mongo-ref="mongo" />

<!-- 讀寫分離級別配置  -->
<!-- 首選主節點,大多情況下讀操作在主節點,如果主節點不可用,如故障轉移,讀操作在從節點。 -->
<bean id="primaryPreferredReadPreference" class="com.mongodb.TaggableReadPreference.PrimaryPreferredReadPreference" />
<!-- 最鄰近節點,讀操作在最鄰近的成員,可能是主節點或者從節點。  -->
<bean id="nearestReadPreference" class="com.mongodb.TaggableReadPreference.NearestReadPreference" />
<!-- 從節點,讀操作只在從節點, 如果從節點不可用,報錯或者拋出異常。存在的問題是secondary節點的數據會比primary節點數據舊。  -->
<bean id="secondaryReadPreference" class="com.mongodb.TaggableReadPreference.SecondaryReadPreference" />
<!-- 優先從secondary節點進行讀取操作,secondary節點不可用時從主節點讀取數據  -->
<bean id="secondaryPreferredReadPreference" class="com.mongodb.TaggableReadPreference.SecondaryPreferredReadPreference" />
<!-- mongodb的主要操作對象,所有對mongodb的增刪改查的操作都是通過它完成 -->
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
    <property name="readPreference" ref="primaryPreferredReadPreference" />
</bean>

 

對應的配置(在建立mongoDB的連接時,指定ReadPreference)

請仔細看好 spring.data.mongodb.uri 的配置,他的格式如下,可以參考mongodb連接

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

例子:

# MongoDB URI配置 重要,添加了用戶名和密碼驗證
spring.data.mongodb.uri=mongodb://zhuyu:zhuyu@192.168.68.138:27017,192.168.68.137:27017,192.168.68.139:27017/ai?slaveOk=true&replicaSet=zypcy&write=1&readPreference=secondaryPreferred&connectTimeoutMS=300000

#每個主機的連接數
spring.data.mongodb.connections-per-host=50
#線程隊列數,它以上面connectionsPerHost值相乘的結果就是線程隊列最大值
spring.data.mongodb.threads-allowed-to-block-for-connection-multiplier=50
spring.data.mongodb.connect-timeout=5000
spring.data.mongodb.socket-timeout=3000
spring.data.mongodb.max-wait-time=1500
#控制是否在一個連接時,系統會自動重試
spring.data.mongodb.auto-connect-retry=true
spring.data.mongodb.socket-keep-alive=true

驗證讀寫分離是否生效:

創建一個 Rest風格的 IndexController ,提供:添加與查詢接口,訪問這2個接口,看控制台輸出,是否查操作自動分配到從庫,寫操作分配到主庫

@RequestMapping("/index")
@RestController
public class IndexController {

    @Autowired private MongoTemplate mongoTemplate;

    @RequestMapping("/getList")
    public List<TestModel> getList(){
        List<TestModel> list = mongoTemplate.findAll(TestModel.class,"test");
        return list;
    }

    @RequestMapping("/add")
    public String add(){
        TestModel model = new TestModel("zhuyu" + System.currentTimeMillis());
        mongoTemplate.insert(model , "test");
        return "success";
    }
}

2.2、在代碼層面動態切換

通過mongoTemplate對象動態指定 mongoTemplate.setReadPreference(readPreference);

例如,在同一個應用中定義2個mongoTemplate對象,一個設置從primary讀,一個設置從Secondary讀,根據應用場景選擇不同的mongoTemplate

 

三、MongoDB讀寫分離驗證

 

調整優先級的方法1:

改優先級,登錄指定shard主節點,mongo ip:22001 -u root --password=xxxx --authenticationDatabase admin
1. 先刪除節點,rs.remove("ip1:22002")
2. 再添加回節點,指定優先級
rs.add({
_id: 0,
host: "ip1:22002",
priority: 5
})
3. 執行rs.reconfig()使配置生效
rs.add({
_id: 0,
host: "ip1:22002",
priority: 5
})

調整優先級的方法2:

 

 分別進行讀/寫的場景壓測,看服務器資源的消耗情況就知道讀寫分離是否生效了。

轉自:

https://blog.csdn.net/zhuyu19911016520/article/details/82998162?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3

https://blog.xizhibei.me/2019/05/05/mongodb-replica-set/

https://blog.csdn.net/cxu123321/article/details/108897067

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM