了解 Redis 的同學都知道它是一個純內存的數據庫,憑借優秀的并發和易用性打下了互聯網項的半壁江山。Redis 之所以高性能是因為它的純內存訪問特性,而這也成了它致命的弱點 —— 內存的成本太高。所以在絕大多數場合,它比較適合用來做緩存,長期不被訪問的冷數據被淘汰掉,只有熱的數據緩存在內存中,這樣就不會浪費太多昂貴的內存空間。
但是 Redis 的誘惑太大了,用它來做持久存儲使用起來太方便了。要是內存的價格低廉,真恨不得把所有的數據都堆到 Redis 中,但是技術的選擇總是要考慮到現實世界的成本問題。那如何才能享受到 Redis 作為持久層易用性的同時還可以節省內存成本呢?
## **LevelDB 來了!**
它是 Google 開源的 NOSQL 存儲引擎庫,是現代分布式存儲領域的一枚原子彈。在它的基礎之上,Facebook 開發出了另一個 NOSQL 存儲引擎庫 RocksDB,沿用了 LevelDB 的先進技術架構的同時還解決了 LevelDB 的一些短板。你可以將 RocksDB 比喻成氫彈,它比 LevelDB 的威力更大一些。現代開源市場上有很多數據庫都在使用 RocksDB 作為底層存儲引擎,比如大名鼎鼎的 TiDB。
## **Redis 緩存有什么問題?**
當我們將 Redis 拿來做緩存用時,背后肯定還有一個持久層數據庫記錄了全量的冷熱數據。Redis 和持久層數據庫之間的數據一致性是由應用程序自己來控制的。應用程序會優先去緩存中獲取數據,當緩存中沒有數據時,應用程序需要從持久層加載數據,然后再放進緩存中。當數據更新發生時,需要將緩存置為失效。
~~~js
function getUser(String userId) User {
User user = redis.get(userId);
if user == null {
user = db.get(userId);
if user != null {
redis.set(userId, user);
}
}
return user;
}
function updateUser(String userId, User user) {
db.update(userId, user);
redis.expire(userId);
}
~~~
有過這方面開發經驗的朋友們就知道寫這樣的代碼還是挺繁瑣的,所有的涉及到緩存的業務代碼都需要加上這一部分邏輯。
嚴格來說我們還需要仔細考慮緩存一致性問題,比如在 updateUser 方法中,數據庫正確執行了更新,但是緩存 redis 因為網絡抖動等原因置為失效沒有成功,那么緩存中的數據就成了過期數據。如果你將設置緩存和更新持久存的先后順序反過來,也還是會有其它問題,這個讀者可以自行思考一下。
在多進程高并發場合也會導致緩存不一致,比如一個進程對某個 userId 調用 getUser() 方法,因為緩存里沒有,它需要從數據庫里加載。結果剛剛加載出來,正準備要設置緩存,這時候發生了內存 fullgc 代碼暫停了一會,而正在此時另一個進程調用了 updateUser 方法更新了數據庫,將緩存置為失效(其實緩存里本來就沒有數據)。然后前面那個進程終于 fullgc 結束要開始設置緩存了,這時候進緩存的就是過期的數據。
## **LevelDB 是如何解決的?**
LevelDB 將 Redis 緩存和持久層合二為一,一次性幫你搞定緩存和持久層。有了 LevelDB,你的代碼可以簡化成下面這樣
~~~js
function getUser(String userId) User {
return leveldb.get(userId);
}
function updateUser(String userId, User user) {
leveldb.set(userId, user);
}
~~~
而且你再也不用當心緩存一致性問題了,LevelDB 的數據更新要么成功要么不成功,不存在中間薛定諤狀態。LevelDB 的內部已經內置了內存緩存和持久層的磁盤文件,用戶完全不用操心內部是數據如何保持一致的。
## **LevelDB 具體是什么?**
前面我們說道它是一個 NOSQL 存儲引擎,它和 Redis 不是一個概念。Redis 是一個完備的數據庫,而 LevelDB 它只是一個引擎。如果將數據庫比喻成一輛高級跑車,那么存儲引擎就是它的發動機,是核心是心臟。有了這個發動機,我們再給它包裝上一系列的配件和裝飾,就可以成為數據庫。不過也不要小瞧了配件和裝飾,做到極致那也是非常困難,將 LevelDB 包裝成一個簡單易用的數據庫需要加上太多太多精致的配件。LevelDB 和 RocksDB 出來這么多年,能夠在它的基礎上做出非常一個完備的生產級數據庫寥寥無幾。
在使用 LevelDB 時,我們還可以將它看成一個 Key/Value 內存數據庫。它提供了基礎的 Get/Set API,我們在代碼里可以通過這個 API 來讀寫數據。你還可以將它看成一個無限大小的高級 HashMap,我們可以往里面塞入無限條 Key/Value 數據,只要磁盤可以裝下。
正是因為它只能算作一個內存數據庫,它里面裝的數據無法跨進程跨機器共享。**在分布式領域,LevelDB 要如何大顯身手呢?**
這就需要靠包裝技術了,在 LevelDB 內存數據庫的基礎上包裝一層網絡 API。當不同機器上不同的進程要來訪問它時,都統一走網絡 API 接口。這樣就形成了一個簡易的數據庫。如果在網絡層我們使用 Redis 協議來包裝,那么使用 Redis 的客戶端就可以讀寫這個數據庫了。
-----
https://zhuanlan.zhihu.com/p/53299778
- 前言
- 服務器開發設計
- Reactor模式
- 一種心跳,兩種設計
- 聊聊 TCP 長連接和心跳那些事
- 學習TCP三次握手和四次揮手
- Linux基礎
- Linux的inode的理解
- 異步IO模型介紹
- 20個最常用的GCC編譯器參數
- epoll
- epoll精髓
- epoll原理詳解及epoll反應堆模型
- epoll的坑
- epoll的本質
- socket的SO_REUSEADDR參數全面分析
- 服務器網絡
- Protobuf
- Protobuf2 語法指南
- 一種自動反射消息類型的 Protobuf 網絡傳輸方案
- 微服務
- RPC框架
- 什么是RPC
- 如何科學的解釋RPC
- RPC 消息協議
- 實現一個極簡版的RPC
- 一個基于protobuf的極簡RPC
- 如何基于protobuf實現一個極簡版的RPC
- 開源RPC框架
- thrift
- grpc
- brpc
- Dubbo
- 服務注冊,發現,治理
- Redis
- Redis發布訂閱
- Redis分布式鎖
- 一致性哈希算法
- Redis常見問題
- Redis數據類型
- 緩存一致性
- LevelDB
- 高可用
- keepalived基本理解
- keepalived操做
- LVS 學習
- 性能優化
- Linux服務器程序性能優化方法
- SRS性能(CPU)、內存優化工具用法
- centos6的性能分析工具集合
- CentOS系統性能工具 sar 示例!
- Linux性能監控工具集sysstat
- gdb相關
- Linux 下如何產生core文件(core dump設置)