摘要:布隆過濾器布隆過濾器是一種空間利用率較高的概率數(shù)據(jù)結(jié)構(gòu),用來測試某元素是否某個集的一員。則利用布隆過濾器過濾掉不包含特殊行或列的塊磁盤讀取,使讀取速度得到明顯提升。搜索,就能發(fā)現(xiàn)很多布隆過濾器項目,其中一些還支持可調(diào)諧精度。
【編者按】本文作者為 Xinyu Liu,文章的第一部分重點概述了 Redis 方方面面的特性。在第二部分,將介紹詳細(xì)的用例。文章系國內(nèi) ITOM 管理平臺 OneAPM 編譯呈現(xiàn)。
把 Redis 當(dāng)作數(shù)據(jù)庫的用例現(xiàn)在我們來看看在服務(wù)器端 Java 企業(yè)版系統(tǒng)中把 Redis 當(dāng)作數(shù)據(jù)庫的各種用法吧。無論用例的簡繁,Redis 都能幫助用戶優(yōu)化性能、處理能力和延遲,讓常規(guī) Java 企業(yè)版技術(shù)棧望而卻步。
1. 全局唯一增量計數(shù)器我們先從一個相對簡單的用例開始吧:一個增量計數(shù)器,可顯示某網(wǎng)站受到多少次點擊。Spring Data Redis 有兩個適用于這一實用程序的類:RedisAtomicInteger 和 RedisAtomicLong。和 Java 并發(fā)包中的 AtomicInteger 和 AtomicLong 不同的是,這些 Spring 類能在多個 JVM 中發(fā)揮作用。
列表 3:全局唯一增量計數(shù)器
RedisAtomicLong counter = new RedisAtomicLong("UNIQUE_COUNTER_NAME", redisTemplate.getConnectionFactory()); Long myCounter = counter.incrementAndGet();// return the incremented value
請注意整型溢出并謹(jǐn)記,在這兩個類上進(jìn)行操作需要付出相對較高的代價。
2. 全局悲觀鎖時不時的,用戶就得應(yīng)對服務(wù)器集群的爭用。假設(shè)你從一個服務(wù)器集群運行一個預(yù)定作業(yè)。在沒有全局鎖的情況下,集群中的節(jié)點會發(fā)起冗余作業(yè)實例。假設(shè)某個聊天室分區(qū)可容納 50 人。如果聊天室已滿,就需要創(chuàng)建新的聊天室實例來容納另外 50 人。
如果檢測到聊天室已滿但沒有全局鎖,集群中的各個節(jié)點就會創(chuàng)建自有的聊天室實例,為整個系統(tǒng)帶來不可預(yù)知的因素。列表 4 介紹了應(yīng)當(dāng)如何充分利用 SETNX(SET if Not eXists:如果不存在,則設(shè)置)這一 Redis 命令來執(zhí)行全局悲觀鎖。
列表4:全局悲觀鎖
public String aquirePessimisticLockWithTimeout(String lockName, int acquireTimeout, int lockTimeout) { if (StringUtils.isBlank(lockName) || lockTimeout <= 0) return null; final String lockKey = lockName; String identifier = UUID.randomUUID().toString(); Calendar atoCal = Calendar.getInstance(); atoCal.add(Calendar.SECOND, acquireTimeout); Date atoTime = atoCal.getTime(); while (true) { // try to acquire the lock if (redisTemplate.execute(new RedisCallback() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.setNX( redisTemplate.getStringSerializer().serialize(lockKey), redisTemplate.getStringSerializer().serialize(identifier)); } })) { // successfully acquired the lock, set expiration of the lock redisTemplate.execute(new RedisCallback () { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.expire(redisTemplate .getStringSerializer().serialize(lockKey), lockTimeout); } }); return identifier; } else { // fail to acquire the lock // set expiration of the lock in case ttl is not set yet. if (null == redisTemplate.execute(new RedisCallback () { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.ttl(redisTemplate .getStringSerializer().serialize(lockKey)); } })) { // set expiration of the lock redisTemplate.execute(new RedisCallback () { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.expire(redisTemplate .getStringSerializer().serialize(lockKey), lockTimeout); } }); } if (acquireTimeout < 0) // no wait return null; else { try { Thread.sleep(100l); // wait 100 milliseconds before retry } catch (InterruptedException ex) { } } if (new Date().after(atoTime)) break; } } return null; } public void releasePessimisticLockWithTimeout(String lockName, String identifier) { if (StringUtils.isBlank(lockName) || StringUtils.isBlank(identifier)) return; final String lockKey = lockName; redisTemplate.execute(new RedisCallback () { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { byte[] ctn = connection.get(redisTemplate .getStringSerializer().serialize(lockKey)); if(ctn!=null && identifier.equals(redisTemplate.getStringSerializer().deserialize(ctn))) connection.del(redisTemplate.getStringSerializer().serialize(lockKey)); return null; } }); }
如果使用關(guān)系數(shù)據(jù)庫,一旦最先生成鎖的程序意外退出,鎖就可能永遠(yuǎn)得不到釋放。Redis 的 EXPIRE 設(shè)置可確保在任何情況下釋放鎖。
3. 位屏蔽(Bit Mask)假設(shè) web 客戶端需要輪詢一臺 web 服務(wù)器,針對某個數(shù)據(jù)庫中的多個表查詢客戶指定更新內(nèi)容。如果盲目地查詢所有相應(yīng)的表以尋找潛在更新,成本較高。為了避免這一做法,可以嘗試在 Redis 中給每個客戶端保存一個整型作為臟指標(biāo),整型的每個數(shù)位表示一個表。該表中存在客戶所需更新時,設(shè)置數(shù)位。輪詢期間,不會觸發(fā)對表的查詢,除非設(shè)置了相應(yīng)數(shù)位。就獲取并將這樣的位屏蔽設(shè)置為 STRING 而言,Redis 非常高效。
4. 排行榜(Leaderboard)Redis 的 ZSET 數(shù)據(jù)結(jié)構(gòu)為游戲玩家排行榜提供了簡潔的解決方案。ZSET 的工作方式有些類似于 Java 中的 PriorityQueue,各個對象均為經(jīng)過排序的數(shù)據(jù)結(jié)構(gòu),井井有條??梢园凑辗?jǐn)?shù)排出游戲玩家在排行榜上的位置。Redis 的 ZSET 定義了一份內(nèi)容豐富的命令列表,支持靈活有效的查詢。例如,ZRANGE(包括 ZREVRANGE)可返回有序集內(nèi)的指定范圍要素。
你可以使用這一命令列出排行榜前 100 名玩家。ZRANGEBYSCORE 返回指定分?jǐn)?shù)范圍內(nèi)的要素(例如列出得分為 1000 至 2000 之間的玩家),ZRNK 則返回有序集內(nèi)的要素的排名,諸如此類。
5. 布隆(Bloom)過濾器布隆過濾器 (Bloom filter) 是一種空間利用率較高的概率數(shù)據(jù)結(jié)構(gòu),用來測試某元素是否某個集的一員??赡軙霈F(xiàn)誤報匹配,但不會漏報。查詢可返回“可能在集內(nèi)”或“肯定不在集內(nèi)”。
就在線服務(wù)和離線服務(wù)包括大數(shù)據(jù)分析等方面,布隆過濾器數(shù)據(jù)結(jié)構(gòu)都能派上很多用場。Facebook 利用布隆過濾器進(jìn)行輸入提示搜索,為用戶輸入的查詢提取朋友和朋友的朋友。Apache HBase 則利用布隆過濾器過濾掉不包含特殊行或列的 HFile 塊磁盤讀取,使讀取速度得到明顯提升。Bitly 用布隆過濾器來避免將用戶重定向到惡意網(wǎng)站,而 Quara 則在訂閱后端執(zhí)行了一個切分的布隆過濾器,用來過濾掉之前查看過的內(nèi)容。在我自己的項目里,我用布隆過濾器追蹤用戶對各個主題的投票情況。
借助出色的速度和處理能力,Redis 極好地融合了布隆過濾器。搜索 GitHub,就能發(fā)現(xiàn)很多 Redis 布隆過濾器項目,其中一些還支持可調(diào)諧精度。
6. 高效的全局通知:發(fā)布/訂閱渠道Redis 發(fā)布/訂閱渠道的工作方式類似于一個扇出消息傳遞系統(tǒng),或 JMS 語義中的一個主題。JMS 主題和 Redis 發(fā)布/訂閱渠道的一個區(qū)別是,通過 Redis 發(fā)布的消息并不持久。消息被推送給所有相連的客戶端后,Redis 上就會刪除這一消息。換句話說,訂閱者必須一直在線才能接收新消息。Redis 發(fā)布/訂閱渠道的典型用例包括實時配置分布、簡單的聊天服務(wù)器等。
在 web 服務(wù)器集群中,每個節(jié)點都可以是 Redis 發(fā)布/訂閱渠道的一個訂閱者。發(fā)布到渠道上的消息也會被即時推送到所有相連節(jié)點。這一消息可以是某種配置更改,也可以是針對所有在線用戶的全局通知。和恒定輪詢相比,這種推送溝通模式顯然極為高效。
Redis 性能優(yōu)化Redis 非常強大,但也可以從整體上和根據(jù)特定編程場景做出進(jìn)一步優(yōu)化??梢钥紤]以下技巧。
存活時間所有 Redis 數(shù)據(jù)結(jié)構(gòu)都具備存活時間 (TTL) 屬性。當(dāng)你設(shè)置這一屬性時,數(shù)據(jù)結(jié)構(gòu)會在過期后自動刪除。充分利用這一功能,可以讓 Redis 保持較低的內(nèi)存損耗。
管道技術(shù)在一條請求中向 Redis 發(fā)送多個命令,這種方法叫做管道技術(shù)。這一技術(shù)節(jié)省了網(wǎng)絡(luò)往返的成本,這一點非常重要,因為網(wǎng)絡(luò)延遲可能比 Redis 延遲要高上好幾個量級。但這里存在一個陷阱:管道中的 Redis 命令列表必須預(yù)先確定,并且應(yīng)當(dāng)彼此獨立。如果一個命令的參數(shù)是由先前命令的結(jié)果計算得出,管道技術(shù)就不起作用。列表 5 給出了 Redis 管道技術(shù)的一個示例。
列表 5:管道技術(shù)
@Override public List副本集和切分fetchLeaderboard(String key, String... playerIds) { final List entries = new ArrayList<>(); redisTemplate.executePipelined(new RedisCallback
Redis 支持主從副本配置。和 MongoDB 一樣,副本集也是不對稱的,因為從節(jié)點是只讀的,以便共享讀取工作量。我在文章開頭提到過,也可以執(zhí)行切分來橫向擴(kuò)展 Redis 的處理能力和存儲容量。事實上,Redis 非常強大,據(jù)亞馬遜公司的內(nèi)部基準(zhǔn)顯示,類型 r3.4xlarge 的一個 EC2 實例每秒可輕松處理 100000 次請求。傳說還有把每秒 700000 次請求作為基準(zhǔn)的。對于中小型應(yīng)用程序,通常無需考慮 Redis 切分。(請參見這篇非常出色的文章《運行中的 Redis》,進(jìn)一步了解 Redis 的性能優(yōu)化和切分。)
Redis 中的事務(wù)Redis 并不像關(guān)系數(shù)據(jù)庫管理系統(tǒng)那樣能支持全面的 ACID 事務(wù),但其自有的事務(wù)也非常有效。從本質(zhì)上來說,Redis 事務(wù)是管道、樂觀鎖、確定提交和回滾的結(jié)合。其思想是執(zhí)行一個管道中的一個命令列表,然后觀察某一關(guān)鍵記錄的潛在更新(樂觀鎖)。根據(jù)所觀察的記錄是否會被另一個進(jìn)程更新,該命令列表或整體確定提交,或完全回滾。
下面以某個拍賣網(wǎng)站上的賣方庫存為例。買方試圖從賣方處購買某件商品時,你負(fù)責(zé)觀察 Redis 事務(wù)內(nèi)的賣方庫存變化。同時,你要從同一個庫存中刪除此商品。事務(wù)關(guān)閉前,如果庫存被一個以上進(jìn)程觸及(例如,如果兩個買方同時購買了同一件商品),事務(wù)將回滾,否則事務(wù)會確定提交。回滾后可開始重試。
Spring Data Redis 中的事務(wù)陷阱我在 Spring 的 RedisTemplate 類 redisTemplate.setEnableTransactionSupport(true); 中啟用 Redis 事務(wù)時得到一個慘痛的教訓(xùn):Redis 會在運行幾天后開始返回垃圾數(shù)據(jù),導(dǎo)致數(shù)據(jù)嚴(yán)重?fù)p壞。StackOverflow 上也報道了類似情況。
在運行一個 monitor 命令后,我的團(tuán)隊發(fā)現(xiàn),在進(jìn)行 Redis 操作或 RedisCallback 后,Spring 并沒有自動關(guān)閉 Redis 連接,而事實上它是應(yīng)該關(guān)閉的。如果再次使用未關(guān)閉的連接,可能會從意想不到的 Redis 密鑰返回垃圾數(shù)據(jù)。有意思的是,如果在 RedisTemplate 中把事務(wù)支持設(shè)為 false,這一問題就不會出現(xiàn)了。
我們發(fā)現(xiàn),我們可以先在 Spring 語境里配置一個 PlatformTransactionManager(例如 DataSourceTransactionManager),然后再用 @Transactional 注釋來聲明 Redis 事務(wù)的范圍,讓 Spring 自動關(guān)閉 Redis 連接。
根據(jù)這一經(jīng)驗,我們相信,在 Spring 語境里配置兩個多帶帶的 RedisTemplate 是很好的做法:其中一個 RedisTemplates 的事務(wù)設(shè)為 false,用于大多數(shù) Redis 操作,另一個 RedisTemplates 的事務(wù)已激活,僅用于 Redis 事務(wù)。當(dāng)然必須要聲明 PlatformTransactionManager 和 @Transactional,以防返回垃圾數(shù)值。
另外,我們還發(fā)現(xiàn)了 Redis 事務(wù)和關(guān)系數(shù)據(jù)庫事務(wù)(在本例中,即 JDBC)相結(jié)合的不利之處?;旌闲褪聞?wù)的表現(xiàn)和預(yù)想的不太一樣。
結(jié)論我希望通過這篇文章向其他 Java 企業(yè)開發(fā)師介紹 Redis 的強大之處,尤其是將 Redis 用作遠(yuǎn)程數(shù)據(jù)緩存和用于易揮發(fā)數(shù)據(jù)時。在這里我介紹了 Redis 的六個有效用例,分享了一些性能優(yōu)化技巧,還說明了我的 Glu Mobile 團(tuán)隊怎樣解決了 Spring Data Redis 事務(wù)配置不當(dāng)造成的垃圾數(shù)據(jù)問題。我希望這篇文章能夠激發(fā)你對 Redis NoSQL 的好奇心,讓你能夠受到啟發(fā),在自己的 Java 企業(yè)版系統(tǒng)里創(chuàng)造出一番天地。
本文系 OneAPM 工程師編譯整理。OneAPM 能為您提供端到端的 Java 應(yīng)用性能解決方案,我們支持所有常見的 Java 框架及應(yīng)用服務(wù)器,助您快速發(fā)現(xiàn)系統(tǒng)瓶頸,定位異常根本原因。分鐘級部署,即刻體驗,Java 監(jiān)控從來沒有如此簡單。想閱讀更多技術(shù)文章,請訪問 OneAPM 官方技術(shù)博客。
本文轉(zhuǎn)自 OneAPM 官方博客
原文地址:http://www.javaworld.com/article/3062899/big-data/lightning-fast-nosql-with-spring-data-redis.html?page=2
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/64852.html
摘要:以遠(yuǎn)程緩存服務(wù)器見長,對易揮發(fā)數(shù)據(jù)來說是極快型數(shù)據(jù)庫。即使成功寫入數(shù)據(jù)庫,最后也可能會因為網(wǎng)絡(luò)故障而使得緩存服務(wù)器以失敗告終。 【編者按】本文作者為 Xinyu Liu,詳細(xì)介紹了 Redis 的特性,并輔之以豐富的用例。在本文的第一部分,將重點概述 Redis 的方方面面。文章系國內(nèi) ITOM 管理平臺 OneAPM 編譯呈現(xiàn)。 建立在 Java 企業(yè)版之上的多層體系結(jié)構(gòu)是強大的服務(wù)...
摘要:全球最大的控件提供商葡萄城宣布,新一代純前端控件發(fā)布版本,進(jìn)一步增強產(chǎn)品功能,并支持在上的安裝和發(fā)布,極大的提升了產(chǎn)品的易用性。葡萄城的控件和軟件產(chǎn)品在國內(nèi)外屢獲殊榮,在全球被數(shù)十萬家企業(yè)學(xué)校和政府機(jī)構(gòu)廣泛應(yīng)用。 全球最大的控件提供商葡萄城宣布,新一代純前端控件 WijmoJS 發(fā)布2018 v1 版本,進(jìn)一步增強產(chǎn)品功能,并支持在 Npm 上的安裝和發(fā)布,極大的提升了產(chǎn)品的易用性。 ...
摘要:全球最大的控件提供商葡萄城宣布,新一代純前端控件發(fā)布版本,進(jìn)一步增強產(chǎn)品功能,并支持在上的安裝和發(fā)布,極大的提升了產(chǎn)品的易用性。葡萄城的控件和軟件產(chǎn)品在國內(nèi)外屢獲殊榮,在全球被數(shù)十萬家企業(yè)學(xué)校和政府機(jī)構(gòu)廣泛應(yīng)用。 全球最大的控件提供商葡萄城宣布,新一代純前端控件 WijmoJS 發(fā)布2018 v1 版本,進(jìn)一步增強產(chǎn)品功能,并支持在 Npm 上的安裝和發(fā)布,極大的提升了產(chǎn)品的易用性。 ...
閱讀 1404·2021-11-22 15:11
閱讀 2843·2019-08-30 14:16
閱讀 2761·2019-08-29 15:21
閱讀 2920·2019-08-29 15:11
閱讀 2461·2019-08-29 13:19
閱讀 2992·2019-08-29 12:25
閱讀 423·2019-08-29 12:21
閱讀 2838·2019-08-29 11:03