本文主要內容python
- Redis與其餘軟件的相同之處和不一樣之處
- Redis的用法
- 使用Python示例代碼與Redis進行簡單的互動
- 使用Redis解決實際問題
Redis是一個遠程內存數據庫,它不只性能強勁,並且還具備複製特性以及爲解決問題而生的獨一無二的數據模型。Redis提供了5種不一樣類型的數 據結構,各式各樣的問題均可以很天然地映射到這些數據結構上:Redis的數據結構致力於幫助用戶解決問題,而不會像其餘數據庫那樣,要求用戶扭曲問題來 適應數據庫。除此以外,經過複製、持久化(persistence)和客戶端分片(client-side sharding)等特性,用戶能夠很方便地將Redis擴展成一個可以包含數百GB數據、每秒處理上百萬次請求的系統。git
筆者第一次使用Redis是在一家公司裏面,這家公司須要對一個保存了6萬個客戶聯繫方式的關係數據庫進行搜索,搜索能夠根據名字、郵件地址、所在 地和電話號碼來進行,每次搜索須要花費10~15秒的時間。在花了一週時間學習Redis的基礎知識以後,我使用Redis重寫了一個新的搜索引擎,而後 又花費了數週時間來仔細測試這個新系統,使它達到生產級別,最終這個新的搜索系統不只能夠根據名字、郵件地址、所在地和電話號碼等信息來過濾和排序客戶聯 系方式,而且每次操做均可以在50毫秒以內完成,這比原來的搜索系統足足快了 200 倍。閱讀本書可讓你學到不少小技巧、小竅門以及使用Redis解決某些常見問題的方法。程序員
本章將介紹Redis的適用範圍,以及在不一樣環境中使用Redis的方法(好比怎樣跟不一樣的組件和編程語言進行通訊等);而以後的章節則會展現各式各樣的問題,以及使用Redis來解決這些問題的方法。github
如今你已經知道我是怎樣開始使用Redis的了,也知道了這本書大概要講些什麼內容了,是時候更詳細地介紹一下Redis,並說明爲何應該使用Redis了。web
安裝Redis和Python 附錄A介紹了快速安裝Redis和Python的方法。redis
在其餘編程語言裏面使用Redis 本書只展現了使用Python語言編寫的示例代碼,使用Ruby、Java和JavaScript(Node.js)編寫的示例代碼能夠在這裏找到:https://github.com/josiahcarlson/redis-in-action。使用Spring框架的讀者能夠經過查看http://www.springsource.org/spring-data/redis來學習如何在Spring框架中使用Redis。算法
前面對於Redis數據庫的描述只說出了一部分真相。Redis是一個速度很是快的非關係數據庫(non-relational database),它能夠存儲鍵(key)與5種不一樣類型的值(value)之間的映射(mapping),能夠將存儲在內存的鍵值對數據持久化到硬 盤,可使用複製特性來擴展讀性能,還可使用客戶端分片1來擴展寫性能,接下來的幾節將分別介紹Redis的這幾個特性。spring
若是你熟悉關係數據庫,那麼你確定寫過用來關聯兩個表的數據的SQL查詢。而Redis則屬於人們常說的NoSQL數據庫或者非關係數據庫:Redis不使用表,它的數據庫也不會預約義或者強制去要求用戶對Redis存儲的不一樣數據進行關聯。數據庫
高性能鍵值緩存服務器memcached也常常被拿來與Redis進行比較:這二者均可用於存儲鍵值映射,彼此的性能也相差無幾,可是Redis能 夠自動以兩種不一樣的方式將數據寫入硬盤,而且Redis除了能存儲普通的字符串鍵以外,還能夠存儲其餘4種數據結構,而memcached只能存儲普通的 字符串鍵。這些不一樣之處使得Redis能夠用於解決更爲普遍的問題,而且既能夠用做主數據庫(primary database)使用,又能夠做爲其餘存儲系統的輔助數據庫(auxiliary database)使用。編程
本書的後續章節會分別介紹將Redis用做主存儲(primary storage)和二級存儲(secondary storage)時的用法和查詢模式。通常來講,許多用戶只會在Redis的性能或者功能是必要的狀況下,纔會將數據存儲到Redis裏面:若是程序對性 能的要求不高,又或者由於費用緣由而沒辦法將大量數據存儲到內存裏面,那麼用戶可能會選擇使用關係數據庫,或者其餘非關係數據庫。在實際中,讀者應該根據 本身的需求來決定是否使用Redis,並考慮是將Redis用做主存儲仍是輔助存儲,以及如何經過複製、持久化和事務等手段保證數據的完整性。
表1-1展現了一部分在功能上與Redis有重疊的數據庫服務器和緩存服務器,從這個表能夠看出Redis與這些數據庫及軟件之間的區別。
表1-1 一些數據庫和緩存服務器的特性與功能
名稱 |
類型 |
數據存儲選項 |
查詢類型 |
附加功能 |
---|---|---|---|---|
Redis |
使用內存存儲(in-memory)的非關係數據庫 |
字符串、列表、集合、散列表、有序集合 |
每種數據類型都有本身的專屬命令,另外還有批量操做(bulk operation)和不徹底(partial)的事務支持 |
發佈與訂閱,主從複製(master/slave replication),持久化,腳本(存儲過程,stored procedure) |
memcached |
使用內存存儲的鍵值緩存 |
鍵值之間的映射 |
建立命令、讀取命令、更新命令、刪除命令以及其餘幾個命令 |
爲提高性能而設的多線程服務器 |
MySQL |
關係數據庫 |
每一個數據庫能夠包含多個表,每一個表能夠包含多個行;能夠處理多個表的視圖(view);支持空間(spatial)和第三方擴展 |
|
支持ACID性質(須要使用InnoDB),主從複製和主主複製 (master/master replication) |
PostgreSQL |
關係數據庫 |
每一個數據庫能夠包含多個表,每一個表能夠包含多個行;能夠處理多個表的視圖;支持空間和第三方擴展;支持可定製類型 |
|
支持ACID性質,主從複製,由第三方支持的多主複製(multi-master replication) |
MongoDB |
使用硬盤存儲(on-disk)的非關係文檔存儲 |
每一個數據庫能夠包含多個表,每一個表能夠包含多個無schema(schema-less)的BSON文檔 |
建立命令、讀取命令、更新命令、刪除命令、條件查詢命令等 |
支持map-reduce操做,主從複製,分片,空間索引(spatial index) |
在使用相似Redis這樣的內存數據庫時,一個首先要考慮的問題就是「當服務器被關閉時,服務器存儲的數據將何去何從呢?」Redis擁有兩種不一樣 形式的持久化方法,它們均可以用小而緊湊的格式將存儲在內存中的數據寫入硬盤:第一種持久化方法爲時間點轉儲(point-in-time dump),轉儲操做既能夠在「指定時間段內有指定數量的寫操做執行」這一條件被知足時執行,又能夠經過調用兩條轉儲到硬盤(dump-to-disk) 命令中的任何一條來執行;第二種持久化方法將全部修改了數據庫的命令都寫入一個只追加(append-only)文件裏面,用戶能夠根據數據的重要程度, 將只追加寫入設置爲從不一樣步(sync)、每秒同步一次或者每寫入一個命令就同步一次。咱們將在第4章中更加深刻地討論這些持久化選項。
另外,儘管Redis的性能很好,但受限於Redis的內存存儲設計,有時候只使用一臺Redis服務器可能沒有辦法處理全部請求。所以,爲了擴展 Redis的讀性能,併爲Redis提供故障轉移(failover)支持,Redis實現了主從複製特性:執行復制的從服務器會鏈接上主服務器,接收主 服務器發送的整個數據庫的初始副本(copy);以後主服務器執行的寫命令,都會被髮送給全部鏈接着的從服務器去執行,從而實時地更新從服務器的數據集。 由於從服務器包含的數據會不斷地進行更新,因此客戶端能夠向任意一個從服務器發送讀請求,以此來避免對主服務器進行集中式的訪問。咱們將在第4章中更加深 入地討論Redis從服務器。
有memcached使用經驗的讀者可能知道,用戶只能用APPEND
命令將數據添加到已有字符串的末尾。memcached的文檔中聲明,能夠用APPEND
命 令來管理元素列表。這很好!用戶能夠將元素追加到一個字符串的末尾,並將那個字符串看成列表來使用。但隨後如何刪除這些元素呢?memcached採用的 辦法是經過黑名單(blacklist)來隱藏列表裏面的元素,從而避免對元素執行讀取、更新、寫入(包括在一次數據庫查詢以後執行的memcached 寫入)等操做。相反地,Redis的LIST
和SET
容許用戶直接添加或者刪除元素。
使用Redis而不是memcached來解決問題,不只可讓代碼變得更簡短、更易懂、更易維護,並且還可使代碼的運行速度更快(由於用戶不須要經過讀取數據庫來更新數據)。除此以外,在其餘許多狀況下,Redis的效率和易用性也比關係數據庫要好得多。
數據庫的一個常見用法是存儲長期的報告數據,並將這些報告數據用做固定時間範圍內的聚合數據(aggregates)。收集聚合數據的常見作法是: 先將各個行插入一個報告表裏面,以後再經過掃描這些行來收集聚合數據,並根據收集到的聚合數據來更新聚合表中已有的那些行。之因此使用插入行的方式來存 儲,是由於對於大部分數據庫來講,插入行操做的執行速度很是快(插入行只會在硬盤文件末尾進行寫入)。不過,對錶裏面的行進行更新倒是一個速度至關慢的操 做,由於這種更新除了會引發一次隨機讀(random read)以外,還可能會引發一次隨機寫(random write)。而在Redis裏面,用戶能夠直接使用原子的(atomic)INCR
命令及其變種來計算聚合數據,而且由於Redis將數據存儲在內存裏面2,並且發送給Redis的命令請求並不須要通過典型的查詢分析器(parser)或者查詢優化器(optimizer)進行處理,因此對Redis存儲的數據執行隨機寫的速度老是很是迅速的。
使用 Redis 而不是關係數據庫或者其餘硬盤存儲數據庫,能夠避免寫入沒必要要的臨時數據,也免去了對臨時數據進行掃描或者刪除的麻煩,並最終改善程序的性能。雖然上面列舉的都是一些簡單的例子,但它們很好地證實了「工具會極大地改變人們解決問題的方式」這一點。
除了第6章提到的任務隊列(task queue)以外,本書的大部份內容都致力於實時地解決問題。本書經過展現各類技術並提供可工做的代碼來幫助讀者消滅瓶頸、簡化代碼、收集數據、分發 (distribute)數據、構建實用程序(utility),並最終幫助讀者更輕鬆地完成構建軟件的任務。只要正確地使用書中介紹的技術,讀者的軟件 就能夠擴展至令那些所謂的「Web擴展技術(web-sacle technology)」相形見絀的地步。
在瞭解了Redis是什麼、它能作什麼以及咱們爲何要使用它以後,是時候來實際地使用一下它了。接下來的一節將對Redis提供的數據結構進行介紹,說明這些數據結構的做用,並展現操做這些數據結構的其中一部分命令。
正如以前的表1-1所示,Redis能夠存儲鍵與5種不一樣數據結構類型之間的映射,這5種數據結構類型分別爲STRING
(字符串)、LIST
(列表)、SET
(集合)、HASH
(散列)和ZSET
(有序集合)。有一部分Redis命令對於這5種結構都是通用的,如DEL
、TYPE
、RENAME
等;但也有一部分Redis命令只能對特定的一種或者兩種結構使用,第3章將對Redis提供的命令進行更深刻的介紹。
大部分程序員應該都不會對Redis的STRING
、LIST
、HASH
這3種結構感到陌生,由於它們和不少編程語言內建的字符串、列表和散列等結構在實現和語義(semantics)方面都很是類似。有些編程語言還有集合數據結構,在實現和語義上相似於Redis的SET
。ZSET
在某種程度上是一種Redis特有的結構,可是當你熟悉了它以後,就會發現它也是一種很是有用的結構。表1-2對比了Redis提供的5種結構,說明了這些結構存儲的值,並簡單介紹了它們的語義。
表1-2 Redis提供的5種結構
結構類型 |
結構存儲的值 |
結構的讀寫能力 |
---|---|---|
|
能夠是字符串、整數或者浮點數 |
對整個字符串或者字符串的其中一部分執行操做;對整數和浮點數執行自增(increment)或者自減(decrement)操做 |
|
一個鏈表,鏈表上的每一個節點都包含了一個字符串 |
從鏈表的兩端推入或者彈出元素;根據偏移量對鏈表進行修剪(trim);讀取單個或者多個元素;根據值查找或者移除元素 |
|
包含字符串的無序收集器(unordered collection),而且被包含的每一個字符串都是獨一無2、各不相同的 |
添加、獲取、移除單個元素;檢查一個元素是否存在於集合中;計算交集、並集、差集;從集合裏面隨機獲取元素 |
|
包含鍵值對的無序散列表 |
添加、獲取、移除單個鍵值對;獲取全部鍵值對 |
|
字符串成員(member)與浮點數分值(score)之間的有序映射,元素的排列順序由分值的大小決定 |
添加、獲取、刪除單個元素;根據分值範圍(range)或者成員來獲取元素 |
命令列表 本節在介紹每一個數據類型的時候,都會在一個表格裏面展現一小部分處理這些數據結構的命令,以後的第 3 章會展現一個更詳細(但仍不完整)的命令列表,完整的 Redis 命令列表能夠在http://redis.io/commands找到。
這一節將介紹如何表示Redis的這5種結構,而且還會介紹Redis命令的使用方法,從而爲本書的後續內容打好基礎。本書展現的全部示例代碼都是 用Python寫的,若是讀者已經按照附錄A裏面描述的方法安裝好了Redis,那麼應該也已經安裝好了Python,以及在Python裏面使用 Redis所需的客戶端庫。只要讀者在電腦裏面安裝了Redis、Python和redis-py庫,就能夠在閱讀本書的同時,嘗試執行書中展現的示例代 碼了。
請安裝Redis和Python 在閱讀後續內容以前,請讀者先按照附錄A中介紹的方法安裝Redis和Python。若是讀者以爲附錄A描述的安裝方法過於複雜,那麼這裏有一個更簡單的方法,但這個方法只能用於Debian系統(或者該系統的衍生系統):從http://redis.io/download下載Redis的壓縮包,解壓壓縮包,執行make && sudo make install
,以後再執行sudo python -m easy_install redis hiredis
(hiredis是可選的,它是一個使用C語言編寫的高性能Redis客戶端)。
若是讀者熟悉過程式編程語言或者面向對象編程語言,那麼即便沒有使用過Python,應該也能夠看懂Python代碼。另外一方面,若是讀者決定使用其餘編程語言來操做Redis,那麼就須要本身來將本書的Python代碼翻譯成正在使用的語言的代碼。
使用其餘語言編寫的示例代碼 儘管沒有包含在書中,但本書展現的Python示例代碼已經被翻譯成了Ruby代碼、Java代碼和JavaScript代碼,這些翻譯代碼能夠在https://github.com/josiahcarlson/redis-in-action下載到。跟Python編寫的示例代碼同樣,這些翻譯代碼也包含相應的註釋,方便讀者參考。
爲了讓示例代碼儘量地簡單,本書會盡可能避免使用Python的高級特性,並使用函數而不是類或者其餘東西來執行Redis操做,以此來將焦點放在 使用Redis解決問題上面,而沒必要過多地關注Python的語法。本節將使用redis-cli控制檯與Redis進行互動。首先,讓咱們來了解一下 Redis中最簡單的結構:STRING
。
Redis的STRING
和其餘編程語言或者其餘鍵值存儲提供的字符串很是類似。本書在使用圖片表示鍵和值的時候,一般會將鍵名(key name)和值的類型放在方框的頂部,並將值放在方框的裏面。圖1-1以鍵爲hello
、值爲world
的STRING
爲例,分別標記了方框的各個部分。
圖1-1 一個STRING
示例,鍵爲hello
,值爲world
STRING
擁有一些和其餘鍵值存儲類似的命令,好比GET
(獲取值)、SET
(設置值)和DEL
(刪除值)。若是讀者已經按照附錄A中給出的方法安裝了Redis,那麼能夠根據代碼清單1-1展現的例子,嘗試使用redis-cli執行SET
、GET
和DEL
,表1-3描述了這3個命令的基本用法。
表1-3 字符串命令
命令 |
行爲 |
---|---|
|
獲取存儲在給定鍵中的值 |
|
設置存儲在給定鍵中的值 |
|
刪除存儲在給定鍵中的值(這個命令能夠用於全部類型) |
代碼清單1-1 SET
、GET
和DEL
的使用示例
使用 redis-cli 爲了讓讀者在一開始就能便捷地與 Redis 進行交互,本章將使用redis-cli這個交互式客戶端來介紹Redis命令。
除了可以GET
、SET
和DEL
字符串值以外,Redis還提供了一些能夠對字符串的其中一部份內容進行讀取和寫入的命令,以及一些能對字符串存儲的數值執行自增或者自減操做的命令。第3章將對這些命令進行介紹,可是在此以前,咱們還有許多基礎知識須要瞭解,下面來看一下Redis的列表及其功能。
Redis對鏈表(linked-list)結構的支持使得它在鍵值存儲的世界中獨樹一幟。一個列表結構能夠有序地存儲多個字符串,和表示字符串時使用的方法同樣,本節使用帶有標籤的方框來表示列表,並將列表包含的元素放在方框裏面。圖1-2展現了一個這樣的示例。
圖1-2 list-key
是一個包含3個元素的列表鍵,注意列表裏面的元素是能夠重複的
Redis列表可執行的操做和不少編程語言裏面的列表操做很是類似:LPUSH
命令和RPUSH
命令分別用於將元素推入列表的左端(left end)和右端(right end);LPOP
命令和RPOP
命令分別用於從列表的左端和右端彈出元素;LINDEX
命令用於獲取列表在給定位置上的一個元素;LRANGE
命令用於獲取列表在給定範圍上的全部元素。代碼清單1-2展現了一些列表命令的使用示例,表1-4簡單介紹了示例中用到的各個命令。
表1-4 列表命令
命令 |
行爲 |
---|---|
|
將給定值推入列表的右端 |
|
獲取列表在給定範圍上的全部值 |
|
獲取列表在給定位置上的單個元素 |
|
從列表的左端彈出一個值,並返回被彈出的值 |
代碼清單1-2 RPUSH
、LRANGE
、LINDEX
和LPOP
的使用示例
即便Redis的列表只支持以上提到的幾個命令,它也已經能夠用來解決不少問題了,但Redis並無就此止步——除了上面提到的命令之 外,Redis列表還擁有從列表裏面移除元素的命令、將元素插入列表中間的命令、將列表修剪至指定長度(至關於從列表的其中一端或者兩端移除元素)的命 令,以及其餘一些命令。第3章將介紹許多列表命令,可是在此以前,讓咱們先來了解一下Redis的集合。
Redis 的集合和列表均可以存儲多個字符串,它們之間的不一樣在於,列表能夠存儲多個相同的字符串,而集合則經過使用散列表來保證本身存儲的每一個字符串都是各不相同 的(這些散列表只有鍵,但沒有與鍵相關聯的值)。本書表示集合的方法和表示列表的方法基本相同,圖1-3展現了一個包含3個元素的示例集合。
圖1-3 set-key
是一個包含3個元素的集合鍵
由於Redis的集合使用無序(unordered)方式存儲元素,因此用戶不能像使用列表那樣,將元素推入集合的某一端,或者從集合的某一端彈出元素。不過用戶可使用SADD
命令將元素添加到集合,或者使用SRAM
命令從集合裏面移除元素。另外還能夠經過SISMEMBER
命令快速地檢查一個元素是否已經存在於集合中,或者使用SMEMBERS
命令獲取集合包含的全部元素(若是集合包含的元素很是多,那麼SMEMBERS
命令的執行速度可能會很慢,因此請謹慎地使用這個命令)。代碼清單1-3展現了一些集合命令的使用示例,表1-5簡單介紹了代碼清單裏面用到的各個命令。
代碼清單1-3 SADD
、SMEMBERS
、SISMEMBER
和SREM
的使用示例
表1-5 集合命令
命令 |
行爲 |
---|---|
|
將給定元素添加到集合 |
|
返回集合包含的全部元素 |
|
檢查給定元素是否存在於集合中 |
|
若是給定的元素存在於集合中,那麼移除這個元素 |
跟字符串和列表同樣,集合除了基本的添加操做和移除操做以外,還支持不少其餘操做,好比SINTER
、SUNION
、SDIFF``這
3個命令就能夠分別執行常見的交集計算、並集計算和差集計算。第3章將對集合的相關命令進行更詳細的介紹,另外第7章還會展現如何使用集合來解決多個問題。不過別心急,由於在Redis提供的5種數據結構中,還有兩種咱們還沒有了解,讓咱們先來看看Redis的散列。
Redis的散列能夠存儲多個鍵值對之間的映射。和字符串同樣,散列存儲的值既能夠是字符串又能夠是數字值,而且用戶一樣能夠對散列存儲的數字值執行自增操做或者自減操做。圖1-4展現了一個包含兩個鍵值對的散列。
圖1-4 hash-key
是一個包含兩個鍵值對的散列鍵
散列在不少方面就像是一個微縮版的Redis,很多字符串命令都有相應的散列版本。代碼清單1-4展現了怎樣對散列執行插入元素、獲取元素和移除元素等操做,表1-6簡單介紹了代碼清單裏面用到的各個命令。
代碼清單1-4 HSET
、HGET
、HGETALL
和HDEL
的使用示例
表1-6 散列命令
命令 |
行爲 |
---|---|
|
在散列裏面關聯起給定的鍵值對 |
|
獲取指定散列鍵的值 |
|
獲取散列包含的全部鍵值對 |
|
若是給定鍵存在於散列裏面,那麼移除這個鍵 |
熟悉文檔數據庫的讀者能夠將Redis的散列看做是文檔數據庫裏面的文檔,而熟悉關係數據庫的讀者則能夠將Redis的散列看做是關係數據庫裏面的行,由於散列、文檔和行這三者都容許用戶同時訪問或者修改一個或多個域(field)。最後,讓咱們來了解一下Redis的5種數據結構中的最後一種:有序集合。
有序集合和散列同樣,都用於存儲鍵值對:有序集合的鍵被稱爲成員(member),每一個成員都是獨一無二的;而有序集合的值則被稱爲分值(score),分值必須爲浮點數。有序集合是Redis裏面惟一一個既能夠根據成員訪問元素(這一點和散列同樣),又能夠根據分值以及分值的排列順序來訪問元素的結構。圖1-5展現了一個包含兩個元素的有序集合示例。
圖1-5 zset-key
是一個包含兩個元素的有序集合鍵
和Redis的其餘結構同樣,用戶能夠對有序集合執行添加、移除和獲取等操做,代碼清單1-5展現了這些操做的執行示例,表1-7簡單介紹了代碼清單裏面用到的各個命令。
代碼清單1-5 ZADD
、ZRANGE
、ZRANGEBYSCORE
和ZREM
的使用示例
表1-7 有序集合命令
命令 |
行爲 |
---|---|
|
將一個帶有給定分值的成員添加到有序集合裏面 |
|
根據元素在有序排列中所處的位置,從有序集合裏面獲取多個元素 |
|
獲取有序集合在給定分值範圍內的全部元素 |
|
若是給定成員存在於有序集合,那麼移除這個成員 |
如今讀者應該已經知道有序集合是什麼和它能幹什麼了,到目前爲止,咱們基本瞭解了Redis提供的5種結構。接下來的一節將展現如何經過結合散列的數據存儲能力和有序集合內建的排序能力來解決一個常見的問題。
在對Redis提供的5種結構有了基本的瞭解以後,如今是時候來學習一下怎樣使用這些結構來解決實際問題了。最近幾年,愈來愈多的網站開始提供對網 頁連接、文章或者問題進行投票的功能,其中包括圖1-6展現的reddit以及圖1-7展現的StackOverflow。這些網站會根據文章的發佈時間 和文章得到的投票數量計算出一個評分,而後按照這個評分來決定如何排序和展現文章。本節將展現如何使用Redis來構建一個簡單的文章投票網站的後端。
圖1-6 Reddit是一個能夠對文章進行投票的網站
圖1-7 StackOverflow是一個能夠對問題進行投票的網站
要構建一個文章投票網站,咱們首先要作的就是爲了這個網站設置一些數值和限制條件:若是一篇文章得到了至少200張支持票(up vote),那麼網站就認爲這篇文章是一篇有趣的文章;假如這個網站天天發佈1000篇文章,而其中的50篇符合網站對有趣文章的要求,那麼網站要作的就 是把這50篇文章放到文章列表前100位至少一天;另外,這個網站暫時不提供投反對票(down vote)的功能。
爲了產生一個可以隨着時間流逝而不斷減小的評分,程序須要根據文章的發佈時間和當前時間來計算文章的評分,具體的計算方法爲:將文章獲得的支持票數量乘以一個常數,而後加上文章的發佈時間,得出的結果就是文章的評分。
咱們使用從UTC時區1970年1月1日到如今爲止通過的秒數來計算文章的評分,這個值一般被稱爲Unix時間。之因此選擇使用 Unix時間,是由於在全部可以運行Redis的平臺上面,使用編程語言獲取這個值都是一件很是簡單的事情。另外,計算評分時與支持票數量相乘的常量爲 432,這個常量是經過將一天的秒數(86 400)除以文章展現一天所需的支持票數量(200)得出的:文章每得到一張支持票,程序就須要將文章的評分增長432分。
構建文章投票網站除了須要計算文章評分以外,還須要使用Redis結構存儲網站上的各類信息。對於網站裏的每篇文章,程序都使用一個散列來存儲文章 的標題、指向文章的網址、發佈文章的用戶、文章的發佈時間、文章獲得的投票數量等信息,圖1-8展現了一個使用散列來存儲文章信息的例子。
圖1-8 一個使用散列存儲文章信息的例子
使用冒號做爲分隔符 本書使用冒號(:
)來分隔名字的不一樣部分:好比圖 1-8 裏面的鍵名article:92617
就使用了冒號來分隔單詞article
和文章的ID號92617
,以此來構建命名空間(namespace)。使用:
做爲分隔符只是個人我的喜愛,不過大部分Redis用戶也都是這麼作的,另外還有一些常見的分隔符,如句號(.
)、斜線(/
),有些人甚至還會使用管道符號(|
)。不管使用哪一個符號來作分隔符,都要保持分隔符的一致性。同時,請讀者注意觀察和學習本書使用冒號建立嵌套命名空間的方法。
咱們的文章投票網站將使用兩個有序集合來有序地存儲文章:第一個有序集合的成員爲文章 ID,分值爲文章的發佈時間;第二個有序集合的成員一樣爲文章 ID,而分值則爲文章的評分。經過這兩個有序集合,網站既能夠根據文章發佈的前後順序來展現文章,又能夠根據文章評分的高低來展現文章,圖1-9展現了這 兩個有序集合的一個示例。
圖1-9 兩個有序集合分別記錄了根據發佈時間排序的文章和根據評分排序的文章
爲了防止用戶對同一篇文章進行屢次投票,網站須要爲每篇文章記錄一個已投票用戶名單。爲此,程序將爲每篇文章建立一個集合,並使用這個集合來存儲全部已投票用戶的ID,圖1-10展現了一個這樣的集合示例。
圖1-10 爲100408號文章投過票的一部分用戶
爲了儘可能節約內存,咱們規定當一篇文章發佈期滿一週以後,用戶將不能再對它進行投票,文章的評分將被固定下來,而記錄文章已投票用戶名單的集合也會被刪除。
在實現投票功能以前,讓咱們來看看圖 1-11:這幅圖展現了當115423號用戶給100408號文章投票的時候,數據結構發生的變化。
圖1-11 當115423號用戶給100408號文章投票的時候,數據結構發生的變化
既然咱們已經知道了網站計算文章評分的方法,也知道了網站存儲數據所需的數據結構,那麼如今是時候實際地實現這個投票功能了!當用戶嘗試對一篇文章進行投票時,程序須要使用ZSCORE
命令檢查記錄文章發佈時間的有序集合,判斷文章的發佈時間是否未超過一週。若是文章仍然處於能夠投票的時間範圍以內,那麼程序將使用SADD
命令,嘗試將用戶添加到記錄文章已投票用戶名單的集合裏面。若是添加操做執行成功的話,那麼說明用戶是第一次對這篇文章進行投票,程序將使用ZINCRBY
命令爲文章的評分增長432分(ZINCRBY``命令
用於對有序集合成員的分值執行自增操做),並使用HINCRBY
命令對散列記錄的文章投票數量進行更新(HINCRBY``命令
用於對散列存儲的值執行自增操做),代碼清單1-6展現了投票功能的實現代碼。
代碼清單1-6 article_vote()
函數
Redis事務 從技術上來說,要正確地實現投票功能,咱們須要將代碼清單1-6裏面的SADD
、ZINCRBY
和HINCRBY
這3個命令放到一個事務裏面執行,不過由於本書要等到第4章才介紹Redis事務,因此咱們暫時忽略這個問題。
這個投票功能仍是很不錯的,對吧?那麼發佈文章的功能要怎麼實現呢?
發佈一篇新文章首先須要建立一個新的文章ID,這項工做能夠經過對一個計數器(counter)執行INCR
命令來完成。接着程序須要使用SADD
將文章發佈者的ID添加到記錄文章已投票用戶名單的集合裏面,並使用EXPIRE
命令爲這個集合設置一個過時時間,讓Redis在文章發佈期滿一週以後自動刪除這個集合。以後,程序會使用HMSET
命令來存儲文章的相關信息,並執行兩個ZADD
命令,將文章的初始評分(initial score)和發佈時間分別添加到兩個相應的有序集合裏面。代碼清單1-7展現了發佈新文章功能的實現代碼。
代碼清單1-7 post_article()
函數
好了,咱們已經陸續實現了文章投票功能和文章發佈功能,接下來要考慮的就是如何取出評分最高的文章以及如何取出最新發布的文章了。爲了實現這兩個功能,程序須要先使用ZREVRANGE
命令取出多個文章ID,而後再對每一個文章ID執行一次HGETALL
命令來取出文章的詳細信息,這個方法既能夠用於取出評分最高的文章,又能夠用於取出最新發布的文章。這裏特別要注意的一點是,由於有序集合會根據成員的分值從小到大地排列元素,因此使用ZREVRANGE
命令,以「分值從大到小」的排列順序取出文章ID纔是正確的作法,代碼清單1-8展現了文章獲取功能的實現函數。
代碼清單1-8 get_articles()
函數
Python的默認值參數和關鍵字參數 代碼清單1-8中的get_articles()
函數爲order
參數設置了默認值score:
。 Python語言的初學者可能會對「默認值參數」以及「根據名字(而不是位置)來傳入參數」的一些細節感到陌生。若是讀者在理解函數定義或者參數傳遞方面 有困難,能夠考慮去看看《Python語言教程》,教程裏面對這兩個方面進行了很好的介紹,點擊如下短連接就能夠直接訪問教程的相關章節:http://mng.bz/KM5x。
雖然咱們構建的網站如今已經能夠展現最新發布的文章和評分最高的文章了,但它還不具有目前不少投票網站都支持的羣組(group)功能:這個功能可 以讓用戶只看見與特定話題有關的文章,好比與「可愛的動物」有關的文章、與「政治」有關的文章、與「Java編程」有關的文章或者介紹「Redis用法」 的文章等等。接下來的一節將向咱們展現爲文章投票網站添加羣組功能的方法。
羣組功能由兩個部分組成,一個部分負責記錄文章屬於哪一個羣組,另外一個部分負責取出羣組裏面的文章。爲了記錄各個羣組都保存了哪些文章,網站須要爲每 個羣組建立一個集合,並將全部同屬一個羣組的文章ID都記錄到那個集合裏面。代碼清單1-9展現了將文章添加到羣組裏面的方法,以及從羣組裏面移除文章的 方法。
代碼清單1-9 add_remove_groups()
函數
初看上去,可能會有讀者以爲使用集合來記錄羣組文章並無多大用處。到目前爲止,讀者只看到了集合結構檢查某個元素是否存在的能力,但實際上Redis不只能夠對多個集合執行操做,甚至在一些狀況下,還能夠在集合和有序集合之間執行操做。
爲了可以根據評分對羣組文章進行排序和分頁(paging),網站須要將同一個羣組裏面的全部文章都按照評分有序地存儲到一個有序集合裏面。Redis的ZINTERSTORE
命令能夠接受多個集合和多個有序集合做爲輸入,找出全部同時存在於集合和有序集合的成員,並以幾種不一樣的方式來組合(combine)這些成員的分值(全部集合成員的分值都會被視爲是1)。對於咱們的文章投票網站來講,程序須要使用ZINTERSTORE
命令選出相同成員中最大的那個分值來做爲交集成員的分值:取決於所使用的排序選項,這些分值既能夠是文章的評分,也能夠是文章的發佈時間。
圖 1-12 展現了對一個包含少許文章的羣組集合和一個包含大量文章及評分的有序集合執行ZINTERSTORE
命令的過程,注意觀察那些同時出如今集合和有序集合裏面的文章是怎樣被添加到結果有序集合裏面的。
圖1-12 對集合groups:programming
和有序集合score:
進行交集計算得出了新的有序集合score:programming
,它包含了全部同時存在於集合groups:programming
和有序集合score:
的成員。由於集合groups:programming
的全部成員的分值都被視爲是1
,而有序集合score:
的全部成員的分值都大於1
,而且此次交集計算挑選的分值爲相同成員中的最大分值,因此有序集合score:programming
的成員的分值其實是由有序集合score:
的成員的分值來決定的
經過對存儲羣組文章的集合和存儲文章評分的有序集合執行ZINTERSTORE
命令,程序能夠獲得按照文章評分排序的羣組文章;而經過對存儲羣組文章的集合和存儲文章發佈時間的有序集合執行ZINTERSTORE
命令,程序則能夠獲得按照文章發佈時間排序的羣組文章。若是羣組包含的文章很是多,那麼執行ZINTERSTORE
命令就會比較花時間,爲了儘可能減小Redis的工做量,程序會將這個命令的計算結果緩存60秒。另外,咱們還重用了已有的get_articles()
函數來分頁並獲取羣組文章,代碼清單1-10展現了網站從羣組裏面獲取一整頁文章的方法。
代碼清單1-10 get_group_articles()
函數
有些網站只容許用戶將文章放在一個或者兩個羣組裏面(其中一個是「全部文章」羣組,另外一個是最適合文章的羣組)。在這種狀況下,最好直接將文章所在的羣組記錄到存儲文章信息的散列裏面,並在article_vote()
函數的末尾增長一個ZINCRBY
命 令調用,用於更新文章在羣組中的評分。可是在這個示例裏面,咱們構建的文章投票網站容許一篇文章同時屬於多個羣組(好比一篇文章能夠同時屬於「編程」和 「算法」兩個羣組),因此對於一篇同時屬於多個羣組的文章來講,更新文章的評分意味着程序須要對文章所屬的所有羣組執行自增操做。在這種狀況下,若是一篇 文章同時屬於不少個羣組,那麼更新文章評分這一操做可能會變得至關耗時,所以,咱們在get_group_articles()
函數裏面對ZINTERSTORE
命令的執行結果進行了緩存處理,以此來儘可能減小ZINTERSTORE
命令的執行次數。開發者在靈活性或限制條件之間的取捨將改變程序存儲和更新數據的方式,這一點對於任何數據庫都是適用的,Redis也不例外。
練習:實現投反對票的功能
咱們的示例目前只實現了投支持票的功能,可是在不少實際的網站裏面,反對票也能給用戶提供有用的反饋信息。所以,請讀者能想辦法在
article_vote()
函數和post_article()
函數裏面添加投反對票的功能。除此以外,讀者還能夠嘗試爲用戶提供對調投票的功能:好比將支持票轉換成反對票,或者將反對票轉換成支持票。提示:若是讀者在實現對調投票功能時出現了困難,能夠參考一下第3章介紹的SMOVE
命令。
好的,如今咱們已經成功地構建起了一個展現最受歡迎文章的網站後端,這個網站能夠獲取文章、發佈文章、對文章進行投票甚至還能夠對文章進行分組。如 果你以爲前面展現的內容很差理解,或者弄不懂這些示例,又或者沒辦法運行本書提供的源代碼,那麼請閱讀下一節來了解如何獲取幫助。
當你遇到與Redis有關的問題時,不要懼怕求助於別人,由於其餘人可能也遇到過相似的問題。首先,你能夠根據錯誤信息在搜索引擎裏面進行查找,看是否有所發現。
若是搜索一無所得,又或者你遇到的問題與本書的示例代碼有關,那麼你能夠到Manning出版社提供的論壇裏面發問(http://www.manning-sandbox.com/forum.jspa?forumID=809),我和其餘熟悉本書的人將爲你提供幫助。
若是你遇到的問題與Redis自己有關,又或者你正在解決的問題在這本書裏面沒有出現過,那麼你能夠到Redis的郵件列表裏面發問(https://groups.google.com/d/forum/redis-db/),一樣地,我和其餘熟悉Redis的人將爲你提供幫助。
最後,若是你遇到的問題與某個函數庫或者某種編程語言有關,那麼比起在Redis郵件列表裏面發帖提問,更好的方法是直接到你正在使用的那個函數庫或者那種編程語言的郵件列表或論壇裏面尋求幫助。
本章對Redis進行了初步的介紹,說明了Redis與其餘數據庫的相同之處和不一樣之處,以及一些讀者可能會使用Redis的理由。在閱讀本書的後 續章節以前,請記住本書的目標並非構建一個完整的應用或者工具,而是展現各式各樣的問題,並給出使用Redis來解決這些問題的辦法。
本章但願向讀者傳達這樣一個概念:Redis是一個能夠用來解決問題的工具,它既擁有其餘數據庫不具有的數據結構,又擁有內存存儲(這使得 Redis的速度很是快)、遠程(這使得Redis能夠與多個客戶端和服務器進行鏈接)、持久化(這使得服務器能夠在重啓以後仍然保持重啓以前的數據)和 可擴展(經過主從複製和分片)等多個特性,這使得用戶能夠以熟悉的方式爲各類不一樣的問題構建解決方案。
在閱讀本書的後續章節時,請讀者注意本身解決問題的方式發生了什麼變化:你也許會驚訝地發現,本身思考數據問題的方式已經從原來的「怎樣將個人想法塞進數據庫的表和行裏面」,變成了「使用哪一種Redis數據結構來解決這個問題比較好呢?」。