realm服務器,android開發realm多線程操作,數據庫的設計:深入理解 Realm 的多線程處理機制

 2023-10-12 阅读 21 评论 0

摘要:你已經閱讀過 Realm 關于線程的基礎知識。你已經知道了在處理多線程的時候你不需要關心太多東西了,因為強大的 Realm 會幫你處理好這些,但是你還是很想知道更多細節……你想知道在 Realm 的引擎蓋下它到底是怎么工作的。你想學習些相關的理論、機制和背后的原因

你已經閱讀過 Realm 關于線程的基礎知識。你已經知道了在處理多線程的時候你不需要關心太多東西了,因為強大的 Realm 會幫你處理好這些,但是你還是很想知道更多細節……

你想知道在 Realm 的引擎蓋下它到底是怎么工作的。你想學習些相關的理論、機制和背后的原因。好吧,你來到正確的地方了。 我們馬上會講到相關的所有有趣的細節。在這篇博文里,我們會解釋 Realm 是如何還有為什么是這樣構建的,以及它重要的原因。

讓我們開始吧:

“復雜性是你的敵人。任何傻瓜都可以做出復雜的東西。簡化才是最困難的。”

Richard Branson 先生

realm服務器?這句名言非常重要,它展示了我們全力想傳達的福音。我們處理好了許多非常復雜的任務,然后讓開發者感到容易起來 - 線程,并發,數據一致性,還有更多,所有這一切想做好都是非常困難的。我們不是怪人,沒有成千上百次的嘗試和錯誤,我們也想不出解決并發的方法,那時我們常常也犯下非常低級的錯誤。Realm 的目標就是要為你解決這些問題。

Realm 的基石

Realm 是一個 MVCC 數據庫 ,開始是用 C++ 編寫的。MVCC 指的是多版本并發控制。

這沒有它聽起來那么復雜,相信我們。先停一下,你馬上就會豁然開朗的。 💡

MVCC 解決了一個重要的并發問題:在所有的數據庫中都有這樣的時候,當有人正在寫數據庫的時候有人又想讀取數據庫了(例如,不同的線程可以同時讀取或者寫入同一個數據庫)。這會導致數據的不一致性 - 可能當你讀取記錄的時候一個寫操作才部分結束。如果數據庫允許這種事情發生,你就會得到和最終數據庫里的數據不一致的數據。

這太糟糕了。

Java thread,這個時候,你的視圖中的數據和你的數據庫里的數據是不一樣的。哎呀,數據不一致了,而且不可靠。

你希望你的數據庫是 ACID 的:

原子性

一致性

隔離性

持久性

java多線程并發入庫文本文件,有很多的辦法可以解決讀、寫并發的問題,最常見的就是給數據庫加鎖。在之前的情況下,我們在寫數據的時候就會加上一個鎖。在寫操作完成之前,所有的讀操作都會被阻塞。這就是眾所周知的讀-寫鎖。這常常都會很慢。

這是 Realm 的 MVCC 設計決定能大顯身手的地方。

Realm 是一個 MVCC 數據庫

類似 Realm 的 MVCC 的數據庫采用了另外的一個方法:每一個連接的線程都會有數據在一個特定時刻的快照。

這到底意味著什么?

MVCC 在設計上采用了和 Git 一樣的源文件管理算法。你可以把 Realm 的內部想象成一個 Git,它也有分支和原子化的提交操作。這意味著你可能工作在許多分支上(數據庫的版本),但是你卻沒有一個完整的數據拷貝。Realm 和真正的 MVCC 數據庫還是有些不同的。一個像 Git 的真正的 MVCC 數據庫,你可以有成為版本樹上 HEAD 的多個候選者。而 Realm 在某個時刻只有一個寫操作,而且總是操作最新的版本 - 它不可以在老的版本上工作。

android 多線程,更進一步,Realm 更像一個龐大的樹形數據結構(準確的說是一個 B 樹),任何時候,你都有最上層的節點,如下 R 節點(和 Git 的 HEAD 提交類似)。

c77f795db15a9d0862383e4bafdda2a4.png

