[转]十五分钟介绍 Redis数据结构
下面是一個(gè)對(duì)Redis官方文檔《A fifteen minute introduction to Redis data types》一文的翻譯,如其題目所言,此文目的在于讓一個(gè)初學(xué)者能通過(guò)15分鐘的簡(jiǎn)單學(xué)習(xí)對(duì)Redis的數(shù)據(jù)結(jié)構(gòu)有一個(gè)了解。
Redis是一種面向“鍵/值”對(duì)類型數(shù)據(jù)的分布式NoSQL數(shù)據(jù)庫(kù)系統(tǒng),特點(diǎn)是高性能,持久存儲(chǔ),適應(yīng)高并發(fā)的應(yīng)用場(chǎng)景。它起步較晚,發(fā)展迅速,目前已被許多大型機(jī)構(gòu)采用,比如Github,看看誰(shuí)在用它。
本文翻譯自Redis的一篇官方文檔:A fifteen minute introduction to Redis data types
方便感興趣的朋友,快速介紹Redis的數(shù)據(jù)類型。
中英文對(duì)照,如有疏漏敬請(qǐng)留言,某些關(guān)鍵詞不譯,便于閱讀。
—————————————————————————————————————
你也許已經(jīng)知道Redis并不是簡(jiǎn)單的key-value存儲(chǔ),實(shí)際上他是一個(gè)數(shù)據(jù)結(jié)構(gòu)服務(wù)器,支持不同類型的值。也就是說(shuō),你不必僅僅把字符串當(dāng)作鍵所指向的值。下列這些數(shù)據(jù)類型都可作為值類型。
- 二進(jìn)制安全的 字符串?string
- 二進(jìn)制安全的 字符串列表?list?of string
- 二進(jìn)制安全的 字符串集合?set?of string,換言之:它是一組無(wú)重復(fù)未排序的element。可以把它看成Ruby中的?hash–其key等于element,value都等于’true‘。
- 有序集合sorted set?of string,類似于集合set,但其中每個(gè)元素都和一個(gè)浮點(diǎn)數(shù)score(評(píng)分)關(guān)聯(lián)。element根據(jù)score排序。可以把它看成Ruby中的 hash–其key等于element,value等于score,但元素總是按score的順序排列,無(wú)需額外的排序操作。
Redis 鍵
Redis key值是二進(jìn)制安全的,這意味著可以用任何二進(jìn)制序列作為key值,從形如”foo”的簡(jiǎn)單字符串到一個(gè)JPEG文件的內(nèi)容都可以。空字符串也是有效key值。
關(guān)于key的幾條規(guī)則:
- 太長(zhǎng)的鍵值不是個(gè)好主意,例如1024字節(jié)的鍵值就不是個(gè)好主意,不僅因?yàn)橄膬?nèi)存,而且在數(shù)據(jù)中查找這類鍵值的計(jì)算成本很高。
- 太短的鍵值通常也不是好主意,如果你要用”u:1000:pwd”來(lái)代替”user:1000:password”,這沒(méi)有什么問(wèn)題,但后者更易閱讀,并且由此增加的空間消耗相對(duì)于key object和value object本身來(lái)說(shuō)很小。當(dāng)然,沒(méi)人阻止您一定要用更短的鍵值節(jié)省一丁點(diǎn)兒空間。
- 最好堅(jiān)持一種模式。例如:”object-type:id:field”就是個(gè)不錯(cuò)的注意,像這樣”user:1000:password”。我喜歡對(duì)多單詞的字段名中加上一個(gè)點(diǎn),就像這樣:”comment:1234:reply.to”。
字符串類型
這是最簡(jiǎn)單Redis類型。如果你只用這種類型,Redis就像一個(gè)可以持久化的memcached服務(wù)器(注:memcache的數(shù)據(jù)僅保存在內(nèi)存中,服務(wù)器重啟后,數(shù)據(jù)將丟失)。
我們來(lái)玩兒一下字符串類型:
$ redis-cli set mykey "my binary safe value" OK $ redis-cli get mykey my binary safe value正如你所見(jiàn)到的,通常用SET command?和?GET command來(lái)設(shè)置和獲取字符串值。
值可以是任何種類的字符串(包括二進(jìn)制數(shù)據(jù)),例如你可以在一個(gè)鍵下保存一副jpeg圖片。值的長(zhǎng)度不能超過(guò)1GB。
雖然字符串是Redis的基本值類型,但你仍然能通過(guò)它完成一些有趣的操作。例如:原子遞增:
$ redis-cli set counter 100 OK $ redis-cli incr counter (integer) 101 $ redis-cli incr counter (integer) 102 $ redis-cli incrby counter 10 (integer) 112INCR?命令將字符串值解析成整型,將其加一,最后將結(jié)果保存為新的字符串值,類似的命令有INCRBY,?DECR?and?DECRBY。實(shí)際上他們?cè)趦?nèi)部就是同一個(gè)命令,只是看上去有點(diǎn)兒不同。
INCR是原子操作意味著什么呢?就是說(shuō)即使多個(gè)客戶端對(duì)同一個(gè)key發(fā)出INCR命令,也決不會(huì)導(dǎo)致競(jìng)爭(zhēng)的情況。例如如下情況永遠(yuǎn)不可能發(fā)生:『客戶端1和客戶端2同時(shí)讀出“10”,他們倆都對(duì)其加到11,然后將新值設(shè)置為11』。最終的值一定是12,read-increment-set操作完成時(shí),其他客戶端不會(huì)在同一時(shí)間執(zhí)行任何命令。
對(duì)字符串,另一個(gè)的令人感興趣的操作是GETSET命令,行如其名:他為key設(shè)置新值并且返回原值。這有什么用處呢?例如:你的系統(tǒng)每當(dāng)有新用戶訪問(wèn)時(shí)就用INCR命令操作一個(gè)Redis key。你希望每小時(shí)對(duì)這個(gè)信息收集一次。你就可以GETSET這個(gè)key并給其賦值0并讀取原值。
列表類型
要說(shuō)清楚列表數(shù)據(jù)類型,最好先講一點(diǎn)兒理論背景,在信息技術(shù)界List這個(gè)詞常常被使用不當(dāng)。例如”Python Lists”就名不副實(shí)(名為L(zhǎng)inked Lists),但他們實(shí)際上是數(shù)組(同樣的數(shù)據(jù)類型在Ruby中叫數(shù)組)
一般意義上講,列表就是有序元素的序列:10,20,1,2,3就是一個(gè)列表。但用數(shù)組實(shí)現(xiàn)的List和用Linked List實(shí)現(xiàn)的List,在屬性方面大不相同。
Redis lists基于Linked Lists實(shí)現(xiàn)。這意味著即使在一個(gè)list中有數(shù)百萬(wàn)個(gè)元素,在頭部或尾部添加一個(gè)元素的操作,其時(shí)間復(fù)雜度也是常數(shù)級(jí)別的。用LPUSH?命令在十個(gè)元素的list頭部添加新元素,和在千萬(wàn)元素list頭部添加新元素的速度相同。
那么,壞消息是什么?在數(shù)組實(shí)現(xiàn)的list中利用索引訪問(wèn)元素的速度極快,而同樣的操作在linked list實(shí)現(xiàn)的list上沒(méi)有那么快。
Redis Lists are implemented with linked lists because for a database system it is crucial to be able to add elements to a very long list in a very fast way. Another strong advantage is, as you’ll see in a moment, that Redis Lists can be taken at constant length in constant time.
Redis Lists用linked list實(shí)現(xiàn)的原因是:對(duì)于數(shù)據(jù)庫(kù)系統(tǒng)來(lái)說(shuō),至關(guān)重要的特性是:能非常快的在很大的列表上添加元素。另一個(gè)重要因素是,正如你將要看到的:Redis lists能在常數(shù)時(shí)間取得常數(shù)長(zhǎng)度。
Redis lists 入門
LPUSH?命令可向list的左邊(頭部)添加一個(gè)新元素,而RPUSH命令可向list的右邊(尾部)添加一個(gè)新元素。最后LRANGE?命令可從list中取出一定范圍的元素
$ redis-cli rpush messages "Hello how are you?" OK $ redis-cli rpush messages "Fine thanks. I‘m having fun with Redis" OK $ redis-cli rpush messages "I should look into this NOSQL thing ASAP" OK $ redis-cli lrange messages 0 2 1. Hello how are you? 2. Fine thanks. I‘m having fun with Redis 3. I should look into this NOSQL thing ASAP注意LRANGE?帶有兩個(gè)索引,一定范圍的第一個(gè)和最后一個(gè)元素。這兩個(gè)索引都可以為負(fù)來(lái)告知Redis從尾部開(kāi)始計(jì)數(shù),因此-1表示最后一個(gè)元素,-2表示list中的倒數(shù)第二個(gè)元素,以此類推。
As you can guess from the example above, lists can be used, for instance, in order to implement a chat system. Another use is as queues in order to route messages between different processes. But the key point is that you can use Redis lists every time you require to access data in the same order they are added. This will not require any SQL ORDER BY operation, will be very fast, and will scale to millions of elements even with a toy Linux box.
正如你可以從上面的例子中猜到的,list可被用來(lái)實(shí)現(xiàn)聊天系統(tǒng)。還可以作為不同進(jìn)程間傳遞消息的隊(duì)列。關(guān)鍵是,你可以每次都以原先添加的順序訪問(wèn)數(shù)據(jù)。這不需要任何SQL ORDER BY 操作,將會(huì)非常快,也會(huì)很容易擴(kuò)展到百萬(wàn)級(jí)別元素的規(guī)模。
例如在評(píng)級(jí)系統(tǒng)中,比如社會(huì)化新聞網(wǎng)站 reddit.com,你可以把每個(gè)新提交的鏈接添加到一個(gè)list,用LRANGE可簡(jiǎn)單的對(duì)結(jié)果分頁(yè)。
在博客引擎實(shí)現(xiàn)中,你可為每篇日志設(shè)置一個(gè)list,在該list中推入進(jìn)博客評(píng)論,等等。
向Redis list壓入ID而不是實(shí)際的數(shù)據(jù)
在上面的例子里 ,我們將“對(duì)象”(此例中是簡(jiǎn)單消息)直接壓入Redis list,但通常不應(yīng)這么做,由于對(duì)象可能被多次引用:例如在一個(gè)list中維護(hù)其時(shí)間順序,在一個(gè)集合中保存它的類別,只要有必要,它還會(huì)出現(xiàn)在其他list中,等等。
讓我們回到reddit.com的例子,將用戶提交的鏈接(新聞)添加到list中,有更可靠的方法如下所示:
$ redis-cli incr next.news.id (integer) 1 $ redis-cli set news:1:title "Redis is simple" OK $ redis-cli set news:1:url "http://code.google.com/p/redis" OK $ redis-cli lpush submitted.news 1 OK我們自增一個(gè)key,很容易得到一個(gè)獨(dú)一無(wú)二的自增ID,然后通過(guò)此ID創(chuàng)建對(duì)象–為對(duì)象的每個(gè)字段設(shè)置一個(gè)key。最后將新對(duì)象的ID壓入submitted.news?list。
這只是牛刀小試。在命令參考文檔中可以讀到所有和list有關(guān)的命令。你可以刪除元素,旋轉(zhuǎn)list,根據(jù)索引獲取和設(shè)置元素,當(dāng)然也可以用LLEN得到list的長(zhǎng)度。
Redis 集合
Redis集合是未排序的集合,其元素是二進(jìn)制安全的字符串。SADD命令可以向集合添加一個(gè)新元素。和sets相關(guān)的操作也有許多,比如檢測(cè)某個(gè)元素是否存在,以及實(shí)現(xiàn)交集,并集,差集等等。一例勝千言:
$ redis-cli sadd myset 1 (integer) 1 $ redis-cli sadd myset 2 (integer) 1 $ redis-cli sadd myset 3 (integer) 1 $ redis-cli smembers myset 1. 3 2. 1 3. 2我向集合中添加了三個(gè)元素,并讓Redis返回所有元素。如你所見(jiàn)它們是無(wú)序的。
現(xiàn)在讓我們檢查某個(gè)元素是否存在:
$ redis-cli sismember myset 3 (integer) 1 $ redis-cli sismember myset 30 (integer) 0“3″是這個(gè)集合的成員,而“30”不是。集合特別適合表現(xiàn)對(duì)象之間的關(guān)系。例如用Redis集合可以很容易實(shí)現(xiàn)標(biāo)簽功能。
下面是一個(gè)簡(jiǎn)單的方案:對(duì)每個(gè)想加標(biāo)簽的對(duì)象,用一個(gè)標(biāo)簽ID集合與之關(guān)聯(lián),并且對(duì)每個(gè)已有的標(biāo)簽,一組對(duì)象ID與之關(guān)聯(lián)。
例如假設(shè)我們的新聞ID 1000被加了三個(gè)標(biāo)簽tag 1,2,5和77,就可以設(shè)置下面兩個(gè)集合:
$ redis-cli sadd news:1000:tags 1 (integer) 1 $ redis-cli sadd news:1000:tags 2 (integer) 1 $ redis-cli sadd news:1000:tags 5 (integer) 1 $ redis-cli sadd news:1000:tags 77 (integer) 1 $ redis-cli sadd tag:1:objects 1000 (integer) 1 $ redis-cli sadd tag:2:objects 1000 (integer) 1 $ redis-cli sadd tag:5:objects 1000 (integer) 1 $ redis-cli sadd tag:77:objects 1000 (integer) 1要獲取一個(gè)對(duì)象的所有標(biāo)簽,如此簡(jiǎn)單:
$ redis-cli smembers news:1000:tags 1. 5 2. 1 3. 77 4. 2而有些看上去并不簡(jiǎn)單的操作仍然能使用相應(yīng)的Redis命令輕松實(shí)現(xiàn)。例如我們也許想獲得一份同時(shí)擁有標(biāo)簽1, 2, 10和27的對(duì)象列表。這可以用SINTER命令來(lái)做,他可以在不同集合之間取出交集。因此為達(dá)目的我們只需:
$ redis-cli sinter tag:1:objects tag:2:objects tag:10:objects tag:27:objects ... no result in our dataset composed of just one object ...在命令參考文檔中可以找到和集合相關(guān)的其他命令,令人感興趣的一抓一大把。一定要留意SORT命令,Redis集合和list都是可排序的。
題外話:如何為字符串獲取唯一標(biāo)識(shí)
在標(biāo)簽的例子里,我們用到了標(biāo)簽ID,卻沒(méi)有提到ID從何而來(lái)。基本上你得為每個(gè)加入系統(tǒng)的標(biāo)簽分配一個(gè)唯一標(biāo)識(shí)。你也希望在多個(gè)客戶端同時(shí)試著添加同樣的標(biāo)簽時(shí)不要出現(xiàn)競(jìng)爭(zhēng)的情況。此外,如果標(biāo)簽已存在,你希望返回他的ID,否則創(chuàng)建一個(gè)新的唯一標(biāo)識(shí)并將其與此標(biāo)簽關(guān)聯(lián)。
Redis 1.4將增加Hash類型。有了它,字符串和唯一ID關(guān)聯(lián)的事兒將不值一提,但如今我們?nèi)绾斡矛F(xiàn)有Redis命令可靠的解決它呢?
我們首先的嘗試(以失敗告終)可能如下。假設(shè)我們想為標(biāo)簽“redis”獲取一個(gè)唯一ID:
- 為了讓算法是二進(jìn)制安全的(只是標(biāo)簽而不考慮utf8,空格等等)我們對(duì)標(biāo)簽做SHA1簽名。SHA1(redis)=b840fc02d524045429941cc15f59e41cb7be6c52。
- 檢查這個(gè)標(biāo)簽是否已與一個(gè)唯一ID關(guān)聯(lián),
用命令GET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id - 如果上面的GET操作返回一個(gè)ID,則將其返回給用戶。標(biāo)簽已經(jīng)存在了。
- 否則… 用INCR next.tag.id命令生成一個(gè)新的唯一ID(假定它返回123456)。
- 最后關(guān)聯(lián)標(biāo)簽和新的ID,
SET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id 123456
并將新ID返回給調(diào)用者。
多美妙,或許更好…等等!當(dāng)兩個(gè)客戶端同時(shí)使用這組指令嘗試為標(biāo)簽“redis”獲取唯一ID時(shí)會(huì)發(fā)生什么呢?如果時(shí)間湊巧,他們倆都會(huì)從GET操作獲得nil,都將對(duì)next.tag.id key做自增操作,這個(gè)key會(huì)被自增兩次。其中一個(gè)客戶端會(huì)將錯(cuò)誤的ID返回給調(diào)用者。幸運(yùn)的是修復(fù)這個(gè)算法并不難,這是明智的版本:
- 為了讓算法是二進(jìn)制安全的(只是標(biāo)簽而不考慮utf8,空格等等)我們對(duì)標(biāo)簽做SHA1簽名。SHA1(redis)=b840fc02d524045429941cc15f59e41cb7be6c52。
- 檢查這個(gè)標(biāo)簽是否已與一個(gè)唯一ID關(guān)聯(lián),
用命令GET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id - 如果上面的GET操作返回一個(gè)ID,則將其返回給用戶。標(biāo)簽已經(jīng)存在了。
- 否則… 用INCR next.tag.id命令生成一個(gè)新的唯一ID(假定它返回123456)。
- 下面關(guān)聯(lián)標(biāo)簽和新的ID,(注意用到一個(gè)新的命令)
SETNX tag:b840fc02d524045429941cc15f59e41cb7be6c52:id 123456。如果另一個(gè)客戶端比當(dāng)前客戶端更快,SETNX將不會(huì)設(shè)置key。而且,當(dāng)key被成功設(shè)置時(shí)SETNX返回1,否則返回0。那么…讓我們?cè)僮鲎詈笠徊竭\(yùn)算。 - 如果SETNX返回1(key設(shè)置成功)則將123456返回給調(diào)用者,這就是我們的標(biāo)簽ID,否則執(zhí)行GET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id?并將其結(jié)果返回給調(diào)用者。
有序集合
集合是使用頻率很高的數(shù)據(jù)類型,但是…對(duì)許多問(wèn)題來(lái)說(shuō)他們也有點(diǎn)兒太不講順序了;)因此Redis1.2引入了有序集合。他和集合非常相似,也是二進(jìn)制安全的字符串集合,但是這次帶有關(guān)聯(lián)的score,以及一個(gè)類似LRANGE的操作可以返回有序元素,此操作只能作用于有序集合,它就是,ZRANGE?命令。
基本上有序集合從某種程度上說(shuō)是SQL世界的索引在Redis中的等價(jià)物。例如在上面提到的reddit.com例子中,并沒(méi)有提到如何根據(jù)用戶投票和時(shí)間因素將新聞組合生成首頁(yè)。我們將看到有序集合如何解決這個(gè)問(wèn)題,但最好先從更簡(jiǎn)單的事情開(kāi)始,闡明這個(gè)高級(jí)數(shù)據(jù)類型是如何工作的。讓我們添加幾個(gè)黑客,并將他們的生日作為“score”。
$ redis-cli zadd hackers 1940 "Alan Kay" (integer) 1 $ redis-cli zadd hackers 1953 "Richard Stallman" (integer) 1 $ redis-cli zadd hackers 1965 "Yukihiro Matsumoto" (integer) 1 $ redis-cli zadd hackers 1916 "Claude Shannon" (integer) 1 $ redis-cli zadd hackers 1969 "Linus Torvalds" (integer) 1 $ redis-cli zadd hackers 1912 "Alan Turing" (integer) 1對(duì)有序集合來(lái)說(shuō),按生日排序返回這些黑客易如反掌,因?yàn)樗麄円呀?jīng)是有序的。有序集合是通過(guò)一個(gè)dual-ported 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,它包含一個(gè)精簡(jiǎn)的有序列表和一個(gè)hash table,因此添加一個(gè)元素的時(shí)間復(fù)雜度是O(log(N))。這還行,但當(dāng)我們需要訪問(wèn)有序的元素時(shí),Redis不必再做任何事情,它已經(jīng)是有序的了:
$ redis-cli zrange hackers 0 -1 1. Alan Turing 2. Claude Shannon 3. Alan Kay 4. Richard Stallman 5. Yukihiro Matsumoto 6. Linus Torvalds你知道Linus比Yukihiro年輕嗎
無(wú)論如何,我想反向?qū)@些元素排序,這次就用?ZREVRANGE?代替?ZRANGE吧:
$ redis-cli zrevrange hackers 0 -1 1. Linus Torvalds 2. Yukihiro Matsumoto 3. Richard Stallman 4. Alan Kay 5. Claude Shannon 6. Alan Turing一個(gè)非常重要的小貼士,ZSets只是有一個(gè)“默認(rèn)的”順序,但你仍然可以用?SORT命令對(duì)有序集合做不同的排序(但這次服務(wù)器要耗費(fèi)CPU了)。要想得到多種排序,一種可選方案是同時(shí)將每個(gè)元素加入多個(gè)有序集合。
區(qū)間操作
有序集合之能不止于此,他能在區(qū)間上操作。例如獲取所有1950年之前出生的人。我們用?ZRANGEBYSCORE?命令來(lái)做:
$ redis-cli zrangebyscore hackers -inf 1950 1. Alan Turing 2. Claude Shannon 3. Alan Kay我們請(qǐng)求Redis返回score介于負(fù)無(wú)窮到1950年之間的元素(兩個(gè)極值也包含了)。
也可以刪除區(qū)間內(nèi)的元素。例如從有序集合中刪除生日介于1940到1960年之間的黑客。
$ redis-cli zremrangebyscore hackers 1940 1960 (integer) 2ZREMRANGEBYSCORE?這個(gè)名字雖然不算好,但他卻非常有用,還會(huì)返回已刪除的元素?cái)?shù)量。
回到Reddit的例子
最后,回到 Reddit的例子。現(xiàn)在我們有個(gè)基于有序集合的像樣方案來(lái)生成首頁(yè)。用一個(gè)有序集合來(lái)包含最近幾天的新聞(用 ZREMRANGEBYSCORE 不時(shí)的刪除舊新聞)。用一個(gè)后臺(tái)任務(wù)從有序集合中獲取所有元素,根據(jù)用戶投票和新聞時(shí)間計(jì)算score,然后用新聞IDs和scores關(guān)聯(lián)生成?reddit.home.page?有序集合。要顯示首頁(yè),我們只需閃電般的調(diào)用?ZRANGE。
不時(shí)的從?reddit.home.page?有序集合中刪除過(guò)舊的新聞也是為了讓我們的系統(tǒng)總是工作在有限的新聞集合之上。
更新有序集合的scores
結(jié)束這篇指南之前還有最后一個(gè)小貼士。有序集合scores可以在任何時(shí)候更新。只要用 ZADD 對(duì)有序集合內(nèi)的元素操作就會(huì)更新它的score(和位置),時(shí)間復(fù)雜度是O(log(N)),因此即使大量更新,有序集合也是合適的。
這篇指南遠(yuǎn)未盡言,這只是從Redis開(kāi)始的基礎(chǔ),欲深入之請(qǐng)讀命令參考文檔。
謝謝閱讀。Salvatore。
?
?來(lái)自:http://blog.nosqlfan.com/html/3202.html?ref=rediszt
轉(zhuǎn)載于:https://www.cnblogs.com/crazyant/archive/2012/12/19/2825444.html
總結(jié)
以上是生活随笔為你收集整理的[转]十五分钟介绍 Redis数据结构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android中常用到的权限
- 下一篇: SQL 分页查询语句大全即(查找第N到M