1. 程式人生 > >redis學習筆記——不僅僅是存取資料

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來測試瞭解。