天天看點

分布式緩存Redis之資料類型

寫在前面

  本學習教程所有示例代碼見GitHub:https://github.com/selfconzrr/Redis_Learning

  Redis 資料類型官方文檔:http://www.redis.net.cn/tutorial/3505.html

  Redis支援五種資料類型:string(字元串),hash(哈希),list(清單),set(集合)及zset(sorted set:有序集合)

使用場景及簡介

String:

  string是最簡單的類型,你可以了解成與Memcached是一模一樣的類型,一個key對應一個value,其上支援的操作與Memcached的操作類似。但它的功能更豐富。

  string類型是二進制安全的。意思是redis的string可以包含任何資料,比如jpg圖檔或者序列化的對象。從内部實作來看其實string可以看作byte數組,最大上限是1G位元組,下面是string類型的定義:

struct sdshdr {
  long len;
  long free;
  char buf[];
  };
           

  其中:

  len是buf數組的長度。

  free是數組中剩餘可用位元組數,由此可以了解為什麼string類型是二進制安全的了,因為它本質上就是個byte數組,當然可以包含任何資料了。

  buf是個char數組用于存貯實際的字元串内容,其實char和c#中的byte是等價的,都是一個位元組。

  另外string類型可以被部分指令按int處理。比如incr等指令,如果隻用string類型,redis就可以被看作加上持久化特性的memcached。當然redis對string類型的操作比memcached還是多很多的。

  學習的時候,就有個疑問了:什麼是二進制安全?為什麼string是二進制安全的?

  見博文:http://blog.csdn.net/u011489043/article/details/78738374

LIST:

  Redis的list類型其實就是一個每個子元素都是string類型的雙向連結清單。連結清單的最大長度是(2的32次方)。我們可以通過push,pop操作從連結清單的頭部或者尾部添加删除元素。這使得list既可以用作棧,也可以用作隊列。

  list的pop操作還有阻塞版本的,當我們[l/r]pop一個list對象時,如果list是空,或者不存在,會立即傳回nil。但是阻塞版本的b[l/r]pop則可以阻塞,當然可以加逾時時間,逾時後也會傳回nil。

  為什麼要阻塞版本的pop呢,主要是為了避免輪詢。舉個簡單的例子如果我們用list來實作一個工作隊列,執行任務的thread可以調用阻塞版本的pop去擷取任務。這樣就可以避免輪詢去檢查是否有任務存在,當任務來時候工作線程可以立即傳回,也可以避免輪詢帶來的延遲。

  

  應用: 可以作為消息隊列,LPUSH連結清單頭作為生産者插入消息,RPOP作為消費者取得消息。

Hash:

  Redis hash是一個string類型的field和value的映射表。它的添加、删除操作都是O(1)(平均)。hash特别适合用于存儲對象。相較于将對象的每個字段存成單個string類型。将一個對象存儲在hash類型中會占用更少的記憶體,并且可以更友善的存取整個對象。

  省記憶體的原因是建立一個hash對象時,開始是用zipmap(又稱為small hash)來存儲的。這個zipmap其實并不是hash table,但是zipmap相比正常的hash實作可以節省不少hash本身需要的一些中繼資料存儲開銷。盡管zipmap的添加,删除,查找都是O(n),但是由于一般對象的field數量都不太多。是以使用zipmap也是很快的,也就是說添加删除平均還是O(1)。如果field或者value的大小超出一定限制後,Redis會在内部自動将zipmap替換成正常的hash實作。這個限制可以在配置檔案中指定。

hash-max-zipmap-entries 64 #配置字段最多64個。
hash-max-zipmap-value 512 #配置value最大為512位元組。
           

  應用: AIOP中使用者标簽資訊使用HASHMAP存儲,存儲結構為user:phone tag1 val1 tag2 val2 ,AIOP傳遞客戶手機号,即可通過HGET user:phone tag1 tag2 取出标簽值。

Set:

  Redis的set是string類型的無序集合。set元素最大可以包含(2的32次方)個元素。

  set的是通過hash table實作的,是以添加、删除和查找的複雜度都是O(1)。hash table會随着添加或者删除自動的調整大小。需要注意的是調整hash table大小時候需要同步(擷取寫鎖)會阻塞其他讀寫操作,可能不久後就會改用跳表(skip list)來實作,跳表已經在sorted set中使用了。關于set集合類型除了基本的添加删除操作,其他有用的操作還包含集合的取并集(union),交集(intersection),差集(difference)。通過這些操作可以很容易的實作sns中的好友推薦和blog的tag功能。

  應用: COC中将符合标簽的使用者進行聚類,存放到Set中,比如set1 存放學生标簽使用者、set2存放低消費使用者、set3存放非合約使用者,那麼取出可能購買小米的低消非合約學生使用者群:

SINTERSTORE  aset set1 set2 // 将set1與set2的交集賦給aset
SDIFF aset set3   //取aset與set3的差集
           

