網(wǎng)站推薦
- redis在線嘗試與教程
- redis中文官網(wǎng)
- 《Redis入門指南》
- 《Redis 設(shè)計(jì)與實(shí)現(xiàn)》
- Redis 3.0 源碼注釋
- Redis 2.6 源碼注釋
- 《The Little Redis Book》中文版md
- Redis 命令參考
Redis優(yōu)點(diǎn)簡(jiǎn)述
來(lái)自Redis快速入門
- 異??焖?/strong> : Redis是非常快的,每秒可以執(zhí)行大約110000設(shè)置操作,81000個(gè)/每秒的讀取操作。
- 支持豐富的數(shù)據(jù)類型 : Redis支持最大多數(shù)開發(fā)人員已經(jīng)知道如列表,集合,可排序集合,哈希等數(shù)據(jù)類型。這使得在應(yīng)用中很容易解決的各種問(wèn)題,因?yàn)槲覀冎滥男﹩?wèn)題處理使用哪種數(shù)據(jù)類型更好解決。
- 操作都是原子的 : 所有 Redis 的操作都是原子,從而確保當(dāng)兩個(gè)客戶同時(shí)訪問(wèn) Redis 服務(wù)器得到的是更新后的值(最新值)。
- MultiUtility工具:Redis是一個(gè)多功能實(shí)用工具,可以在很多如:緩存,消息傳遞隊(duì)列中使用(Redis原生支持發(fā)布/訂閱),在應(yīng)用程序中,如:Web應(yīng)用程序會(huì)話,網(wǎng)站頁(yè)面點(diǎn)擊數(shù)等任何短暫的數(shù)據(jù);
-
Redis 有三個(gè)主要使其有別于其它很多競(jìng)爭(zhēng)對(duì)手的特點(diǎn):
Redis是完全在內(nèi)存中保存數(shù)據(jù)的數(shù)據(jù)庫(kù),使用磁盤只是為了持久性目的;
Redis相比許多鍵值數(shù)據(jù)存儲(chǔ)系統(tǒng)有相對(duì)豐富的數(shù)據(jù)類型;
Redis可以將數(shù)據(jù)復(fù)制到任意數(shù)量的從服務(wù)器中。
Redis環(huán)境安裝
- 在 Ubuntu 上安裝 Redis,打開終端輸入以下命令:
$sudo apt-get update
$sudo apt-get install redis-server
# 啟動(dòng) Redis
$redis-server
# 啟動(dòng) 客戶端
$redis-cli
安全訪問(wèn)
- Redis的安全設(shè)計(jì)是在“Redis運(yùn)行在可信環(huán)境”這個(gè)前提下做出的。在生產(chǎn)環(huán)境運(yùn)行時(shí)不能允許外界直接連接到Redis服務(wù)器上,而應(yīng)該通過(guò)應(yīng)用程序進(jìn)行中轉(zhuǎn),運(yùn)行在可信的環(huán)境中是保證Redis安全的最重要方法。
- Redis的默認(rèn)配置會(huì)接受來(lái)自任何地址發(fā)送來(lái)的請(qǐng)求,即在任何一個(gè)擁有公網(wǎng)IP的服務(wù)器上啟動(dòng)Redis服務(wù)器,都可以被外界直接訪問(wèn)到。配置文件中bind參數(shù)的設(shè)置參考配置redis外網(wǎng)可訪問(wèn)。
- 自由地設(shè)置訪問(wèn)規(guī)則需要通過(guò)防火墻來(lái)完成。
-
數(shù)據(jù)庫(kù)密碼
- 通過(guò)配置件中的requirepass參數(shù)為Redis設(shè)置一個(gè)密碼。例如
requirepass asdfghjkl??蛻舳嗣看芜B接到Redis時(shí)都需要發(fā)送密碼,否則Redis會(huì)拒絕執(zhí)行客戶端發(fā)來(lái)的命令。發(fā)送密碼需要使用AUTH命令,例如AUTH asdfghjkl。 - 由于Redis性能極高,并且輸入密碼錯(cuò)誤后Redis并不會(huì)進(jìn)行主動(dòng)延遲(考慮到Redis的單線程模型),所以攻擊者可以通過(guò)窮舉法破解Redis的密碼(1秒鐘能夠嘗試十幾萬(wàn)個(gè)密碼),因此在設(shè)置時(shí)一定要選擇復(fù)雜的密碼。
- 此外,配置Redis復(fù)制的時(shí)候如果主數(shù)據(jù)庫(kù)設(shè)置了密碼,需要在從數(shù)據(jù)庫(kù)的配置文件中通過(guò)masterauth參數(shù)設(shè)置主數(shù)據(jù)庫(kù)的密碼,以使從數(shù)據(jù)庫(kù)連接主數(shù)據(jù)庫(kù)時(shí)自動(dòng)使用AUTH命令認(rèn)證。
- 通過(guò)配置件中的requirepass參數(shù)為Redis設(shè)置一個(gè)密碼。例如
命令重命名
- Redis支持在配置文件中將命令重命名,比如將FLUSHALL命令重命名為一個(gè)比較復(fù)雜的名字,以保證只有自己的應(yīng)用可以使用該命令。例如
rename-command FLUSHALL asdfghjkl。如果希望禁用命令可以重命名為空字符串""。 - 無(wú)論設(shè)置密碼還是重命名命令,都需要保證配置文件的安全性,否則就沒(méi)有任何意義了。
管理工具
-
Redis-cli 即原版客戶端
- 當(dāng)一條命令的執(zhí)行時(shí)間超過(guò)限制時(shí),Redis會(huì)將該命令的執(zhí)行時(shí)間等信息加入耗時(shí)命令日志以供開發(fā)者查看。
可以通過(guò)配置文件的slowlog-log-slower-than參數(shù)設(shè)置這一限制,要注意單位是微秒,默認(rèn)值是10000。
耗時(shí)命令日志存儲(chǔ)在內(nèi)存中,可以通過(guò)配置文件的slowlog-max-len參數(shù)來(lái)限制記錄的條數(shù)。
使用SLOWLOG GET命令獲取。
每條日志由以下4個(gè)部分組成:
(1)該日志唯一ID;
(2)該命令執(zhí)行的Unix時(shí)間;
(3)該命令的耗時(shí)時(shí)間,單位是微秒;
(4)命令及參數(shù)。 - 命令監(jiān)控 MONITOR。一個(gè)客戶端使用該命令會(huì)降低Redis將近一半的負(fù)載能力。可以使用Redis-faina (Instagram團(tuán)隊(duì)開發(fā)的基于MONITOR命令的Redis查詢分析程序)
- 當(dāng)一條命令的執(zhí)行時(shí)間超過(guò)限制時(shí),Redis會(huì)將該命令的執(zhí)行時(shí)間等信息加入耗時(shí)命令日志以供開發(fā)者查看。
-
phpRedisAdmin,圖形化管理工具
安裝:
git clone https://github.com/ErikDubbelboer/phpRedisAdmin.git
cd phpRedisAdmin
git submodule init
git submodule update
- 配置:?默認(rèn)phpRedisAdmin會(huì)連接到127.0.0.1,端口6379,如果需要更改或者添加數(shù)據(jù)庫(kù)信息可以編輯includes文件夾中的config.inc.php文件。
- 由于Redis使用單線程處理命令,所以對(duì)生產(chǎn)環(huán)境下?lián)碛写髷?shù)據(jù)量的數(shù)據(jù)庫(kù)來(lái)說(shuō)不適宜使用該管理器。
- **Redis桌面管理**可從 [redisdesktop](http://redisdesktop.com/download) 下載。Redis 桌面管理器會(huì)提供管理 Redis 鍵和數(shù)據(jù)的用戶界面。
- **Rdbtools**是一個(gè)Redis的快照文件解析器,可以根據(jù)快照文件導(dǎo)出JSON數(shù)據(jù)文件、分析Redis中每個(gè)鍵的占用空間情況等。
### 使用實(shí)例與技巧
1. 文章訪問(wèn)量統(tǒng)計(jì),**使用字符串類型的`INCR posts:文章ID:page.view`來(lái)記錄文章的訪問(wèn)量**。每次訪問(wèn)時(shí)鍵值遞增。(另外文章數(shù)據(jù)也可以在序列化之后使用字符串類型存儲(chǔ))
2. 利用位操作(對(duì)于字符串類型鍵使用)命令可以非常緊湊地存儲(chǔ)布爾值。比如如果網(wǎng)站的每個(gè)用戶都有一個(gè)遞增的整數(shù)ID,如果**使用一個(gè)字符串類型鍵配合位操作來(lái)記錄每個(gè)用戶的性別**(用戶ID作為索引,二進(jìn)制位值1和0表示男性和女性),那么記錄100萬(wàn)個(gè)用戶的性別只需占用100KB多的空間,而且由于GETBIT和SETBIT的時(shí)間復(fù)雜度都是O(1),所以**讀取二進(jìn)制位值性能很高**。(在一臺(tái)2014年的MacBookPro筆記奔上,設(shè)置偏移量232-1的值(即分配500MB的內(nèi)存)需要耗費(fèi)將近1秒的時(shí)間)。要注意的是分配過(guò)大的偏移量不僅會(huì)造成服務(wù)器阻塞,還會(huì)造成空間浪費(fèi)。
位操作:
SETBIT key offset value,GETBIT key offset
BITCOUNT key 可以獲得字符串類型鍵中值是1的二進(jìn)制位個(gè)數(shù)
BITOP可以對(duì)多個(gè)字符串類型鍵進(jìn)行位運(yùn)算,并將結(jié)果存儲(chǔ)在destkey參數(shù)指定的鍵中
3. 利用散列類型存儲(chǔ)文章數(shù)據(jù)。**使用`post:文章ID鍵+title/author/time/content等字段`存儲(chǔ)**。美中不足的是散列類型沒(méi)有類似字符串類型的MGET命令那樣可以通過(guò)一條命令同時(shí)獲得多個(gè)鍵的鍵值的版本,所以對(duì)于每個(gè)文章ID都需要請(qǐng)求一次數(shù)據(jù)庫(kù),也就都會(huì)產(chǎn)生一次`往返時(shí)延(round-trip delay time)`,可以使用**管道和腳本**來(lái)優(yōu)化這個(gè)問(wèn)題。
4. 由于列表類型內(nèi)部是使用雙向鏈表實(shí)現(xiàn),獲取頭尾的元素的速度很快。**使用列表類型實(shí)現(xiàn)社交網(wǎng)站的新鮮事**(關(guān)心的只是最新的內(nèi)容)。由于在兩端插入記錄的時(shí)間復(fù)雜度O(1),**使用列表類型來(lái)記錄日志**,可以保證新加入日志的速度不會(huì)受到已有日志數(shù)量的影響。另外還可以做隊(duì)列使用。
另外可以**使用列表類型鍵posts:list記錄文章ID列表 和 文章評(píng)論列表**。當(dāng)發(fā)布新文章時(shí)使用LPUSH命令把新文章的ID加入這個(gè)列表中,另外刪除文章時(shí)把列表中的文章ID刪除,就像這樣:`LREM posts:list 1 要?jiǎng)h除的文章ID`。存儲(chǔ)評(píng)論時(shí):
# 將評(píng)論序列化成字符串
$serializedComment = serialize($author, $email, $time, $content)
LPUSH post:42:comments, $serializedComment
5. **利用集合類型存儲(chǔ)文章標(biāo)簽(tag)**
6. **使用有序集合類型來(lái)實(shí)現(xiàn)按訪問(wèn)量排序的文章存儲(chǔ)。**在集合類型的基礎(chǔ)上有序集合類型為集合中的每個(gè)元素都關(guān)聯(lián)了一個(gè)分?jǐn)?shù),使我們可以獲得分?jǐn)?shù)最高的前N個(gè)元素、指定分?jǐn)?shù)范圍的元素等。集合中的元素不同,但分?jǐn)?shù)可以相同。在這個(gè)鍵中以文章ID作為元素,以該文章的點(diǎn)擊量作為該元素的分?jǐn)?shù)。將該鍵命名為`posts:page.view`,每次用戶訪問(wèn)一次文章時(shí),博客程序就通過(guò)`ZINCRBY posts:page.view 1 文章ID` 更新訪問(wèn)量。獲取文章訪問(wèn)量通過(guò)`ZSCORE posts:page.view 文章ID` 來(lái)實(shí)現(xiàn)。
另外可以**實(shí)現(xiàn)文章按發(fā)布時(shí)間排序**,使元素的分?jǐn)?shù)為文章發(fā)布的Unix時(shí)間。借助`ZREVRANGEBYSCORE`命令還可以輕松獲取指定時(shí)間范圍的文章列表,可以實(shí)現(xiàn)按月份查看文章的功能。
7. **使用列表類型鍵實(shí)現(xiàn)訪問(wèn)頻率限制**。如果要精確地保證每分鐘最多訪問(wèn)10次,需要記錄下用戶每次訪問(wèn)的時(shí)間。因此對(duì)每個(gè)用戶,我們使用一個(gè)列表類型的鍵來(lái)記錄他最近10次訪問(wèn)博客的時(shí)間。一旦鍵中的元素超過(guò) 10 個(gè),就判斷時(shí)間最早的元素距現(xiàn)在的時(shí)間是否小于1分鐘。如果是則表示用戶最近1分鐘的訪問(wèn)次數(shù)超過(guò)了10次;如果不是就將現(xiàn)在的時(shí)間加入到列表中,同時(shí)把最早的元素刪除。
$listLength = LLEN rate.limiting:$IP
if $listLength < 10
LPUSH rate.limiting:$IP, now()
else
$time = LINDEX rate.limiting:$IP, -1
if now() - $time < 60
print 訪問(wèn)頻率超過(guò)了限制,請(qǐng)稍后再試。
else
LPUSH rate.limiting:$IP, now()
LTRIM rate.limiting:$IP, 0, 9
代碼中now()的功能是獲得當(dāng)前的Unix時(shí)間。由于需要記錄每次訪問(wèn)的時(shí)間,所以當(dāng)要限制"A時(shí)間最多訪問(wèn)B次"時(shí),如果"B"的數(shù)值較大,此方法會(huì)占用較多的存儲(chǔ)空間,實(shí)際使用時(shí)還需要開發(fā)者自己去權(quán)衡。除此之外該方法也會(huì)出現(xiàn)**競(jìng)態(tài)條件**,同樣可以通過(guò)腳本功能避免。
8. **對(duì)列表,集合,有序集合類型鍵使用sort … by … 排序**。SORT命令可以使用于 集合,列表,有序集合。針對(duì)有序集合類型排序時(shí)會(huì)忽略元素的分?jǐn)?shù),只針對(duì)元素自身的值進(jìn)行排序。除了可以排列數(shù)字外,sort命令還可以通過(guò)ALPHA參數(shù)實(shí)現(xiàn)按照字典順序排列非數(shù)字元素。
- `SORT tag:ruby:posts BY post:*->time DESC` 由 `tag:ruby:posts`獲得的值替換`*`,一般為ID,即依據(jù)`post:ID`的`time`的散列值來(lái)對(duì)`tag:ruby:posts`排序
- 可以再加上`GET`參數(shù),同樣可以使用*號(hào),`GET #`得到元素本身,還有`STORE key`參數(shù)
SORT tag:ruby:posts
BY post:->time DESC
GET post:->title GET post:*->time GET #
STORE sort.result
- 使用SORT命令時(shí)注意使用LIMIT參數(shù)只獲取需要的數(shù)據(jù)(M),盡可能減少待排序鍵中元素的數(shù)量(N),盡可能在數(shù)據(jù)量大時(shí)緩存結(jié)果。時(shí)間復(fù)雜度為`O(n+mlog(m))`
9. **BRPOP實(shí)現(xiàn)任務(wù)隊(duì)列,以及通知消費(fèi)者的優(yōu)先級(jí)隊(duì)列**。BRPOP和RPOP的區(qū)別是當(dāng)列表中沒(méi)有元素時(shí)BRPOP命令會(huì)一直阻塞住連接,直到有新元素加入。BRPOP接收兩個(gè)參數(shù),第一個(gè)是鍵名(可以多個(gè)鍵),第二個(gè)是超時(shí)時(shí)間(0表示不限)。如果有多個(gè)鍵,當(dāng)多個(gè)鍵都有元素則按照從左到右的順序取第一個(gè)鍵中的一個(gè)元素。借此特性可以實(shí)現(xiàn)區(qū)分優(yōu)先級(jí)的任務(wù)隊(duì)列。
### Redis概念拾遺
- 利用Redis中的**事務(wù)**(transaction)來(lái)進(jìn)行多個(gè)連續(xù)命令的原子操作。
def follow($currentUser, $targetUser)
SADD user:$currentUser:following, $targetUser
SADD user:$targetUser:followers, $currentUser
這種操作容易產(chǎn)生導(dǎo)致錯(cuò)誤的競(jìng)態(tài)
利用事務(wù)來(lái)使多條數(shù)據(jù)庫(kù)操作變?yōu)樵硬僮?
MUTLI
…
EXEC
Redis保證**一個(gè)事務(wù)中執(zhí)行的命令要么都執(zhí)行,要么都不執(zhí)行**。如果在發(fā)送EXEC之前客戶端斷線了,則Redis會(huì)清空事務(wù)隊(duì)列,事務(wù)中的所有命令都不會(huì)執(zhí)行。Redis的事務(wù)能夠保證一個(gè)事務(wù)中的命令依次執(zhí)行不被其他命令插入。
- 限制Redis**最大可用內(nèi)存大小**,修改配置文件的maxmemory參數(shù)(單位是字節(jié)),當(dāng)超出了這個(gè)限制時(shí)Redis會(huì)依據(jù)maxmemory-policy參數(shù)指定的策略來(lái)刪除不需要的鍵直到Redis占用的內(nèi)存小于指定內(nèi)存。
- Redis具有**發(fā)布/訂閱模式**:publish/subscribe。與ROS(機(jī)器人操作系統(tǒng))中的發(fā)布/訂閱類似。
- 使用**管道、腳本**優(yōu)化往返延時(shí)。
- 修改配置文件實(shí)現(xiàn)**內(nèi)部編碼優(yōu)化**。
- Redis的每個(gè)鍵值都是使用一個(gè)**redisObject結(jié)構(gòu)體**保存的,
```C
typedef struct redisObject {
unsigned type:4;
unsigned notused:2; /* Not used */
unsigned encoding:4;
unsigned lru:22; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;
關(guān)于里面的特殊語(yǔ)法參考Redis源碼閱讀筆記,講解很清楚。
Redis使用一個(gè)sdshdr類型的變量來(lái)存儲(chǔ)字符串,而redisObject的ptr字段指向的是該變量的地址。sdshdr的定義如下:
struct sdshdr {
int len;
int free;
char buf[];
};
其中l(wèi)en字段表示的是字符串的長(zhǎng)度,free字段表示buf中的剩余空間,而buf字段存儲(chǔ)的才是字符串的內(nèi)容。所以當(dāng)執(zhí)行SET key foobar時(shí),存儲(chǔ)鍵值需要占用的空間是sizeof(redisObject) + sizeof(sdshdr) + strlen("foobar") = 30字節(jié)。而當(dāng)鍵值內(nèi)容可以用一個(gè)64位有符號(hào)整數(shù)表示時(shí),Redis會(huì)將鍵值轉(zhuǎn)換成long類型來(lái)存儲(chǔ)。如SET key 123456,實(shí)際占用的空間是sizeof(redisObject) = 16字節(jié),比存儲(chǔ)"foobar"節(jié)省了一半的存儲(chǔ)空間。
-
列表類型和有序集合類型辯異:
- 相似:
- 二者都是有序的。
- 二者都可以獲得某一范圍的元素。
- 不同:
- 列表類型是通過(guò)鏈表實(shí)現(xiàn)的,獲取靠近兩端的數(shù)據(jù)速度極快,而當(dāng)元素增多后,訪問(wèn)中間數(shù)據(jù)的速度會(huì)較慢,所以它更加適合實(shí)現(xiàn)如“新鮮事”或“日志”這樣很少訪問(wèn)中間元素的應(yīng)用。
- 有序集合類型是使用散列表和跳躍表(Skip list)實(shí)現(xiàn)的,所以即使讀取位于中間部分的數(shù)據(jù)速度也很快(時(shí)間復(fù)雜度是O(log(N)))。
- 列表中不能簡(jiǎn)單地調(diào)整某個(gè)元素的位置,但是有序集合可以(通過(guò)更改這個(gè)元素的分?jǐn)?shù))。
- 有序集合要比列表類型更耗費(fèi)內(nèi)存。
持久化
-
RDB方式,通過(guò)快照,即當(dāng)符合一定條件時(shí)Redis會(huì)自動(dòng)將內(nèi)存中的所有數(shù)據(jù)生成一份副本并存儲(chǔ)在硬盤上。
條件:- 根據(jù)配置規(guī)則進(jìn)行自動(dòng)快照
- 用戶執(zhí)行SAVE(同步)或BGSAVE(異步)命令
- 執(zhí)行FLUSHALL命令
- 執(zhí)行復(fù)制(replication)時(shí)
-
AOF方式,默認(rèn)沒(méi)有開啟,通過(guò)
appendonly yes配置參數(shù)啟動(dòng)。開啟后每執(zhí)行一條會(huì)更改Redis中的數(shù)據(jù)的命令,Redis就會(huì)將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同。此外,由于操作系統(tǒng)的緩存機(jī)制,數(shù)據(jù)并沒(méi)有真正地寫入硬盤,而是進(jìn)入了系統(tǒng)的硬盤緩存。在默認(rèn)情況下系統(tǒng)每30秒會(huì)執(zhí)行一次同步操作,以便將硬盤緩存中的內(nèi)容真正寫入硬盤。
可以通過(guò)appendfsync參數(shù)設(shè)置同步的時(shí)機(jī):
# appendfsync always
appendfsync everysec
# appendfsync no
lua腳本簡(jiǎn)要
使用腳本可以
- 減少網(wǎng)絡(luò)開銷:減少往返時(shí)延
- 原子操作:Redis會(huì)將整個(gè)腳本作為一個(gè)整體執(zhí)行,中間不會(huì)被其他命令插入。無(wú)需擔(dān)心競(jìng)態(tài),無(wú)需使用事務(wù)。
- 復(fù)用:客戶端發(fā)送的腳本會(huì)永久儲(chǔ)存在Redis中,這就意味著其他客戶端(可以是其他語(yǔ)言開發(fā)的項(xiàng)目)可以復(fù)用這一腳本而不需要使用代碼完成同樣的邏輯。
利用腳本實(shí)現(xiàn)訪問(wèn)頻率限制:
local times = redis.call( 'incr', KEYS[1] )
if times == 1 then
--KEYS[1]鍵剛創(chuàng)建,所以為其設(shè)置生存時(shí)間
redis.call( 'expire', KEYS[1], ARGV[1] )
end
if times > tonumber(ARGV[2]) then
return 0
end
return 1
通過(guò)
$redis-cli --eval /path/to/ratelimiting.lua rate.limiting:IP , 10 3
--eval是告訴redis-cli讀取并運(yùn)行后面的Lua腳本
腳本參數(shù)以‘空格,空格’分隔的是KEYS和ARGV參數(shù),該命令的作用是將訪問(wèn)頻率限制為每10秒最多3次。