一旦你要提交改變,copy-on-write 才會發生。Copy-on-write 意味著你重新創建了樹的另一個分支,然后在不改變現有數據的情況下完成寫操作。

Receive news and updates from Realm straight to your inbox

訂閱

Comments

771c5d54c13114e97f934773a09bf11f.png

采用這種方法,如果在寫事務過程中發生了錯誤的話,原始數據是不受影響的,頂指針依舊指向沒有被損壞的數據,因為你是在別處做的寫操作。Realm 采用了兩階段提交來驗證寫操作,Realm 會驗證所有寫到磁盤中的內容,這樣數據才是安全的。只有在這個時候,Realm 的指針才會移動而且說,“好的,這是新的官方的版本。” 這意味著在一次寫事件中最壞的情況是你僅僅失去你更新的數據,而不是整個 Realm 數據庫。

多線程并發訪問數據庫、Realm 對象和對象關系

另一個關于 Realm 有趣的事情是對象間的關系是本地引用,而且因為 Realm 采用了 zero-copy 架構,這樣幾乎就沒有內存開銷。這是因為每一個 Realm 對象直接通過一個本地 long 指針和底層數據庫對應,這個指針是數據庫中數據的鉤子。

Realm 避免了大部分不必要的緩慢的位交換和內存拷貝,這些在傳統數據庫訪問技術中是必要的。

為什么這很重要?

原因就是這樣最簡單。沒有必要為獲得引用的對象而作額外的工作。引用的對象是第一等的公民。這對性能影響很大:不需要進行額外的查詢或者開銷很大的連接操作。

而且,所有的移動設備都是內存受限的。Realm 內存消耗小,這會幫助你的應用避免內存不夠的情況和其他內存受限的問題。

realmeq。解釋零拷貝,和為什么它會這么快

Realm 采用了 零拷貝 架構。為了理解零拷貝的威力和它的重要性,讓我們快速回顧一下傳統數據庫的 ORM(關系型對象映射)中數據是如何獲取的。

從 ORM、Core Data 中獲取對象的傳統方法

大部分的時候,你都把數據存在磁盤上的數據庫文件中。開發者發起一個從持久化機制(比如 ORM 或者 Core Data)中獲取數據的請求,數據格式會是和本地平臺密切相關的(比如 安卓或者蘋果)。這個時候,持久化機制會把請求轉換成一系列的 SQL 語句,創建一個數據庫連接(如果沒有創建的話),發送到磁盤上,執行查詢,讀取命中查詢的每一行的數據,然后存到內存里(這里有內存消耗)。之后你需要把數據序列化成可在內存里面存儲的格式,這意味著比特對齊,這樣 CPU 才能處理它們。最后,數據需要轉換成語言層面的類型,然后它會以對象的形式返回,這樣平臺才能用(POJO, NSManagedObject 等等)來處理它。如果你在你的持續化機制中有子引用或者列表引用的話,這個過程會更復雜。這個過程會一遍一遍的執行(取決于你的持續化機制和配置)。如果你使用自產自銷的機制,情況也大致相同。

正如你能了解的那樣,有許多事情需要做 是為了把數據變成你的應用中能夠使用的數據結構。

Realm 對象獲取

多線程開發?Realm 的方法不一樣。這就是我們零拷貝架構起作用的地方。

Realm 跳過了整個拷貝過程,因為數據庫文件是 memory-mapped。Realm 在訪問文件偏移的時候就好像文件已經在內存中一樣,實際上不是 - 是虛擬內存。這是個 Realm 核心文件格式的重要設計決定。它允許文件能在沒有做任何反序列化的情況下可以在內存中讀取。Realm 跳過了所有這些開銷很大的步驟,而這些步驟在傳統的持久化機制中必須執行。Realm 只需要簡單地計算偏移來找到文件中的數據,然后從原始訪問點返回數據結構(POJO/NSManagedObject/等等)的值 。這更有效而且更快。

Realm 中的對象關系也特別的快,因為它們是相關對象的一個類 B 樹的數據結構的索引。這比查詢快多了。正因為如此,沒有必要再進行一次像 ORM 做的全查詢了。它是個簡單的指向相關對象的本地指針。這就是所有要做的事情了。