Sorted Set:

  和set一樣sorted set也是string類型元素的集合,不同的是每個元素都會關聯一個double類型的score。sorted set的實作是skip list和hash table的混合體。

  當元素被添加到集合中時,一個元素到score的映射被添加到hash table中,是以給定一個元素擷取score的開銷是O(1),另一個score到元素的映射被添加到skip list,并按照score排序,是以就可以有序的擷取集合中的元素。**添加,删除操作開銷都是O(log(N))**和skip list的開銷一緻。

  redis的skip list實作用的是雙向連結清單,這樣就可以逆序從尾部取元素。sorted set最經常的使用方式應該是作為索引來使用。我們可以把要排序的字段作為score存儲,對象的id當元素存儲。

  

  **應用:**可以用于一個大型線上遊戲的積分排行榜。每當玩家的分數發生變化時,可以執行ZADD指令更新玩家的分數,此後再通過ZRANGE指令擷取積分TOP TEN的使用者資訊。當然我們也可以利用ZRANK指令通過username來擷取玩家的排行資訊。最後我們将組合使用ZRANGE和ZRANK指令快速的擷取和某個玩家積分相近的其他使用者的資訊。

在Ubuntu下的終端操作:

  啟動redis伺服器後,進入redis用戶端,然後大家可以嘗試如下指令的操作,一方面熟悉Linux操作,一方面熟悉redis支援的資料類型。

1、增加一條字元串記錄key1

# 增加一條記錄key1
redis 127.0.0.1:6379> set key1 "hello"
OK

# 列印記錄
redis 127.0.0.1:6379> get key1
"hello"

2、增加一條數字記錄key2

# 增加一條數字記錄key2
set key2 1
OK

# 讓數字自增
redis 127.0.0.1:6379> INCR key2
(integer) 2
redis 127.0.0.1:6379> INCR key2
(integer) 3

# 列印記錄
redis 127.0.0.1:6379> get key2
"3"

3、增加一條清單記錄key3

# 增加一個清單記錄key3
redis 127.0.0.1:6379> LPUSH key3 a
(integer) 1

# 從左邊插入清單
redis 127.0.0.1:6379> LPUSH key3 b
(integer) 2

# 從右邊插入清單
redis 127.0.0.1:6379> RPUSH key3 c
(integer) 3

# 列印清單記錄,按從左到右的順序
redis 127.0.0.1:6379> LRANGE key3 0 3
1) "b"
2) "a"
3) "c"

4、增加一條哈希表記錄key4

# 增加一個哈希記表錄key4
redis 127.0.0.1:6379> HSET key4 name "John Smith"
(integer) 1

# 在哈希表中插入,email的Key和Value的值
redis 127.0.0.1:6379> HSET key4 email "[email protected]"
(integer) 1

# 列印哈希表中,name為key的值
redis 127.0.0.1:6379> HGET key4 name
"John Smith"

# 列印整個哈希表
redis 127.0.0.1:6379> HGETALL key4
1) "name"
2) "John Smith"
3) "email"
4) "[email protected]"

5、增加一條哈希表記錄key5

# 增加一條哈希表記錄key5,一次插入多個Key和value的值
redis 127.0.0.1:6379> HMSET key5 username antirez password P1pp0 age 3
OK

# 列印哈希表中,username和age為key的值
redis 127.0.0.1:6379> HMGET key5 username age
1) "antirez"
2) "3"

# 列印完整的哈希表記錄
redis 127.0.0.1:6379> HGETALL key5
1) "username"
2) "antirez"
3) "password"
4) "P1pp0"
5) "age"
6) "3"

# 檢視所有的key清單
redis 127.0.0.1:6379> keys *
1) "key2"
2) "key3"
3) "key4"
4) "key5"
5) "key1"

# 删除key1,key5
redis 127.0.0.1:6379> del key1
(integer) 1
redis 127.0.0.1:6379> del key5
(integer) 1

# 檢視所有的key清單
redis 127.0.0.1:6379> keys *
1) "key2"
2) "key3"
3) "key4"
           

在windows下的Eclipse操作

  建立java工程,需要引入用戶端jedis的jar包,兩種方式

  添加maven依賴

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
           

  直接下載下傳,(我的百度網盤)

  Jedis2.1.0,連結:https://pan.baidu.com/s/1o7K0Zai 密碼:wabo

  Jedis2.9.0,連結:https://pan.baidu.com/s/1i5tNhx7 密碼:u9si

  然後,執行下面的簡單測試代碼。看能否成功連接配接Ubuntu下的伺服器,并實作key-value的操作。

  比如在Ubuntu終端下,輸入指令:get key6;如果在終端界面輸出:jedis test6,說明遠端連接配接、操作資料庫成功。

