用redis的scan命令代替keys命令,以及在spring-data-redis中遇到的問題

來源:互聯網
上載者:User

標籤:iter   網路傳輸   成本   pop   row   遊標   connect   redist   assert   

摘要

本文主要是介紹使用redis scan命令遇到的一些問題總結,scan命令本身沒有什麼問題,主要是spring-data-redis的問題。

需求

需要遍曆redis中key,找到符合某些pattern的所有keys。第一反應當然是

KEYS "ABC*

可以找到首碼是ABC的所有KEYS,時間複雜度O(N)。可以使用,但是在生產環境中,這麼使用肯定是不行的,因為生產環境的key的數量比較多,一次查詢會block其他動作。而更重要的是一次性返回這麼多的key,資料量比較大,網路傳輸成本高。所以一般生產環境中去找符合某些條件的KEYS一般使用SCAN 或 Sets。

集合來操作比較好理解,一個個的pop出來,但是相當於在原有的資料結構上多了一個keys的set集合。SCAN的不需要多維護這份列表。

SCAN 命令

SCAN命令的有SCAN,SSCAN,HSCAN,ZSCAN。 
SCAN的話就是遍曆所有的keys 
其他的SCAN命令的話是SCAN選中的集合。 
SCAN命令是增量的迴圈,每次調用只會返回一小部分的元素。所以不會有KEYS命令的坑。 
SCAN命令返回的是一個遊標,從0開始遍曆,到0結束遍曆。

scan 01) "655"2)  1) "test1"    2) "test2"

傳回值一個array,一個是下次迴圈的cursorId,一個是元素數組。SCAN命令不能保證每次返回的值都是有序的,另外同一個key有可能返回多次,不做區分,需要應用程式去處理。

另外SCAN命令可以指定COUNT,預設是10。但是這個並不是指定多少,就能返回多少,這隻是一個提示,並不能保證一定返回這麼多條。

spring-data-redis SCAN命令的坑

拋出NoSuchElementException 錯誤

 RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();        Cursor c = redisConnection.scan(scanOptions);        while (c.hasNext()) {            c.next();        }    java.util.NoSuchElementException at java.util.Collections$EmptyIterator.next(Collections.java:4189) at org.springframework.data.redis.core.ScanCursor.moveNext(ScanCursor.java:215) at org.springframework.data.redis.core.ScanCursor.next(ScanCursor.java:202)

這個錯誤發生在spring-data-redis-1.6版本中。已經被修掉了, 
https://github.com/spring-projects/spring-data-redis/pull/154

看到最後comments 1.5.x 和1.6.x中都修複了,但是不知道為什麼1.6.0沒有修複。

看下ScanCursor.java 源碼,異常時next()方法拋出來的,產生的原因是沒有next的元素了。在前面介紹過,SCAN命令返回兩個一個cursorId,一個是值數組。即使你指定了返回多少條(COUNT),也不能保證實際會返回多少條,當然包括返回0條。這種情況不會經常發生,當你redis server中有大量小的集合時,而掃描時又掃不到匹配的keys,就會返回0個結果,但這並不表示掃描結束,掃描結束的唯一判斷依據是掃描結果返回的cursor = 0

@Overridepublic T next() {    assertCursorIsOpen();    if (!hasNext()) {        throw new NoSuchElementException("No more elements available for cursor " + cursorId + ".");    }    T next = moveNext(delegate);    position++;    return next;}

 

這個錯誤最好的解決辦法是升級spring-data-redis版本。如果沒法升級,只能在程式中捕獲這個異常,再發一次scan請求。而不是依賴spring-data-redis中的scan請求發送。

多線程環境使用的坑

返回這種錯誤,

    java.lang.ClassCastException: java.lang.Long cannot be cast to java.util.List    at redis.clients.jedis.Connection.getRawObjectMultiBulkReply(Connection.java:230)    at redis.clients.jedis.Connection.getObjectMultiBulkReply(Connection.java:236)

或者unknown reply錯誤。

這個的原因是在一次full 掃描期間,發送一次scan請求,返回遊標結果,connection釋放掉了,再發送scan請求時,又拿到一個新的串連。這個在單線程環境下,沒有問題,但是在多線程環境下,一般來說沒有問題,因為scan 命令server沒有狀態,只有一個cursorId。一個線程scan一次完了,釋放掉串連,再發送時,拿到一個新的串連,沒有問題,但是如果拿到其他線程的串連就會出現上述問題。

這個問題在spring-data-redis 1.8 RC1 版本修複。就是每個scan操作的cursor維護一個connection。

如果低版本需要修複的話,就是串連不要交給spring-data-redis管理了,擷取一個串連,自己維護。

用redis的scan命令代替keys命令,以及在spring-data-redis中遇到的問題

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.