自動更新對象和查詢

零拷貝架構不僅僅提供了速度。Realm 對象和 Realm 查詢對象是活著的,底層數據改變了視圖會自動更新,這意味著永遠不需要重取數據。對象的改變會立馬改變查詢的結果。

假設如下的代碼:

android UI線程?Java

RealmResults puppies = realm.where(Dog.class).lessThan("age", 2).findAll();

puppies.size(); // => 0

realm.beginTransaction();

Dog dog = realm.createObject(Dog.class);

dog.setAge(1);

java多線程訪問數據庫、realm.commitTransaction();

puppies.size(); // => 1

// Change the dog from another query

realm.beginTransaction();

Dog theDog = realm.where(Dog.class).equals("age", 1).findFirst();

theDog.setAge(3);

多線程處理數據庫數據、realm.commitTransaction();

// Original dog is auto-updated

dog.getAge(); // => 3

puppies.size(); // => 0

Swift

let puppies = realm.objects(Dog).filter("age < 2")

android多線程并發處理。puppies.count // => 0 because no dogs have been added to the Realm yet

let myDog = Dog()

myDog.name = "Rex"

myDog.age = 1

try! realm.write {

realm.add(myDog)

realm數據庫在x86系統崩潰、}

puppies.count // => 1 updated in real-time

// Access the Dog in a separate query

let puppy = realm.objects(Dog).filter("age == 1").first

try! realm.write {

puppy.age = 3

JAVA多線程查詢數據庫、}

// Original Dog object is auto-updated

myDog.age // => 3

puppies.count // => 0

一旦 Dog 對象創建了而且提交給了 Realm,puppies 查詢結果會自動的用新值更新。如果通過另一個查詢改變 dog,原來的 dog 實例也會自動更新。

同樣的自動更新的特性在別的線程更新 Realm 數據的時候也會起作用。當對象在別的線程中更新的時候,線程本地對象會幾乎實時更新(這意味著如果它們在正在運行的進程中,更新會在下次循環迭代中發生)。而且,你可以通過 Realm#refresh() 操作強制一次數據更新。

手機數據庫軟件。所有的 Realm 對象和查詢結果實例都是這樣。

在運行循環的下次迭代發生時(或者 Realm 的 refresh 方法調用時),Realm 實例會關閉最近的頂指針的訪問(最近的 Realm 數據的版本)。

Realm 對象和查詢結果的這個屬性不僅僅使 Realm 更快,更有效,而且它使得你的代碼更簡單而且更加具備響應性。例如,如果你的 UI 依賴于一個查詢的結果,你可以在一個領域里存儲你的 Realm 對象或者 Realm 查詢結果,然后你就不需要在每次訪問的時候都去確定數據更新了沒有了。

你可以訂閱 Realm 通知機制來了解 Realm 數據發生改變的時機,意味著你的應用的 UI 需要更新了,但這并不需要重新獲取一次你的 Realm 查詢結果。這個功能在大部分的 Realm 產品中已實現,包括 Java, Objective-C,和 Swift,React?Native 正在開發中。

當 Realm 數據變化時獲取通知

自動更新對象是個非常棒的特性,除非你知道它何時發生并且響應它,否則它就沒有那么有用了。謝謝 Realm 已經實現了一個通知機制允許你能在 Realm 數據發生變化的時候做出響應。

Java并發實現原理:JDK源碼剖析、假設以下的情況:有一個 UI 線程使用對象來顯示一些 UI 值。一個后臺線程通過寫操作改變了這個對象。幾乎是即時的(在運行循環的下個迭代),UI 線程的數據對象被更新了(記住對象可以直接脫離核心數據庫工作,因為有零拷貝架構)。

后臺線程通過 Realm 的改變監聽者發送給 UI 線程一個通知消息說,有一個改變發生了。(這個特性在大部分的 Realm 產品中都已實現,包括 Java,Objective-C,和 Swift,React?Native 正在開發中。)這個時候,UI 線程可以更新視圖來顯示新的數據了,如下圖。