Jedis jedis = new Jedis("192.168.65.130", 6379);// 此ip為Ubuntu的ip位址
		jedis.auth("redis");// redis-cli的通路密碼
		System.out.println("Connection to server sucessfully");
		// 檢視服務是否運作
		System.out.println("Server is running: " + jedis.ping());
		// 進行資料庫操作測試
		jedis.set("key6", "redis test");
		jedis.set("key7", "張瑞瑞");//中文在Ubuntu下沒正常顯示
		String string = jedis.get("key7");
		String string1 = jedis.get("key6");
		System.out.println(string + " " + string1);
           

  比如對set類型的詳細操作,完整代碼見開篇留的GitHub源碼位址(幾乎囊括了五種資料類型的所有操作)。主要是熟悉一些常用指令的功能及用法。

public static void SetOperate(Jedis jedis, ShardedJedis shardedJedis) {
		System.out.println("================set=================");
		System.out.println("清空庫中所有資料:" + jedis.flushDB());

		System.out.println("=============增=============");
		System.out.println("向sets集合中加入元素element001:"
				+ jedis.sadd("sets", "element001"));
		System.out.println("向sets集合中加入元素element002:"
				+ jedis.sadd("sets", "element002"));
		System.out.println("向sets集合中加入元素element003:"
				+ jedis.sadd("sets", "element003"));
		System.out.println("向sets集合中加入元素element004:"
				+ jedis.sadd("sets", "element004"));
		System.out.println("向sets集合中加入元素element001:"
				+ jedis.sadd("sets", "element001"));
		System.out.println("檢視sets集合中的所有元素:" + jedis.smembers("sets"));
		System.out.println();

		System.out.println("=============删=============");
		System.out.println("集合sets中删除元素element003:"
				+ jedis.srem("sets", "element003"));
		// smembers(key) :傳回名稱為key的set的所有元素
		System.out.println("檢視sets集合中的所有元素:" + jedis.smembers("sets"));
		/*
		 * System.out.println("sets集合中任意位置的元素出棧:"+jedis.spop("sets")); --無實際意義
		 * System.out.println("檢視sets集合中的所有元素:"+jedis.smembers("sets"));
		 */
		System.out.println();

		System.out.println("=============查=============");
		// sismember(key, member) :member是否是名稱為key的set的元素
		System.out.println("判斷element001是否在集合sets中:"
				+ jedis.sismember("sets", "element001"));
		// scard(key) :傳回名稱為key的set的基數
		System.out.println("基數: " + jedis.scard("sets"));
		System.out.println("循環查詢擷取sets中的每個元素:");
		Set<String> set = jedis.smembers("sets");
		Iterator<String> it = set.iterator();
		while (it.hasNext()) {
			Object obj = it.next();
			System.out.println(obj);
		}
		System.out.println();

		System.out.println("=============集合運算=============");
		System.out.println("sets1中添加元素element001:"
				+ jedis.sadd("sets1", "element001"));
		System.out.println("sets1中添加元素element002:"
				+ jedis.sadd("sets1", "element002"));
		System.out.println("sets1中添加元素element003:"
				+ jedis.sadd("sets1", "element003"));

		System.out.println("sets2中添加元素element002:"
				+ jedis.sadd("sets2", "element002"));
		System.out.println("sets2中添加元素element003:"
				+ jedis.sadd("sets2", "element003"));
		System.out.println("sets2中添加元素element004:"
				+ jedis.sadd("sets2", "element004"));

		System.out.println("檢視sets1集合中的所有元素:" + jedis.smembers("sets1"));
		System.out.println("檢視sets2集合中的所有元素:" + jedis.smembers("sets2"));
		System.out.println("sets1和sets2交集:" + jedis.sinter("sets1", "sets2"));
		System.out.println("sets1和sets2并集:" + jedis.sunion("sets1", "sets2"));
		// 記A,B是兩個集合,則所有屬于A且不屬于B的元素構成的集合,為“差集”
		System.out.println("sets1和sets2差集:" + jedis.sdiff("sets1", "sets2"));
	}
           

------至所有正在努力奮鬥的程式猿們!加油!!

有碼走遍天下 無碼寸步難行

1024 - 夢想,永不止步!

愛程式設計 不愛Bug

愛加班 不愛黑眼圈

固執 但不偏執

瘋狂 但不瘋癫

生活裡的菜鳥

工作中的大神

身懷寶藏,一心憧憬星辰大海

追求極緻,目标始于高山之巅

一群懷揣好奇,夢想改變世界的孩子

一群追日逐浪,正在改變世界的極客

你們用最美的語言,诠釋着科技的力量

你們用極速的創新,引領着時代的變遷

——樂于分享,共同進步,歡迎補充

——Any comments greatly appreciated

——誠心歡迎各位交流讨論!QQ:1138517609

——CSDN:https://blog.csdn.net/u011489043

——簡書:https://www.jianshu.com/u/4968682d58d1

——GitHub:https://github.com/selfconzrr

繼續閱讀