2016-07-06 44 views
4

我成功地使用Redis編寫了文本搜索和其他條件的交集。爲了達到這個目的,我使用了一個Lua腳本。問題是我不僅在閱讀,而且還在寫這個腳本的價值。從Redis 3.2開始,可以通過調用redis.replicate_commands()來實現,但不能在3.2之前完成。如何使用Redis將搜索文本與其他標準結合起來?

以下是我如何存儲值。

名稱

​​

價格

> ZADD product:price 49.90 1 
> ZADD product:price 54.90 2 

然後,爲了獲取匹配'ice',例如,我呼籲所有產品:

> HSCAN product:name 0 MATCH *ice* 

然而,由於HSCAN使用遊標,我必須多次調用它才能獲取所有結果。這是我使用的Lua腳本:

local cursor = 0 
local fields = {} 
local ids = {} 
local key = 'product:name' 
local value = '*' .. ARGV[1] .. '*' 

repeat 
    local result = redis.call('HSCAN', key, cursor, 'MATCH', value) 
    cursor = tonumber(result[1]) 
    fields = result[2] 
    for i, id in ipairs(fields) do 
     if i % 2 == 0 then 
      ids[#ids + 1] = id 
     end 
    end 
until cursor == 0 
return ids 

因爲它不可能與另一個呼叫使用腳本的結果,如SADD key EVAL(SHA) ...。而且,腳本中不可能使用全局變量。我已經改變了部分領域的循環內訪問腳本之外的ID列表:

if i % 2 == 0 then 
    ids[#ids + 1] = id 
    redis.call('SADD', KEYS[1], id) 
end 

我不得不添加redis.replicate_commands()到第一線。通過此更改,我可以從調用腳本時傳遞的密鑰中獲取所有ID(請參閱KEYS[1])。

最後,拿到名單100個產品ID的售價40和50,在名稱中包含「冰」之間,我做了以下內容:

> ZUNIONSTORE tmp:price 1 product:price WEIGHTS 1 
> ZREMRANGEBYSCORE tmp:price 0 40 
> ZREMRANGEBYSCORE tmp:price 50 +INF 
> EVALSHA b81c2b... 1 tmp:name ice 
> ZINTERSTORE tmp:result tmp:price tmp:name 
> ZCOUNT tmp:result -INF +INF 
> ZRANGE tmp:result 0 100 

我用ZCOUNT調用預先知道我會有很多結果頁,做count/100

正如我之前所說的,這與Redis 3.2很好地協作。但是當我試圖在AWS上運行代碼時,它只支持Redis高達2.8,我無法再使它工作了。我不知道如何使用HSCAN遊標進行迭代,而無需使用腳本或者不使用腳本進行寫入。有一種方法可以使它在Redis 2.8上運行?

一些注意事項:

  1. 我知道我可以Redis的外面做加工的部分(比如迭代光標或交叉匹配),但它會影響到應用的整體性能。
  2. 我不想自己部署Redis實例來使用3.2版本。
  3. 上面的標準(價格範圍和名稱)只是一個簡單的例子。我有其他領域和類型的比賽,不僅僅是那些。
  4. 我不確定我存儲數據的方式是否是最好的方法。我願意聽取關於它的建議。
+0

@KevinChristopherHenry以下調用需要複製:'redis.call('SADD',KEYS [1],id)'。 –

回答

1

的問題是不是你寫的數據庫,它是你正在做一個HSCAN後寫,這是一個非確定性的命令。

在我看來,很少有很好的理由在Lua腳本中使用SCAN命令。該命令的主要目的是允許您以小批量操作,因此您不會鎖定處理大型密鑰空間(或密鑰空間)的服務器。由於腳本是原子性的,但使用HSCAN並不能解決問題 - 您仍在鎖定服務器,直到完成整個任務。

這裏是選擇我可以看到:

如果你不能冒險與冗長的命令鎖定了服務器:

  1. 在客戶端上使用HSCAN。這是最安全的選擇,但也是最慢的。

如果你想在單個原子的Lua命令儘可能做盡可能多的處理:

  • 使用Redis的3.2和腳本影響的複製。
  • 在腳本中執行掃描,但將值返回給客戶端並從那裏開始寫入。 (也就是Karthikeyan Gopall的回答。)
  • 而不是HSCAN,在腳本中執行HKEYS並使用Lua的模式匹配過濾結果。由於HKEYS是確定性的,因此後續寫入不會有問題。不利的一面是,你必須首先閱讀所有的鍵,而不管它們是否符合你的模式。 (儘管HSCAN也是散列大小中的O(N)。)
  • +0

    我不知道如何使用'HKEYS'來執行'MATCH',就像我使用'HSCAN'一樣。 –

    +0

    @GustavoStraube:你必須在Lua中進行過濾。我已經用我可以看到的選項更新了答案。 –

    2

    我在這裏發現的唯一問題是將值存儲在lua scirpt中。因此,不要將它們存儲在lua中,而要在lua之外獲取該值(返回string []的值)。使用sadd(鍵,成員[])將它們存儲在一個不同的調用中。然後繼續交叉並返回結果。

    > ZUNIONSTORE tmp:price 1 product:price WEIGHTS 1 
    > ZREVRANGEBYSCORE tmp:price 0 40 
    > ZREVRANGEBYSCORE tmp:price 50 +INF 
    > nameSet[] = EVALSHA b81c2b... 1 ice 
    > SADD tmp:name nameSet 
    > ZINTERSTORE tmp:result tmp:price tmp:name 
    > ZCOUNT tmp:result -INF +INF 
    > ZRANGE tmp:result 0 100 
    

    IMO您的設計是最優化的。一個建議是儘可能使用管道,因爲它會一次處理所有事情。

    希望這有助於

    UPDATE 有在Lua像數組沒有這樣的事情([]),你必須使用lua的表來實現它。在你的腳本中你正確地返回了id,它本身就是一個數組,你可以用它作爲一個單獨的調用來實現sadd。

    String [] nameSet = (String[]) evalsha b81c2b... 1 ice -> This is in java 
    SADD tmp:name nameSet 
    

    而對應的lua腳本與第一個腳本相同。

    local cursor = 0 
    local fields = {} 
    local ids = {} 
    local key = 'product:name' 
    local value = '*' .. ARGV[1] .. '*' 
    
    repeat 
        local result = redis.call('HSCAN', key, cursor, 'MATCH', value) 
        cursor = tonumber(result[1]) 
        fields = result[2] 
        for i, id in ipairs(fields) do 
         if i % 2 == 0 then 
          ids[#ids + 1] = id 
         end 
        end 
    until cursor == 0 
    return ids 
    
    +0

    我不知道可以將腳本的結果存儲在變量中。真棒!我今天晚些時候會試一試,並告訴你它是否有效。 –

    +0

    是的,這是可能的我已經嘗試過你的示例場景,它工作正常。我已經使用Jedis庫在Java中實現這個功能 –

    +0

    嘿!嘗試將腳本結果分配給該變量時出現以下錯誤:'ERR未知命令'nameSet []'' –

    相關問題