Redis相關知識簡介
Redis,一種輕量級鍵值對數據存儲。多數 NoSQL 實現本質上都是鍵值對,但是 Redis 支持非常豐富的值集,其中包括字符串、列表、集以及散列。因此,Redis 通常被稱為數據結構服務器。Redis 也以異常快速而聞名,這使得它成為某一特定類型使用案例的最優選擇。
當我們想要了解一種新事物時,將其同熟知的事物進行比較可能會有所幫助,因此,我們將通過對比其與 memcached 的相似性以開啟 Redis 探索之旅。接著我們將介紹 Redis 的主要功能,這些功能可以使其在某些應用場景可以勝過 memcached。最后我將向您展示如何將 Redis 作為一個傳統數據存儲用于模型對象。
Redis 和 memcached
Memcached 是一個眾所周知的內存對象緩存系統,通過將目標鍵和值導入內存緩存運行。因此,Memcached 能回避讀取磁盤時發生的 I/O 成本問題。在 Web 應用程序和數據庫之間粘貼 memcached 時會產生更好的讀取性能。因此,對于那些需要快速數據查詢的應用程序,Memcached 是一個不錯的選擇。其中的一個例子為股票查詢服務,需要另外訪問數據庫獲取相對靜態數據,如股票名稱或價格信息。
但是 memcached 也有其局限性,其中一個事實就是它所有的值均是簡單的字符串。Redis 作為 memcached 的替代者,支持更加豐富的功能集。一些基準 (benchmarks) 也表明 Redis 的速度要比 memcached 快很多。Redis 提供的豐富數據類型使其可以在內存中存儲更為復雜的數據,這是使用 memcached 無法實現的。同 memcached 不一樣,Redis 可以持久化其數據。
Redis 解決了一個重大的緩存問題,而其豐富的功能集又為其找到了其他用途。由于 Redis 能夠在磁盤上存儲數據以及跨節點復制數據,因而可以作為數據倉庫用于傳統數據模式(也就是說,您可以使用 Redis,就像使用 RDBMS 一樣)。Redis 還經常被用作隊列系統。在本用例中,Redis 是備份和工作隊列持久化存儲(利用 Redis 的列表類型)的基礎。GitHub 是以此種方法使用 Redis 的大規模基礎架構示例
準備好 Redis,立即開始!
要開始使用 Redis,您需要訪問它,可以通過本地安裝或者托管供應商來實現訪問。如果您使用的 MAC,安裝過程可能就不那么簡單。如果您使用的是 Windows?,您需要先安裝Cygwin。如果您正在尋找一個托管供應商,Redis4You 擁有一個免費計劃。不管您以何種方式訪問,您都能夠根據本文下列示例進行操作,但是我需要指出的是,使用一個托管供應商進行緩存可能并不是很好的緩存解決方案,因為網絡延遲可能會抵消任何性能優勢。
您需要通過命令與 Redis 進行交互,這就是說,這里沒有 SQL 類查詢語言。使用 Redis 工作非常類似于使用傳統 map
數據結構,即所有的一切都擁有一個鍵和一個值,每個值都有多種與之關聯的數據類型。每個數據類型都有其自己的命令集。例如,如果您計劃使用簡單數據類型,比如某種緩存模式,您可以使用命令set
和 get
。
您可以通過命令行 shell 與一個 Reids 實例進行交互。還有多個客戶端實現,可以以編程方式與 Redis 進行交互。清單 1 展示了一個使用基礎命令的簡單命令行 shell 交互:
清單 1. 使用基礎的 Redis 命令
redis 127.0.0.1:6379> set page registration OK redis 127.0.0.1:6379> keys * 1) "foo" 2) "page" redis 127.0.0.1:6379> get page "registration"
在這里,我通過 set
命令將鍵 "page" 與值 "registration" 相關聯。接著,我發出 keys
命令(后綴*
表示我想看到所有可用的實例鍵。keys
命令顯示有一個 page
值和一個 foo
,我可以通過 get
命令檢索到與一個鍵關聯的值。請記住,使用 get
檢索到的值只能是一個字符串。如果一個鍵的值是一個列表,那么您必須使用一個特定列表的命令來檢索列表元素。(注意,有可以查詢值類型的命令)。
Java 與 Jedis 集成
對于那些想要將 Redis 集成到 Java 應用程序的編程人員,Redis 團隊建議使用一個名為 Jedis 的項目,Jedis 是一個輕量級庫,可以將本地 Redis 命令映射到 Java 方法。例如 Jedis 可以獲取并設置簡單值,如清單 2 所示:
清單 2. Java 代碼中的基礎 Redis 命令
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost"); Jedis jedis = pool.getResource(); jedis.set("foo", "bar"); String foobar = jedis.get("foo"); assert foobar.equals("bar"); pool.returnResource(jedis); pool.destroy();
在清單 2 中,我配置了一個連接池并捕獲連接,(與您在典型 JDBC 場景中的操作非常相似)然后我在清單的底部設置了返回操作。在連接池邏輯之間,我設置了值 "bar"
和鍵 "foo"
,這是我通過 get
命令檢索到的。
與 memcached 類似,Redis 允許您將過期(expiration)時間關聯到一個值。因此我設置了這樣一個值(比如,股票臨時交易價格),最終將從 Redis 緩存中清除掉。如果我想在 Jedis 中設置一個過期時間,需要在發出set
調用之后將其和一個過期時間關聯。如清單 3 所示:
清單 3. Redis 值可以設置為終止
jedis.set("gone", "daddy, gone"); jedis.expire("gone", 10); String there = jedis.get("gone"); assert there.equals("daddy, gone"); Thread.sleep(4500); String notThere = jedis.get("gone"); assert notThere == null;
在清單 3 中,我使用了一個 expire
調用將 "gone" 的值設置為在 10 秒鐘內終止。調用 Thread.sleep
之后,"gone" 的get
調用會返回 null
。
Redis 中的數據類型
使用 Redis 數據類型,比如列表和散列需要專用命令用法。例如,我可以通過為鍵附加值來創建列表。在清單 4 的代碼中,我發出一個 rpush
命令,將一個值附加至列表右側或者尾端。(相應的lpush
命令會將一個值添加到列表的前端。)
清單 4. Redis 列表
jedis.rpush("people", "Mary"); assert jedis.lindex("people", 0).equals("Mary"); jedis.rpush("people", "Mark"); assert jedis.llen("people") == 2; assert jedis.lindex("people", 1).equals("Mark");
Redis 為數據類型提供許多命令;此外,每個數據類型都有其自己的命令集。這里就不再逐個討論了,我將會在一個實際應用程序開發場景中介紹其中的一些。
使用 Redis 作為一個緩存解決方案
我之前提到過,Redis 可輕易地用作一個緩存解決方案,碰巧我現在正好需要這樣一個!在該應用程序示例中,我將 Redis 集成到我基于定位的移動 Web 服務中,稱之為 Magnus。
如果您沒有關注本系列,那么我會先使用 Play 框架實現 Magnus,從那時起我就已經在各種實現中開發和重構它了。Magnus 是一個簡單服務,可以通過 HTTPPUT
請求使用 JSON 文檔。這些文檔描述了特定帳號的位置,表示持有移動設備的人。
現在,我想要將緩存集成到 Magnus,也就是說我想要通過將不常更改的數據存儲在內存中以減少 I/O 流量。
Magnus 緩存!
在清單 5 中的第一步中,可以通過 get
調用了解新引入的帳戶名稱(一個鍵)是否為 REdis 中的一個鍵。get
調用可以將帳戶 ID 作為一個值返回,或者將返回null
。如果返回一個值,我將用其作為我的 acctId
變量。如果返回的是 null
(表明該帳戶名稱不是 Redis 中一個鍵),那么我將在 MongoDB 查找該帳戶值,并通過set
命令將其添加到 Redis。
這里的優勢是速度:接下來,被請求的帳戶將提交一個位置,這樣我就能夠從 Redis 中獲取其 ID(作為內存緩存),而不是轉到 MongoDB 并帶來額外讀取 I/O 成本。
清單 5. 使用 Redis 作為內存緩存
"/location/:account" { put { def jacksonMapper = new ObjectMapper() def json = jacksonMapper.readValue(request.contentText, Map.class) def formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm") def dt = formatter.parse(json['timestamp']) def res = [:] try{ def jedis = pool.getResource() def acctId = jedis.get(request.parameters['account']) if(!acctId){ def acct = Account.findByName(request.parameters['account']) jedis.set(request.parameters['account'], acct.id.toString()) acctId = acct.id } pool.returnResource(jedis) new Location(acctId.toString(), dt, json['latitude'].doubleValue(), json['longitude'].doubleValue() ).save() res['status'] = 'success' }catch(exp){ res['status'] = "error ${exp.message}" } response.json = jacksonMapper.writeValueAsString(res) } }
注意,清單 5 中的 aMagnus 實現(使用 Groovy 編寫)仍然使用一個 NoSQL 實現作為數據模型存儲;它僅僅使用 Redis 作為一個緩存實現用于查詢數據。因為我的主要帳戶數據位于 MongoDB 中(事實上,它駐留在 MongoHQ.com 中),而我的 Redis 數據存儲在本地運行。在隨后查找帳戶 ID 時,Magnus 速度將顯著提升。
可是等等!我為什么同時需要 MongoDB 和 Redis?難道我就不能單獨使用一個嗎?
ORM 的 Node.js
很多項目均提供 ORM 類映射用于 Redis,其中包括一個極富影響力的基于 Ruby 的備用方案,稱為 Ohm。我檢查了該項目基于 Java 的派生產品(稱為 JOhm),但是最終決定使用一個為 Node 編寫的派生產品。Ohm 及其派生項目的妙處在于他們允許您將一個對象模型映射到一個基于 Redis 的數據結構。因此,您的模型對象是持久性的,同時在大多數情況下其讀取速度也非常之快。
有了 Nohm,我便能夠使用 JavaScript 快速重寫我的 Magnus 應用程序并能立即持久化 Location
對象。在清單 6 中,我已定義了一個Location
模型,該模型包括 3 個屬性。(注意,我通過將 timestamp
設置為一個字符串而不是一個真實的時間戳,從而簡化我的示例。)
清單 6. Node.js 中的 Redis ORM
var Location = nohm.model('Location', { properties: { latitude: { type: 'float', unique: false, validations: [ ['notEmpty'] ] }, longitude: { type: 'float', unique: false, validations: [ ['notEmpty'] ] }, timestamp: { type: 'string', unique: false, validations: [ ['notEmpty'] ] } } });
Node 的 Express 框架使 Nohm Location
對象的使用變得十分簡單。在我的應用程序 PUT
實現中,我可以捕獲正在進入的 JSON 值,并通過 Nohm 的p
調用將其導入到一個 Location
實例。然后我再檢查該示例是否有效,如果有效,我會對其進行持久化。
清單 7. 在 Node 的 Express.js 中使用 Nohm
app.put('/', function(req, res) { res.contentType('json'); var location = new Location; location.p("timestamp", req.body.timestamp); location.p("latitude", req.body.latitude); location.p("longitude", req.body.longitude); if(location.valid()){ location.save(function (err) { if (!err) { res.send(JSON.stringify({ status: "success" })); } else { res.send(JSON.stringify({ status: location.errors })); } }); }else{ res.send(JSON.stringify({ status: location.errors })); } });
正如清單 7 所示,可以輕易地將 Redis 構建成一個極其快速的內存數據存儲。在一些案例中,它甚至是一個比 memcached 更好的緩存!
結束語
Redis 對于許多數據存儲場景非常有用,因為它可以將數據持久化到磁盤(還因為它支持一個豐富的數據集),有時候,它是 memcached 的有力競爭對手。有些情況下,對于您的領域也是很有意義的,您可以使用 Redis 作為數據模型和隊列的一個備份存儲。Redis 客戶端實現幾乎可被移植到任何編程語言中。
Redis 不是 RDMBS 的完全替代品,也不是一個重量級存儲,但是和 MongoDB 一樣擁有豐富的功能。然而,在很多情況下,它可與這些技術共存。正如本文所述,Redis 是一個良好的應用程序單機數據解決方案,可以運行大量數據查詢,或者其中的實時統計可通過 Redis 的快速原子操作完成。