82baa7eada37567f38606a0f80032023.png

單線程保證安全

常常有人問:

“為什么 Realm 對象不能在線程間傳遞?”

這是個好問題,而且一個回答是:這是因為隔離性和數據一致性。

realm、因為 Realm 是基于零拷貝架構,所有對象是鮮活的而且自動更新。如果 Realm 允許對象可在線程間共享,Realm 會無法確保數據的一致性,因為不同的線程會在不確定的什么時間點同時改變對象的數據。這樣數據很快就不一致了。一個線程可能需要寫入一個數據而另一個線程也打算讀取它,反過來也可能。這很快就會變得有問題了,而且你不能夠在相信哪個線程能有正確的數據了。

是的,這可以通過許多方法來解決,一個常用的方法就是鎖住對象,存儲器和訪問器。雖然這能工作,但是鎖會變成一個頭疼的性能瓶頸。除了性能,鎖的其他問題也很明顯,因為鎖 —— 一個長時間的后臺寫事務會阻塞 UI 的讀事務。如果我們采用鎖機制,我們會失去太多的 Realm 可提供的速度優勢和數據一致性的保證。

因此,唯一的限制就是 Realm 對象在不同的線程間是不能共享的。如果你需要在另外一個線程中獲取同樣的數據,你只需要在該線程里面重新查詢。或者,更好的方法是,用 Realm 的響應式架構監聽變化!記住 - 各個線程的所有對象都是自動更新的 - Realm 會在數據變化時通知你。你只需要對這些變化做出響應就可以了。 👍

Realm 和 安卓

因為 Realm 是線程限制的,你需要理解 Realm 是如何和安卓線程環境框架一起工作的。

Realm 和 安卓線程框架

當處理 Realm 和安卓后臺線程的時候,你需要確保你在這些線程里打開和關閉了 Realm。請看文檔的 安卓 Realm 編程 部分來了解最佳實踐。

當 Realm 和安卓主線程一起工作的時候,我們建議你使用 Async API 來保證你不會遇到 ANR(應用不響應)的錯誤。

如果我在主線程里使用了 Realm?它會正常工作嗎?

一些操作在主線程里工作得很好,一些不行。現在問題變成……

什么時候在主線程里運行 Realm 工作不正常?

這里事情變得有些模糊了。有很多因素。避免去理解什么時候主線程里面的操作能成功什么時候不能成功,你需要遵循一些在安卓主線程里使用 Realm 的常見的規則:

使用 Realm 的異步 API。

使用 Realm 的異步 API 來查詢和提交事務

最近 支持異步查詢和事務的版本已發布。它采用一個非常簡單的模式,你能夠非常容易地實現異步操作。

Realm 的異步查詢

你已經理解了內部的線程模型,異步查詢就非常容易理解了。

Realms 查詢方法會有前綴 Async()(例如,findAllAsync()),然后你會立馬得到 RealmResults 或者一個 RealmObject。這些方法保證(和 Java Future 的概念很像)在一個后臺線程里執行。當查詢結束的時候,返回的對象會用查詢的結果更新。

下面是一個異步查詢的例子:

private Dog firstDog;

private RealmChangeListener dogListener = new RealmChangeListener() {

@Override

public void onChange() {

// called once the query complete and on every update

// you can use the result now!

Log.d("Realm", "Woohoo! Found the dog or it got updated!");

}

};

你的應用的其他地方還有如下的代碼(這個例子是 onCreate )。

firstDog = realm.where(Dog.class).equalTo("age", 1).findFirstAsync();

firstDog.addChangeListener(dogListener);

你也需要在 onPause() 方法里加入如下代碼:

firstDog.removeChangeListener(dogListener);

在 onCreate()里,findFirstAsync() 方法會被調用。

調用 findFirstAsync() 會首先在 RealmResults 里返回一個 RealmObject。這個方法立即返回;firstDog 不會有任何數據直到查詢完成為止。我給 firstDog RealmObject 增加了一個監聽者,來獲得操作完成的通知。這個監聽者和其他的監聽者沒有什么不同 - 我需要維護一個它的引用,然后晚點我會刪除它,我在 onPause() 方法里這樣做了。

