redis學習筆記——不僅僅是存取資料
最近專案中用到比較多redis,感覺之前對它一直侷限於get/set資料的層面。其實作為一個強大的NoSql資料庫產品,如果好好利用它,會帶來很多意想不到的效果。(因為我搞java,所以就從jedis的角度來補充一點東西吧。PS:不一定全,只是個人理解,不喜勿噴)
1、關於JedisPool.returnSource(Jedis jeids)
這個方法是從redis的池中釋放一個redis連線的,類似執行緒池對執行緒的回收管理的。看下面的demo程式碼:
public static void main(String[] args) { Jedis jedis = JedisFactory.get();//這個方法是用來得到redis例項的,這裡省略 Jedis jedis2 = JedisFactory.get(); jedis.set("key1", "good job"); jedis.set("key2", "holy crap"); System.out.println(jedis.get("key1") + ", " + jedis2.get("key2")); JedisFactory.close(jedis);//這個方法是用來回收redis例項的,這裡省略 System.out.println("redis池回收jedis1之後:"); System.out.println(jedis.get("key1") + ", " + jedis2.get("key2")); }
在開啟redis伺服器後,列印結果如下:
good job, holy crap
redis池回收jedis1之後:
good job, holy crap
上述demo程式碼說明了兩點:
1)redis池中不同redis例項儲存的資料可以共享——jedis和jeids2都是通過JedisPool.getResource()方法得到的,即它們都是從池中得到的不同例項,但是jeids set的資料可以被jeids2 get到
2)呼叫池的JedisPool.returnResource(Jedis jedis)之後,並不會把jedis set的資料刪掉。
於是,引出我們常見的一個異常:
java.util.NoSuchElementException: Timeout waiting for idle object
這個異常表明沒有得到空閒的redis例項,從而引起超時。
老生常談的問題是,在try{ ... } catch(Exception e){ ... } finally{ ... }程式碼結構中,必須把JedisPool.returnResource(Jedis jedis)放在finally中,這樣無論是否發生異常,jedis都能很好的被歸還到池中,以便下次被其他執行緒使用。而經過上述demo的測試表明——return給池後,並不需要擔心資料會被刪除!這也是我之前一直困惑的地方!
2、redis的key的管理——當key越來越多了咋辦?
redis是可以存放很多資料,並且都是以k-v形式存放的。它也常常被使用在高併發的環境下,資料肯定也會越來越多。這時候問題來了——如何避免key越來越多,而其實大多時候資料只是暫時快取一下而已,並不需要我們長久儲存在記憶體中。這時候管理key就顯得很重要了。看下面的demo程式碼:
public static void main(String[] args) {
Jedis jedis = JedisFactory.get();
Jedis jedis2 = JedisFactory.get();
jedis.set("test1", "well done");
jedis.set("test2", "pain ends");
System.out.println(jedis.get("test1") + " " + jedis2.get("test2"));
jedis2.del("test1");
jedis2.expire("test2", 12);
System.out.println(jedis.get("test1") + " " + jedis2.get("test2"));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10s後:" + jedis.get("test1") + " " + jedis2.get("test2"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("再過3s後:" + jedis.get("test1") + " " + jedis2.get("test2"));
}
程式碼跑完後,列印結果為:
well done pain ends
null pain ends
10s後:null pain ends
再過3s後:null null
列印結果表明:對key的刪除操作和設定key的過期時間效果等同——都會在某段時間後刪除資料,包括對應的key。在專案中,刪除資料的時間點往往不好控制,所以最好是設定key的過期時間,該key會在指定過期時間後自動被記憶體刪除。從而不會一直佔用redis的記憶體。
當然設定redis中某個key的過期時間,必須在儲存該資料的操作之後再進行,否則也會無效~
3、redis庫的“分割槽”
redis預設會有16個"庫"——我們可以把它理解成16個記憶體分割槽。而我們預設是使用第一個庫即:select(0),下面的demo:
Jedis jedis = JedisFactory.get();//這個方法是用來得到redis例項的,這裡省略
Jedis jedis2 = JedisFactory.get();
System.out.println(jedis == jedis2);
jedis.set("key1", "good job");
// jedis.set("key2", "holy crap");
jedis2.select(1);
System.out.println(jedis.get("key1") + ", " + jedis2.get("key1"));
跑出來的結果為:
false
good job, null
說明jedis2沒有get到jedis1 set的東西,這是因為jedis2.select(1);它選擇了第二個庫,自然獲取不到第一個庫裡set的資料了。如果要獲取到對應庫設定的值,必須先select(int index)再獲取。——因為我們平常都是預設使用第一個庫,所以不需要該操作;
但為了對key按不同功能來分割槽,達到解耦和易於管理的目的,最好選擇不同的庫空間。需要get不同的資料時,先要注意它之前是在哪個庫set的。
——是否get set只與所在庫有關?而與redis例項無關?——只要在同一個庫,不同redis例項set的資料,另一個redis例項便可得到。
4、用redis統計過去某段時間的總資料、平均資料等
這個我之前一直是“事後統計”的想法,即把該段時間的東西先存到redis,再去遍歷集合,然後再做統計。這樣迴圈效率很低,尤其是在高併發資料的情形下。所以我們可以邊按不同統計指標來邊入redis。比如使用jedis.incr(key)累加,或jedis.incrBy(key, num)按num累加,統計時就可以直接拿key對應的value,因為它不斷進行了incr操作了,就已經是我們要的總資料了,而非事後拿到所有的資料累加;lpush(key, data)可以快取出一個集合(如果需要去重則用sadd(setKey, data)!需要排序用zadd,之後再用zrange(升序)或zrevrange(降序)取排好序的元素),便於做統計時使用邊入jeids.sort(key)來排序,得出最大、最小之類的指標,而非通過排序演算法來不斷比較。
5、關於hmset hgetAll
這個是value為map型別的,要慎用!因為一旦map裡面某個Entry的value為null的話,會報jedisdataexception異常的,提示set的value不能為null(即:一旦map裡面有一對k-v的value為null,即使整個map不為null,也會報錯!)
所以一般儘量避免使用hmset,不僅容易出錯,而且你取的時候,先得到map,再從map裡用對應的key去取value,效率極低。
6、incr incrBy
incr(String key)用於向給定的key對應的值(數值上必須表現為整型)自增1,incrBy(String key, long value)用於向給定的key對應的值增加value值。他們常常用於做累加統計。“測試下面的程式碼:
System.out.println(jedis.get("test2") + "," + jedis.keys("*"));
jedis.incr("test2");
System.out.println(jedis.get("test2") + "," + jedis.keys("*"));
jedis.incr("test2");
System.out.println(jedis.get("test2") + "," + jedis.keys("*"));
jedis.del("test2");
System.out.println(jedis.get("test2") + "," + jedis.keys("*"));
列印結果:
null,[]
1,[test2]
2,[test2]
null,[]
而測試:
System.out.println(jedis.get("test") + "," + jedis.keys("*"));
jedis.incrBy("test", 12);
System.out.println(jedis.get("test") + "," + jedis.keys("*"));
jedis.incrBy("test", 3);
System.out.println(jedis.get("test") + "," + jedis.keys("*"));
jedis.del("test");
System.out.println(jedis.get("test") + "," + jedis.keys("*"));
列印結果為:
null,[]
12,[test]
15,[test]
null,[]
這說明incr incrBy這2個命令可以在“即使redis中不存在該Key”的情況下,建立該key。並賦給“第一次”的值——incr命令為1,incrBy命令為第一次執行該命令的第二個引數。
7、sort
jedis.sadd("test", "1");
jedis.sadd("test", "2");
jedis.sadd("test", "2");
jedis.sadd("test", "1");
jedis.sadd("test", "16");
jedis.sadd("test", "7");
jedis.sadd("test", "3");
jedis.sadd("test", "22");
System.out.println(jedis.smembers("test"));
System.out.println(jedis.sort("test"));
jedis.del("test");
列印結果為:
[3, 2, 1, 7, 22, 16]
[1, 2, 3, 7, 16, 22]
這說明:sadd具有去重新增的功能,得到的是一個set集合; sort則具有預設的按從小到的排序的功能(關於排序也可以使用sortedset的資料結構,存的時候用zadd方法:zadd(String key, double score, String value),score為排序的權重。取的時候用zrange-從小到大或zrevrange-從大到小)。
——對於redis的強大可以從很多簡單的demo來測試瞭解。