Spring Data Redis 讓 NoSQL 快如閃電 (1)

【編者按】本文做者爲 Xinyu Liu,詳細介紹了 Redis 的特性,並輔之以豐富的用例。在本文的第一部分,將重點概述 Redis 的方方面面。文章系國內 ITOM 管理平臺 OneAPM 編譯呈現。html

創建在 Java 企業版之上的多層體系結構是強大的服務器端編程解決方案。做爲一名從業多年的 Java 企業版開發人員,我最滿意的就是三層企業開發法:最下方是 JPA/Hibernate 持久層,中間是 Spring 或 EJB 應用層,最上方則是 web 層。對於較爲複雜的用例,我用 BPM(業務流程管理)、一個相似於 Drools 的規則引擎和一個集成框架(例如 Camel)集成了一個工做流驅動的解決方案。java

可是,筆者最近接到一個任務,要設計一個擁有亞秒級響應延遲並能支持成千上萬名併發用戶的系統。我當即發現了本身經常使用的 Java 企業版棧區的侷限性。基於關係數據庫管理系統的傳統型 web 應用程序,包括在 Hibernate/JPA 之上構建的應用程序,都有二階延遲,擴展效果不佳。傳統的 Java 企業版持久性體系結構沒法知足我當時設計的系統的性能和處理能力要求。而後我轉而嘗試 NoSQL,最後發現了 Redisgit

做爲一種內存鍵值數據庫,Redis 打破了數據庫的傳統定義(將數據保存在硬盤上)。反之,使用 Redis 時可結合持久性的 NoSQL 數據庫,好比 MongoDB、HBase、Cassandra 或 DynamoDB。Redis 以遠程緩存服務器見長,對易揮發數據來講是極快型數據庫。github

在本文中,筆者會介紹一些有關 Redis 的簡單用例和進階用例以及性能調優狀況。固然,我還會作個簡單概述,但我相信各位基本都瞭解 NoSQL 及其各類解決方案web

Spring Data Redisredis

Redis 幾乎擁有針對全部編程語言的各類客戶端庫,其中就包括 Java。Jedis 多是最受歡迎的 Java 客戶端庫了。本文中的示例都基於 Spring Data Redis,我把它做爲一個較高層次的包裝程序 API。Spring Data Redis 不只配置方便,並且擁有各類友好的 API 和實用插件。spring

Redis 概述

和大多數 NoSQL 數據庫同樣,Redis 捨棄了表格、行列的關係概念。而事實上,Redis 是一種鍵值數據庫,利用獨特的字符串鍵值來存儲和檢索每條記錄。Redis 支持把如下內置數據結構做爲全部記錄的值:sql

  • STRING 保有單個字符串值。mongodb

  • LISTSETHASH 從語義上來講與 Java 中的相同數據結構相一致。數據庫

  • ZSET 是由浮點分數安排的字符串列表,相似於 Java 中的 PriorityQueue

不一樣於關係數據庫管理系統中的表,Redis 數據結構是即時實例化的。若是用戶查詢的內容不存在於 Redis 中,系統只會返回空值。雖然 Redis 不容許嵌套結構,但用戶能夠執行自定義的 Java 或 JSON 串行器/解串器,從而將 POJO 映射到字符串。經過這種方式,就能夠把任意 Java bean 保存爲 STRING,或者將其放置在 LISTSET 中,等等。

性能和可擴展性

對於 Redis,人們注意到的第一個特色可能就是它的速度極快。根據記錄的大小和鏈接的數量,性能基準會有所不一樣,但延遲一般爲單數位毫秒。在大多數用例中,Redis 每秒最多可支持 50000 次請求。若是用戶使用較高端的硬件,處理能力更可高達每秒 700000 次請求(但這一數值可能會被網卡帶寬扼制)。

做爲一種內存數據庫,Redis 的存儲容量有限; AWS EC2 中的最大實例爲 r3.8xlarge,內存 244 GB。因爲數據結構的索引和性能都通過優化,Redis 消耗的內存比所存儲的數據量大得多。切分 Redis 有助於克服這一侷限性。要把內存數據備份到硬盤上,能夠在預約做業中進行時間點轉儲,也能夠根據須要運行 dump 命令。

用 Spring 進行遠程數據緩存

要想提高應用程序服務器的性能,數據緩存多是性價比最高的辦法了。利用 Spring 的緩存抽象註釋(@Cacheable@CachePut@CacheEvict@Caching@CacheConfig)能夠絕不費力地啓用數據緩存。在 Spring 配置下,用戶還能夠把 Ehcache、Memcached 或 Redis 看成基本緩存服務器。

Encache 一般被配置成本地緩存層,具備嵌套結構,在應用的 JVM 上運行。 Memcached 和 Redis 都能做爲獨立的緩存服務器運行。要想把 Redis 緩存集成到基於 Spring 的應用中,須要使用 Spring Data Redis 的 RedisTemplate 和 RedisCacheManager。

在 Redis 中訪問已緩存的對象,耗時一般不到數毫秒,和關係數據庫查詢相比,這大幅提高了應用程序的性能。

延遲和收益