任何時候你想看看你的 RealmObject 或者 RealmResults 更新了沒有,你可以使用 isLoaded() 方法,例如 firstDog.isLoaded()。

非安卓循環線程警告:異步查詢需要使用 Realm 的 Handler 來保證結果的一致性。嘗試使用沒有循環的線程內的 Realm 對象來調用異步查詢會拋出 IllegalStateException。

用異步事務給 Realm 寫數據

異步寫 Realm 是新的異步事務帶來的特性。異步事務支持和當前 executeTransaction 一樣的方法,但是它使得你能在后臺線程里打開 Realm 而不是在同一個線程里打開它。你可以注冊一個回調函數,如果你需要在事務結束或者失敗的時候收到通知的話。

實現異步事務非常簡單:

realm.executeTransactionAsync(new Realm.Transaction() {

@Override

public void execute(Realm realm) {

Dog dog = realm.where(Dog.class).equalTo("age", 1).findFirst();

dog.setName("Fido");

}

}, new Realm.Transaction.OnSuccess() {

@Override

public void onSuccess() {

Log.d("REALM", "All done updating.");

Log.d("BG", t.getName());

}

}, new Realm.Transaction.OnError() {

@Override

public void onError(Throwable error) {

// transaction is automatically rolled-back, do any cleanup here

}

});

上面代碼的最后兩個參數,new Realm.Transaction.OnSuccess 和 new Realm.Transaction.OnError ���可選的。回調函數在這里使得開發者能夠在事務結束或者出錯的時候得到通知。executeTransactionAsync 方法接收了一個 Realm.Transaction 對象,并且在后臺線程中執行。

重載 execute 函數然后在這個線程里面執行你的事務性工作 - 這是在后臺線程里面執行的代碼。execute 函數提供了一個可工作的 Realm。這個 Realm 對象是通過 executeTranscationAsync 方法創建的而且是后臺線程的 Realm。簡單使用這個 Realm 對象就可以找到或者更新你感興趣的條目(這個例子里是 dog),然后就這樣了!executeTransactionAsync 方法在后臺線程調用 beginTransaction 和 commitTransaction,這樣你就不需要自己再做一次了。

一旦操作完成了,Transaction.OnSuccess 方法的 onSuccess 方法會被執行。如果有錯誤發生,異常會被傳到 onError 方法,然后你再處理。你需要注意的是如果 onError 方法被調用了,Realm 事務會因為錯誤而回滾。

最后,Realm 持有一個對 Transaction 的強引用。如果你因為某種原因要取消事務(Activity 或者 Fragment 的停止),簡單地分配一個結果給事務實例,然后在別處取消它。

RealmAsyncTask transaction = realm.executeTransactionAsync(...);

...

// cancel this transaction. eg - in onStop(), etc

if (transaction != null) {

transaction.cancel();

}

Realm 的 異步事務支持也需要 Realm 的 Handler 來處理回調(如果你用的話)。如果你在一個非循環的線程里通過打開的 Realm 啟動了一個異步寫操作的話,你不會得到通知的。

* 每件事情都有例外。你可以用 System.nanotime 來標的你的代碼,如果你感到你需要加速的話。請注意,給 Java 找基準是件非常復雜的話題,因為你的 VM 會隨機地停頓來做垃圾回收,所以請使用 System.nanotime 作為基本的標準。

易磨合。輕上層。優點多多。

Realm 是用來幫助你日常開發的。

我們開發 Realm 是為了是你的持續化層開發更容易,更有趣。我們力求解決數據一致性,性能和多線程上的一些非常困難的問題。你唯一需要注意的事情就是不要在線程間傳遞對象。這就是全部!

如果這就是你所需要做的所有的事情,來保證數據一致性,而且還可以獲得性能好處和 Realm 響應式編程架構……那么……這不再是個糟糕的博弈了 ??

About the content

This content has been published here with the express permission of the author.

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/135384.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息