我們先從一個相對簡單的用例開始吧:一個增量計數器,可顯示某網站受到多少次點選。spring data redis 有兩個适用于這一實用程式的類:<code>redisatomicinteger</code> 和 <code>redisatomiclong</code>。和 java 并發包中的 <code>atomicinteger</code> 和 <code>atomiclong</code> 不同的是,這些 spring 類能在多個 jvm 中發揮作用。
清單 3:全局唯一增量計數器
請注意整型溢出并謹記,在這兩個類上進行操作需要付出相對較高的代價。
時不時的,使用者就得應對伺服器叢集的争用。假設你從一個伺服器叢集運作一個預定作業。在沒有全局鎖的情況下,叢集中的節點會發起備援作業執行個體。假設某個聊天室分區可容納 50 人。如果聊天室已滿,就需要建立新的聊天室執行個體來容納另外 50 人。
清單4:全局悲觀鎖
如果使用關系資料庫,一旦最先生成鎖的程式意外退出,鎖就可能永遠得不到釋放。redis 的 <code>expire</code> 設定可確定在任何情況下釋放鎖。
假設 web 用戶端需要輪詢一台 web 伺服器,針對某個資料庫中的多個表查詢客戶指定更新内容。如果盲目地查詢所有相應的表以尋找潛在更新,成本較高。為了避免這一做法,可以嘗試在 redis 中給每個用戶端儲存一個整型作為髒名額,整型的每個數位表示一個表。該表中存在客戶所需更新時,設定數位。輪詢期間,不會觸發對表的查詢,除非設定了相應數位。就擷取并将這樣的位屏蔽設定為 <code>string</code> 而言,redis 非常高效。
借助出色的速度和處理能力,redis 極好地融合了布隆過濾器。搜尋 github,就能發現很多 redis 布隆過濾器項目,其中一些還支援可調諧精度。
redis 釋出/訂閱管道的工作方式類似于一個扇出消息傳遞系統,或 jms 語義中的一個主題。jms 主題和 redis 釋出/訂閱管道的一個差別是,通過 redis 釋出的消息并不持久。消息被推送給所有相連的用戶端後,redis 上就會删除這一消息。換句話說,訂閱者必須一直線上才能接收新消息。redis 釋出/訂閱管道的典型用例包括實時配置分布、簡單的聊天伺服器等。
在 web 伺服器叢集中,每個節點都可以是 redis 釋出/訂閱管道的一個訂閱者。釋出到管道上的消息也會被即時推送到所有相連節點。這一消息可以是某種配置更改,也可以是針對所有線上使用者的全局通知。和恒定輪詢相比,這種推送溝通模式顯然極為高效。
redis 非常強大,但也可以從整體上和根據特定程式設計場景做出進一步優化。可以考慮以下技巧。
所有 redis 資料結構都具備存活時間 (ttl) 屬性。當你設定這一屬性時,資料結構會在過期後自動删除。充分利用這一功能,可以讓 redis 保持較低的記憶體損耗。
在一條請求中向 redis 發送多個指令,這種方法叫做管道技術。這一技術節省了網絡往返的成本,這一點非常重要,因為網絡延遲可能比 redis 延遲要高上好幾個量級。但這裡存在一個陷阱:管道中的 redis 指令清單必須預先确定,并且應當彼此獨立。如果一個指令的參數是由先前指令的結果計算得出,管道技術就不起作用。清單 5 給出了 redis 管道技術的一個示例。
清單 5:管道技術
redis 并不像關系資料庫管理系統那樣能支援全面的 acid 事務,但其自有的事務也非常有效。從本質上來說,redis 事務是管道、樂觀鎖、确定送出和復原的結合。其思想是執行一個管道中的一個指令清單,然後觀察某一關鍵記錄的潛在更新(樂觀鎖)。根據所觀察的記錄是否會被另一個程序更新,該指令清單或整體确定送出,或完全復原。
下面以某個拍賣網站上的賣方庫存為例。買方試圖從賣方處購買某件商品時,你負責觀察 redis 事務内的賣方庫存變化。同時,你要從同一個庫存中删除此商品。事務關閉前,如果庫存被一個以上程序觸及(例如,如果兩個買方同時購買了同一件商品),事務将復原,否則事務會确定送出。復原後可開始重試。
在運作一個 <code>monitor</code> 指令後,我的團隊發現,在進行 redis 操作或 <code>rediscallback</code> 後,spring 并沒有自動關閉 redis 連接配接,而事實上它是應該關閉的。如果再次使用未關閉的連接配接,可能會從意想不到的 redis 密鑰傳回垃圾資料。有意思的是,如果在 <code>redistemplate</code> 中把事務支援設為 false,這一問題就不會出現了。
我們發現,我們可以先在 spring 語境裡配置一個 <code>platformtransactionmanager</code>(例如 <code>datasourcetransactionmanager</code>),然後再用 <code>@transactional</code> 注釋來聲明 redis 事務的範圍,讓 spring 自動關閉 redis 連接配接。
根據這一經驗,我們相信,在 spring 語境裡配置兩個單獨的 <code>redistemplate</code> 是很好的做法:其中一個 redistemplates 的事務設為 false,用于大多數 redis 操作,另一個 redistemplates 的事務已激活,僅用于 redis 事務。當然必須要聲明 <code>platformtransactionmanager</code> 和 <code>@transactional</code>,以防傳回垃圾數值。
另外,我們還發現了 redis 事務和關系資料庫事務(在本例中,即 jdbc)相結合的不利之處。混合型事務的表現和預想的不太一樣。
我希望通過這篇文章向其他 java 企業開發師介紹 redis 的強大之處,尤其是将 redis 用作遠端資料緩存和用于易揮發資料時。在這裡我介紹了 redis 的六個有效用例,分享了一些性能優化技巧,還說明了我的 glu mobile 團隊怎樣解決了 spring data redis 事務配置不當造成的垃圾資料問題。我希望這篇文章能夠激發你對 redis nosql 的好奇心,讓你能夠受到啟發,在自己的 java 企業版系統裡創造出一番天地。