亞馬遜公司在很大程度上依賴緩存服務器來最大程度地減小其零售網站的延遲,該公司甚至曾經發布過一份案例分析,其中記錄了延遲和收益之間的關係。

本地緩存與遠程緩存

在沒有網絡開銷的系統中,本地緩存快於遠程緩存。本地緩存的缺點是,同一個對象的多個拷貝在服務器集羣中的各個不一樣節點之中會同步得更快。正因如此,本地緩存僅適用於靜態數據,例如可容忍短時間滯後和不一致現象的系統級設置。若是爲易揮發的業務數據(例如用戶數據和交易數據)使用本地緩存,頗有可能會以運行應用程序服務器的單個實例而了結。

遠程緩存服務器就沒有這一侷限性。在同一個鍵的狀況下,可保證緩存服務器上的對象只有一個拷貝。只要用戶讓緩存中的對象及其數據庫值彼此保持同步,就無需處理過時數據。

列表 1 給出了一個 Spring 數據緩存的示例。

列表 1:在基於 Spring 的應用中啓用緩存

@Cacheable(value="User_CACHE_REPOSITORY", key = "#id")
   public User get(Long id) {  
      return em.find(User.class, id);
}
@Caching(put = {@CachePut(value="USER_CACHE_REPOSITORY", key = "#user.getId()")})  
public User update(User user) {
    em.merge(user);    
    return user;  
}
@Caching(evict = {@CacheEvict(value="USER_CACHE_REPOSITORY", key = "#user.getId()")})  public void delete(User user) {
    em.remove(user);
}
@Caching(evict = {@CacheEvict(value="USER_CACHE_REPOSITORY", key = "#user.getId()")})  public void evictCache(User user) {
}

這裏的讀取操做被 Spring 的 @Cacheable 註釋圍繞,做爲 AOP 幕僚而執行。Spring 中的存活時間設置也規定了這些對象可在緩存中停留的時間。調用 get() 方法後,Spring 就會試着先從遠程緩存讀取和返回對象。若是未找到對象,Spring 會執行方法主體,而後將數據庫結果放在遠程緩存中,以後再返回結果。

但若是另外一個過程(例如另外一個服務器節點)甚至同一個 JVM 中的另外一個線程在數據庫中更新了同一個對象,又會怎樣呢?若是隻運用 @Cacheable 註釋,你可能會從遠程緩存服務器收到過時拷貝。

爲了防止發生這種狀況,能夠給全部數據庫更新操做添加一個 @CachePut 註釋。每次調用這些方法時,返回值就會替換掉遠程緩存中原先的對象。在數據庫讀取和寫入上都更新緩存,可讓緩存服務器和後臺數據之間的記錄保持同步。

容錯

聽起來簡直完美,對吧?事實固然不是這樣。利用列表 1 中的配置,負載較低時可能不會遇到任何問題,但隨着服務器集羣上的負載逐漸增長,遠程緩存上就會出現過時數據。要作好準備應對服務器節點爭用甚至更糟的狀況。即便成功寫入數據庫,最後也可能會由於網絡故障而使得緩存服務器 PUT 以失敗了結。另外,NoSQL 一般不支持在關係數據庫中存在完整事務語義,由於這會致使部分提交。爲了讓代碼容錯,能夠考慮給數據模型增長版本號,實現樂觀鎖。

在收到 OptimisticLockingFailureExceptionCurrentModificationException(具體取決於持久性解決方案)時,能夠調用帶有 @CacheEvict 註釋的方法,從緩存中清除過時拷貝,而後重試同一個操做:

列表 2:解決緩存中的過時對象

try{
    User user = userDao.get(id);    // user fetched in cache server
    userDao.update(user, oldname, newname);      
}catch(ConcurrentModificationException ex) {   // cached user object may be stale
    userDao.evictCache(user);
    user =  userDao.get(id);     // refresh user object
    userDao.update(user, oldname, newname);    // retry the same operation. Note it may still throw legitimate ConcurrentModificationException.}

結合 Elasticache 使用 Redis

Amazon Elasticache 是一款內存緩存服務,可結合 Memcached 或 Redis 做爲緩存服務器使用。雖然 Elasticache 不在本文介紹範圍內,但筆者仍是想給各位開發人員介紹一個結合 Redis 使用 Elasticache 的技巧。對於大多數 Redis 參數,使用其默認值並沒有大礙,但 tcp-keepalivetimeout 的默認 Redis 設置並不會移除已無效的客戶鏈接,最後還會耗盡緩存服務器上的套接口。結合 Elasticache 使用 Redis 時,務必每次都明確設置這兩個值。

在本文的第二部分,將介紹 Redis 的6大用例,敬請期待。

本文系 OneAPM 工程師編譯整理。OneAPM 能爲您提供端到端的 Java 應用性能解決方案,咱們支持全部常見的 Java 框架及應用服務器,助您快速發現系統瓶頸,定位異常根本緣由。分鐘級部署,即刻體驗,Java 監控歷來沒有如此簡單。想閱讀更多技術文章,請訪問 OneAPM 官方技術博客

本文轉自 OneAPM 官方博客

原文地址:http://www.javaworld.com/article/3062899/big-data/lightning-fast-nosql-with-spring-data-redis.html?page=1

相關文章
相關標籤/搜索