在之前的文章《 如何優雅地使用Redis之位圖操作 》和《 再談如何優雅地使用Redis之位圖操作
》中,筆者介紹了關于Redis位圖操作的進階應用,其中就講到了如何優雅地實作getbits。Redis官方提供了getbit指令,其可以擷取某個key對應比特位的比特值,而getbits顧名思義就是支援一次性擷取多個比特位的比特值的指令,遺憾的是,Redis官方并沒有提供getbits指令。在上述2篇文章中,筆者是通過解析位元組數組的方式來實作getbits指令的,雖然可以實作,但是卻有2個不足之處:1、這種方式實作的getbits指令不是原子性的,因為這種方式實作的getbits指令其實是分2步進行的,先讀取位元組數組,再解析位元組數組,在這2個步驟之間,Redis是可以執行其他指令的,是以可能會出現資料不一緻的現象。2、當存儲的位圖資料空間占用比較大時,一次性讀取整個位元組數組,會造成Redis伺服器阻塞,嚴重的還會造成用戶端記憶體溢出,雖然可以通過分多次去讀取位元組數組來避免這個問題,但是這樣一來就增加了網絡開銷,不是特别優雅。
今天給大家介紹一種通過Redis原生指令bitfield實作setbits和getbits的方法。
bitfield指令
首先介紹一下bitfield指令的用途。官方對bitfield指令的介紹是:通過bitfield指令可以一次性操作多個比特位域,它會執行一系列操作并傳回一個響應數組,這個數組中的元素對應參數清單中的相應操作的執行結果。說白了就是通過bitfield指令我們可以一次性對多個比特位域進行操作。需要注意的是,這裡提到的是比特位域,不是比特位,所謂比特位域,指的是連續的多個比特位,也就是說,bitfield不僅僅可以對多個單個的比特位進行操作,還支援對多個比特位域進行操作,是以功能是十分強大的。
這裡的操作指的是以下幾種:
- GET <type> <offset>
- SET <type> <offset> <value>
- INCRBY <type> <offset> <increment>
其中,get指令的作用是讀取指定位域的值,set指令的作用是設定指定位域的值并傳回舊的值,increby指令的作用是增加或減少指定位域的值并傳回新的值。
舉個例子:
BITFIELD mykey SET i5 100 10 GET u4 2
這個指令包含了2個子操作,分别是SET i5 100 10和GET u4 2。SET i5 100 10的作用是從第100位開始,将接下來的5位用有符号數10代替,其中i表示的是有符号整數。GET u4 2的作用是從第2位開始,将接下來的4位當成無符号整數并取出,其中u表示的是無符号整數。
上述隻是bitfield的一部分應用,實際上bitfield還有很多更進階的用法,有興趣的可以去Redis官網查閱,這裡就不再詳細介紹了。
實作setbits
接下來說下如果使用bitfield實作setbits原子指令。如果我們要将某一位(比如說第3位)設定為1,可以使用這樣的子操作:set u1 3 1,同樣地,如果我們需要将多個位設定成1,隻要将多個set子操作拼接在一起可以了。舉個例子:如果我們要将第1位、第3位、第6位設定成1,則可以使用如下指令:
bitfield k1 set u1 1 1 set u1 3 1 set u1 6 1
其中k1指的是key。
我們可以寫個程式驗證下。驗證的方法是先使用上述指令對相應的比特位進行設值,然後使用redis原生的getbit指令周遊每個比特位,看看是不是對應的位都可以被設定成1。
程式如下:
@Test
public void testBitField1(){
Jedis jedis=new Jedis(HOST,PORT);
String key="test_"+System.currentTimeMillis();
jedis.bitfield(key,"set","u1","1","1","set","u1","3","1","set","u1","6","1");
for(int i=0;i<8;i++){
System.out.println(i+"---"+jedis.getbit(key,i));
}
}
我們看下結果輸出:

可以看到,第1位、第3位、第6位都被設定成了true,而其他位還是原始值flase。是以這種方式确實可以實作一次性對多個比特位進行設值。
實作getbits
接下來我們使用bitfield指令來實作getbits原子指令。如果想讀取某個位(比如說第3位)的比特值,可以使用這樣的子操作:get u1 3。如果需要同時讀取多個比特位,則隻需要将多個get子操作拼接在一起就可以了。還是舉個例子,如果我們想同時讀取第2位、第4位、第7位的比特值,可以使用如下指令:
bitfield k1 get u1 2 get u1 4 get u1 7
我們還是寫個程式驗證下。驗證的方式是使用Redis原生的setbit指令分别将第2位、第4位、第7位比特位設定成1,然後使用上述bitfield指令分别讀取每個比特位的值,看看是否隻有對應的比特位被設定成了1。
@Test
public void testBitField2(){
Jedis jedis=new Jedis(HOST,PORT);
String key="test_"+System.currentTimeMillis();
jedis.setbit(key,2,true);
jedis.setbit(key,7,true);
jedis.setbit(key,4,true);
"get", "u1", "1",
List<Long> result = jedis.bitfield(key,
"get", "u1", "2",
"get", "u1", "6",
"get", "u1", "3",
"get", "u1", "4",
"get", "u1", "5",
"get", "u1", "7",
}
"get", "u1", "8");
System.out.println(result);
結果輸出如下:
可以看到,第2位、第4位、第7位都被設定成了1,而其他位還是原始的值0。說明确實可以使用bitfield指令來實作getbits。
總結
使用bitfield實作getbits和setbits的好處有2個:1、原子性保證,由于所有操作都是在一個bitfield指令中完成的,是以可以保證操作的原子性。2、由于這種方式是在Redis服務端解析後再傳回給用戶端的,用戶端并不需要一次性讀取整個位元組數組,是以不會造成用戶端記憶體溢出。
原文釋出時間為:2018-08-11
本文作者:黃澤傑
本文來自雲栖社群合作夥伴“
Java架構沉思錄”,了解相關資訊可以關注“
”。