1. 程式人生 > >Redis基於eval的多欄位原子增量計算

Redis基於eval的多欄位原子增量計算

目錄

目錄 1

1. 前言 1

2. 優點 1

3. 方法一:使用struct 2

3.1. 設定初始值(覆蓋原有的,如果存在) 2

3.2. 查詢k1的值 2

3.3. 設定初始值(覆蓋原有的,如果存在) 2

3.4. 查詢k1的值 2

3.5. 增量操作(增1) 2

3.6. 查詢k1的值 3

3.7. 增量操作(增1) 3

3.8. 查詢k1的值 3

3.9. pack和unpack 3

3.10. AOF檔案 3

3.11. 進化的增量操作 4

3.12. 進化的查詢操作 4

4. 方法二:使用CJSON 5

5. 方法三:使用CMSGPACK 5

6. 方法四:藉助Redis的module 5

6.1. 參考一:rediSQL 5

6.2. 參考二:ReJSON 5

1. 前言

一些應用場景需要對多個值進行原子計數,Redis的eval+hincrby可以達到目標,但如果計算的欄位比較多時,效率會是個問題,它的時間複雜度為O(N),而且對於查詢也同樣如此。如果能將所有欄位作為一個個struct成員,時間複雜度會固定下來。如果能象C/C++中的引用或指標操作,時間複雜度可以降低到O(1),否則考慮先get再set,這樣時間複雜度為O(2),當欄位數較多時,比如達到10個甚至更多時,相比O(N)就好了許多。

2. 優點

1)不需要hash,普通kv即可實現多欄位的計數,而且是原子操作

2)當欄位較多時,效能不會線性下降(hincrby多欄位操作效能會線性下降,因為多欄位hincrby操作時間複雜度為O(n))

3)當欄位較多時,查詢效能不變,保持為O(1)

3. 方法一:使用struct

3.1. 設定初始值(覆蓋原有的,如果存在)

呼叫struct的pack函式打包(序列化)兩個欄位的值56和78,並將該struct賦值給本地變數a(注意Redis內的lua不支援全域性變數,如果需要全域性變通,可變通使用Redis的KV),然後將變數a作為k1的值設定進去。

127.0.0.1:6379> EVAL 'local a;a=struct.pack("ll",56,78);local x=redis.call("set","k1",a);return x;' 0

OK

3.2. 查詢k1的值

因為struct是個二進位制值,因為取到值時,需要先unpack反序列化(解包)。

127.0.0.1:6379> eval 'local x=redis.call("get","k1");local m,n=struct.unpack("ll",x);return {m,n}' 0

1) (integer) 56

2) (integer) 78

3.3. 設定初始值(覆蓋原有的,如果存在)

127.0.0.1:6379> EVAL 'local a;a=struct.pack("lll",56,78,99);local x=redis.call("set","k1",a);return x;' 0

OK

3.4. 查詢k1的值

127.0.0.1:6379> eval 'local x=redis.call("get","k1");local m,n,l=struct.unpack("lll",x);return {m,n,l}' 0

1) (integer) 56

2) (integer) 78

3) (integer) 99

3.5. 增量操作(增1)

這內含兩個Redis操作:get和set兩個操作,因此時間複雜度為O(2)。

127.0.0.1:6379> eval 'local x=redis.call("get","k1");local m,n,l=struct.unpack("lll",x);m=m+1;n=n+1;l=l+1;local a=struct.pack("lll",m,n,l);local z=redis.call("set","k1",a);return z;' 0

OK

3.6. 查詢k1的值

127.0.0.1:6379> eval 'local x=redis.call("get","k1");local m,n,l=struct.unpack("lll",x);return {m,n,l}' 0

1) (integer) 57

2) (integer) 79

3) (integer) 100

3.7. 增量操作(增1)

127.0.0.1:6379> eval 'local x=redis.call("get","k1");local m,n,l=struct.unpack("lll",x);m=m+1;n=n+1;l=l+1;local a=struct.pack("lll",m,n,l);local z=redis.call("set","k1",a);return z;' 0

OK

3.8. 查詢k1的值

127.0.0.1:6379> eval 'local x=redis.call("get","k1");local m,n,l=struct.unpack("lll",x);return {m,n,l}' 0

1) (integer) 58

2) (integer) 80

3) (integer) 101

3.9. pack和unpack

Redis內建支援struct,pack和unpack中的第一個引數為格式引數,其中單個“l”表示有符號long型別,大寫的“L”則表示無符號的long型別,更多可以參見eval命令的說明:https://redis.io/commands/eval

3.10. AOF檔案

上述操作對應的AOF檔案內容:

$ tail -f appendonly-6379.aof 

*2

$6

SELECT

$1

0

*3

$4

EVAL

$76

local a;a=struct.pack("ll",56,78);local x=redis.call("set","k1",a);return x;

$1

0

 

*3

$4

EVAL

$80

local a;a=struct.pack("lll",56,78,99);local x=redis.call("set","k1",a);return x;

$1

0

 

*3

$4

eval

$159

local x=redis.call("get","k1");local m,n,l=struct.unpack("lll",x);m=m+1;n=n+1;l=l+1;local a=struct.pack("lll",m,n,l);local z=redis.call("set","k1",a);return z;

$1

0

 

*3

$4

eval

$159

local x=redis.call("get","k1");local m,n,l=struct.unpack("lll",x);m=m+1;n=n+1;l=l+1;local a=struct.pack("lll",m,n,l);local z=redis.call("set","k1",a);return z;

$1

0

3.11. 進化的增量操作

可用於生產環境的增量操作,允許被操作的key不存在(大小超過200位元組):

eval 'local x=redis.call("get",KEYS[1]); local m,n,l;if (x) then m,n,l=struct.unpack("lll",x);m=m+ARGV[1];n=n+ARGV[2];l=l+ARGV[3]; else m=ARGV[1];n=ARGV[2];l=ARGV[3]; end; local a=struct.pack("lll",m,n,l);local z=redis.call("set",KEYS[1],a);return z;' 1 "k1" 1 2 3

3.12. 進化的查詢操作

可用於生產環境的查詢操作,允許被查詢的key不存在:

eval 'local x=redis.call("get",KEYS[1]); if (x) then local m,n,l=struct.unpack("lll",x);return {m,n,l}; else return x; end;' 1 "k1"

4. 方法二:使用CJSON

暫略。

5. 方法三:使用CMSGPACK

暫略。

6. 方法四:藉助Redis的module

6.1. 參考一:rediSQL

https://github.com/RedBeardLab/rediSQL

6.2. 參考二:ReJSON

https://github.com/RedisLabsModules/ReJSON