深入理解Redis

Redis作為目前非常火的非關(guān)系型數(shù)據(jù)庫我們有必要花精力和時間來研究,redis是一個開源的、使用C語言編寫的單線程的、支持網(wǎng)絡(luò)交互的、可基于內(nèi)存也可持久化的Key-Value數(shù)據(jù)庫。

Redis的優(yōu)勢

redis較其他數(shù)據(jù)庫最明顯的優(yōu)勢就是性能和并發(fā)上:

  • redis是純內(nèi)存操作,所以它能夠做出快速響應(yīng);
  • 高并發(fā)的情況下,如果所有的請求都直接訪問MySql會導(dǎo)致數(shù)據(jù)庫連接異常,而redis支持高性能的主從復(fù)制的集群策略,這樣就大大提高滿足高并發(fā)訪問及快速響應(yīng),請求會優(yōu)先訪問redis請求數(shù)據(jù),從而避免高并發(fā)情況下都直接訪問數(shù)據(jù)庫;

Redis單線程

既然redis是單線程,那為什么它性能還這么高呢?

  • redis是純內(nèi)存操作;
  • 因為單線程,避免了頻繁的上下文切換;
  • 采用I/O多路復(fù)用機制;

Redis支持多數(shù)據(jù)類型

string,hash,list,set,sorted set

1. string

字符串類型,其實它表示一個可變的字節(jié)數(shù)組,其key對應(yīng)的value值可以使字符串也可以是數(shù)字。如果對應(yīng)的value值是字符串時,常規(guī)的字符串操作基本上都是支持的;如果對應(yīng)的value值是數(shù)字,可以通過incr/incrby/decr/decrby等操作來實現(xiàn)類似計數(shù)器的功能。

2. list

利用這個數(shù)據(jù)結(jié)構(gòu)可以實現(xiàn)最新消息的排行、消息隊列的存取等。

3. hash

可以類比python的字典,比如用于存放微博用戶的信息(出生年月、性別、手機號等)

4. set

全局去重

5. sorted set

帶有權(quán)重(score)的set,根據(jù)score自動排序

Redis的持久化

持久化就是把數(shù)據(jù)保存到磁盤上,而redis提供了兩種持久化的方式,分別是RDB(Redis DataBase)和AOF(Append Only File)。

RDB,簡而言之,就是在不同的時間點,將redis存儲的數(shù)據(jù)生成快照并存儲到磁盤等介質(zhì)上。

AOF,Append Only File,則是換了一個角度來實現(xiàn)持久化,那就是將redis執(zhí)行過的所有寫指令記錄下來,在下次redis重新啟動時,只要把這些寫指令從前到后再重復(fù)執(zhí)行一遍,就可以實現(xiàn)數(shù)據(jù)恢復(fù)了。

其實RDB和AOF兩種方式也可以同時使用,在這種情況下,如果redis重啟的話,則會優(yōu)先采用AOF方式來進行數(shù)據(jù)恢復(fù),這是因為AOF方式的數(shù)據(jù)恢復(fù)完整度更高。

如果你沒有數(shù)據(jù)持久化的需求,也完全可以關(guān)閉RDB和AOF方式,這樣的話,redis將變成一個純內(nèi)存數(shù)據(jù)庫,就像memcache一樣,下面著重談?wù)凴edis的兩種持久化方式。

1. RDB

RDB方式,是將redis某一時刻的數(shù)據(jù)持久化到磁盤中,是一種快照式的持久化方法。

redis在進行數(shù)據(jù)持久化的過程中,會先將數(shù)據(jù)寫入到一個臨時文件中,待持久化過程都結(jié)束了,才會用這個臨時文件替換上次持久化好的文件。正是這種特性,讓我們可以隨時來進行備份,因為快照文件總是完整可用的。

對于RDB方式,redis會單獨創(chuàng)建(fork)一個子進程來進行持久化,而主進程是不會進行任何IO操作的,這樣就確保了redis極高的性能。

如果需要進行大規(guī)模數(shù)據(jù)的恢復(fù),且對于數(shù)據(jù)恢復(fù)的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效,另外通過RDB對數(shù)據(jù)進行備份容災(zāi)是個不錯的選擇。

雖然RDB有不少優(yōu)點,但它的缺點也是不容忽視的。如果你對數(shù)據(jù)的完整性非常敏感,那么RDB方式就不太適合你,因為即使你每5分鐘都持久化一次,當(dāng)redis故障時,仍然會有近5分鐘的數(shù)據(jù)丟失。所以,redis還提供了另一種持久化方式,那就是AOF。

2. AOF

如果對數(shù)據(jù)的耐久性需求很高,那么RDB快照持久化的方式可能就顯得不那么合適了,此時可以采用AOF,每次有更新數(shù)據(jù)的操作則會把對應(yīng)的命令追加到文件的末尾,等下次redis重啟時通過執(zhí)行文件中的命令來實現(xiàn)數(shù)據(jù)的重建。

AOF通過fsync來進行新命令的追加,默認是間隔1秒。

另外,一般情況下,對于同等規(guī)模的數(shù)據(jù)集,一般AOF文件的體積要比RDB大,并且RDB會具有更好的載入數(shù)據(jù)性能,實際使用場景中,建議兩種持久化方式同時使用。

Redis主從復(fù)制策略

redis主從復(fù)制就是把redis服務(wù)器的數(shù)據(jù)復(fù)制到其他redis節(jié)點,前者是主節(jié)點,后者是從節(jié)點,一般是一主多從的架構(gòu)且一個從節(jié)點只能對應(yīng)唯一的主節(jié)點,另外數(shù)據(jù)的復(fù)制是單向的只能從主節(jié)點到從節(jié)點,而數(shù)據(jù)同步的方法是異步的(從redis 2.8開始,采用psync來實現(xiàn)主從數(shù)據(jù)庫的同步)。

1. 主從復(fù)制的功能

  • 數(shù)據(jù)備份;
  • 故障恢復(fù),當(dāng)主節(jié)點出現(xiàn)故障時,可以切換到從節(jié)點恢復(fù);
  • 讀負載均衡能力加強,一般主節(jié)點承擔(dān)寫而從節(jié)點負責(zé)讀,這樣多個從節(jié)點就分擔(dān)了讀壓力;
  • 主從復(fù)制是redis sentinel和cluster實現(xiàn)的基礎(chǔ);

2. 缺陷

從上面所述不難看出主從沒能解決至少下面三個問題:

  • 無法自動故障切換,只能手動干預(yù)或者寫監(jiān)控程序來實現(xiàn),都比較麻煩;
  • 從節(jié)點故障沒法恢復(fù);
  • 受單機內(nèi)存限制無法存儲更多的數(shù)據(jù),且沒實現(xiàn)寫的負載均衡能力;

3. 數(shù)據(jù)復(fù)制

分部分復(fù)制和全量復(fù)制,得看場景。

4. 超時問題

5. 相關(guān)配置

從節(jié)點配置文件redis.conf加上slaveof <redis_master_ip> <redis_master_port>

Redis Sentinel

在復(fù)制的基礎(chǔ)上,哨兵實現(xiàn)了自動化的故障恢復(fù)。缺陷:寫操作無法負載均衡;存儲能力受到單機的限制。

1. Sentinel模塊構(gòu)成

  • 監(jiān)控模塊:隨時監(jiān)察主從節(jié)點的在線狀態(tài);
  • 自動故障轉(zhuǎn)移:當(dāng)主節(jié)點出現(xiàn)故障時,哨兵會自動開始故障轉(zhuǎn)移的操作,選舉一個從節(jié)點作為新的主節(jié)點,并且其它從節(jié)點會開始復(fù)制新的主節(jié)點;
  • 通知:主要是跟客戶端交互的,比如把故障轉(zhuǎn)移的結(jié)果通知到客戶端;
  • 配置提供者:客戶端在初始化時,通過連接哨兵來獲得當(dāng)前Redis服務(wù)的主節(jié)點地址;

哨兵架構(gòu)的主從節(jié)點跟一般的主從節(jié)點沒有區(qū)別,故障發(fā)現(xiàn)和自動轉(zhuǎn)移是由哨兵控制和完成的;哨兵本質(zhì)上也是redis節(jié)點,不過不存儲數(shù)據(jù);每個哨兵節(jié)點只需配置對主節(jié)點的監(jiān)控,并可自動發(fā)現(xiàn)其他哨兵節(jié)點及從節(jié)點;在哨兵系統(tǒng)啟動和故障轉(zhuǎn)移階段,每個節(jié)點的配置文件都會被重寫,另外,一個哨兵節(jié)點可以監(jiān)控多個主節(jié)點,在sentinel.conf配置多條sentinel monitor語句即可。

2. 實現(xiàn)原理

  • 定時任務(wù):通過向主從節(jié)點發(fā)送info命令獲取最新的主從拓撲結(jié)構(gòu);通過發(fā)布訂閱功能發(fā)現(xiàn)和獲取其他哨兵的信息;通過ping來判斷是不是下線;
  • 主觀下線:單個哨兵通過心跳檢測對服務(wù)器做出的下線判斷;
  • 客觀下線:多個哨兵通過心跳檢測對同一個服務(wù)器做出的下線判斷,并且客觀下線只針對主節(jié)點;
  • 選舉領(lǐng)導(dǎo)者哨兵節(jié)點:通過某種算法選擇一個哨兵節(jié)點來來控制和操作故障轉(zhuǎn)移;
  • 故障轉(zhuǎn)移

3. 相關(guān)配置

配置文件:sentinel.conf
sentinel monitor {masterName} {masterIp} {masterPort} {quorum}哨兵系統(tǒng)最核心的配置。

Redis Cluster

redis集群的設(shè)計可以說是真正實現(xiàn)了redis高可用,集群中是由多個節(jié)點組成,而redis的數(shù)據(jù)是分布在這些節(jié)點上,節(jié)點分為主節(jié)點和從節(jié)點,只有主節(jié)點負責(zé)數(shù)據(jù)的讀寫和集群狀態(tài)的維護,而從節(jié)點只進行主節(jié)點數(shù)據(jù)和狀態(tài)信息的復(fù)制。

redis集群引入了一個新的概念:數(shù)據(jù)分片,它可以使數(shù)據(jù)分散到各個主節(jié)點,解決了單機內(nèi)存大小的限制,存儲量大大增加;另一方面每一個主節(jié)點都可以對外提供讀寫服務(wù),提高了服務(wù)的響應(yīng)能力。redis集群支持主從復(fù)制和自動故障轉(zhuǎn)移的功能,實現(xiàn)高可用。

1. 集群搭建

一般集群的搭建分如下四個步驟:

  • 啟動節(jié)點:仍然使用redis-server命令,需要使用集群模式啟動,cluster-enabled和cluster-config-file這兩個配置參數(shù)是跟集群相關(guān)的,前者是開啟集群模式,默認是單機(standalone)模式,后者是集群節(jié)點配置文件,無須人為修改, 它由 Redis 集群在啟動時創(chuàng)建, 并在有需要時自動進行更新;
  • 節(jié)點握手:各個節(jié)點之間建立網(wǎng)絡(luò)讓各個節(jié)點間可以通訊,相關(guān)的命令cluster meet <node_ip> <node_port>
  • 分配主節(jié)點槽:集群總共有16383個槽,redis-cli -h <master_ip> -p <master_port> cluster addslots {0..5461}
  • 設(shè)置主從關(guān)系:redis-cli cluster replicate <master_node_id>

也可以使用官方推薦的指令redis-trib.rb來完成集群的自動搭建。

Redis分布式鎖/并發(fā)鎖

通過redis的SETNX命令來實現(xiàn),具體就是有且只有當(dāng)該key不存在時,才能獲取鎖設(shè)置value;為避免其他客戶端來競爭鎖造成死鎖的問題,可以給key設(shè)置一個合理的過期時間,當(dāng)檢測到key過期時刪除數(shù)據(jù)來釋放鎖以供其他客戶端使用。

另外,存在setnx操作之后expire操作之前服務(wù)器發(fā)生宕機的可能,可以把這兩個操作打包成一個原子性操作,命令格式:set <key> <value> nx ex <expire_time>。

Redis和數(shù)據(jù)庫雙寫及數(shù)據(jù)的一致性

如果只是讀操作其實不用太關(guān)心redis跟mysql數(shù)據(jù)的一致性問題,但在進行增刪改操作時,由于讀寫的并發(fā)及順序問題,可能會造成緩存和數(shù)據(jù)庫數(shù)據(jù)的不一致,現(xiàn)羅列網(wǎng)上的其中兩種解決方案:

  • 延時雙刪策略:在寫庫前后都加上刪除緩存的操作,且要在寫庫后設(shè)置一個合理的延時時間,保證這個時間內(nèi)其他進程的讀請求已經(jīng)結(jié)束;
  • 訂閱MySQL的bin log(異步更新緩存):一旦發(fā)現(xiàn)數(shù)據(jù)庫有數(shù)據(jù)更新,就將bin log的消息推送給redis達到及時更新緩存的目的;

緩存雪崩、緩存穿透、緩存擊穿

這兩種情形一般中小型企業(yè)不會遇到,但是高并發(fā)情形下需要考慮這兩種問題。

1. 緩存雪崩

緩存數(shù)據(jù)某個時間段大面積失效,高并發(fā)情形下大量請求直接到數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫連接異常。
解決方案:

  • 加鎖排隊;
  • 給數(shù)據(jù)的失效時間加一個隨機值,避免數(shù)據(jù)一時間集體過期失效;
  • 雙緩存,一個緩存設(shè)置失效時間,另一個不設(shè)置;

2. 緩存穿透

特意大量請求緩存不存在的數(shù)據(jù),導(dǎo)致所有請求都直接到了數(shù)據(jù)庫,從而數(shù)據(jù)庫連接異常。
解決方案:

  • 采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的bitmap中,對一定不存在的數(shù)據(jù)進行攔截;
  • 如果返回的結(jié)果為空,不管數(shù)據(jù)是不存在還是因為其他故障,依然對這個空結(jié)果進行緩存,并設(shè)置過期時間(不要超過5mins)

3. 緩存擊穿

指一個key非常熱點,大并發(fā)集中對這個key進行訪問,當(dāng)這個key在失效的瞬間,仍然持續(xù)的大并發(fā)訪問就穿破緩存,轉(zhuǎn)而直接請求數(shù)據(jù)庫。
解決方案:
在訪問key之前,采用SETNX(set if not exists)來設(shè)置另一個短期key來鎖住當(dāng)前key的訪問,訪問結(jié)束再刪除該短期key。

Redis過期刪除和內(nèi)存淘汰機制

對于過期的數(shù)據(jù),redis默認會采用定期刪除+惰性刪除的策略
定期刪除:每隔比如100ms對過期的數(shù)據(jù)進行隨機刪除(顯然,這樣做會導(dǎo)致有些過期數(shù)據(jù)沒被刪除);
惰性刪除:對于那些過期但是通過定期刪除沒刪除的數(shù)據(jù)此時就可以通過惰性刪除策略來刪除了。查詢時先判斷該數(shù)據(jù)是否過期,如果過期則刪除,但是如果短期內(nèi)該數(shù)據(jù)沒被查詢訪問,那么這些數(shù)據(jù)還是沒被刪除;

如此可以設(shè)置內(nèi)存淘汰策略(設(shè)置maxmemeory-policy)來優(yōu)化,目前內(nèi)存淘汰策略有6種:

  • noeviction:當(dāng)內(nèi)存不足以容納新數(shù)據(jù)時直接報錯;
  • allkeys-lru:當(dāng)內(nèi)存不足以容納新數(shù)據(jù),在鍵空間中,移除最近最少使用的key --- 推薦使用
  • allkeys-random:當(dāng)內(nèi)存不足以容納新數(shù)據(jù),在鍵空間中,隨機移除一個key;
  • volatile-lru:當(dāng)內(nèi)存不足以容納新數(shù)據(jù),在設(shè)置了過期時間的鍵空間中,移除最近最少使用的key,這種情況redis既做緩存又做了持久化才用;
  • volatile-random:當(dāng)內(nèi)存不足以容納新數(shù)據(jù),在設(shè)置了過期時間的鍵空間中,隨機移除一個key;
  • volatile-ttl:當(dāng)內(nèi)存不足以容納新數(shù)據(jù),在設(shè)置了過期時間的鍵空間中,有更早過期時間的key優(yōu)先移除;

具體采取哪種內(nèi)存淘汰策略要結(jié)合實際的應(yīng)用場景。

Redis的事務(wù)處理

雖然redis提供的事務(wù)功能不能算是嚴格意義上的事務(wù),但在服務(wù)器不出問題的情況下,還是可以保證命令打包執(zhí)行的。

  • MULTI:用來組裝一個事務(wù);
  • EXEC:用來執(zhí)行一個事務(wù);
  • DISCARD:用來取消一個事務(wù);
  • WATCH:用來監(jiān)視一些key,一旦這些key在事務(wù)執(zhí)行之前被改變,則取消事務(wù)的執(zhí)行。

python下redis緩存的應(yīng)用場景

一般生產(chǎn)環(huán)境中對于使用頻率很大的數(shù)據(jù)經(jīng)常用Redis作緩存,比如在進行數(shù)據(jù)讀的操作時會先從Redis獲取數(shù)據(jù),如果獲取不到才會從mysql數(shù)據(jù)庫讀。

下面是一個簡單的緩存實現(xiàn)

def cache(cmd_func, ex=86400):
    def wrapper(func):
        def decorater(*args, **kwargs):
            client = redisclient.get_client('cache')
            key = 'br_args:{}'.format(cmd_func)
            value = client.get(key)
            if data:
                data = pickle.loads(value)
            else:
                data = func(*args, **kwargs)
                value = pickle.dumps(data)
                client.set(key, value, ex)
            return data
        return decorater
    return wrapper

python下獲取redis client連接接口可以用redis.StrictRedis()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文是我自己在秋招復(fù)習(xí)時的讀書筆記,整理的知識點,也是為了防止忘記,尊重勞動成果,轉(zhuǎn)載注明出處哦!如果你也喜歡,那...
    波波波先森閱讀 3,480評論 0 40
  • 超強、超詳細Redis入門教程 轉(zhuǎn)載2017年03月04日 16:20:02 16916 轉(zhuǎn)載自: http://...
    邵云濤閱讀 17,623評論 3 313
  • 文章已經(jīng)放到github上 ,如果對您有幫助 請給個star[https://github.com/qqxuanl...
    尼爾君閱讀 2,329評論 0 22
  • 【本教程目錄】 1.redis是什么2.redis的作者3.誰在使用redis4.學(xué)會安裝redis5.學(xué)會啟動r...
    徐猿猿閱讀 1,918評論 0 35
  • 從這篇文章開始,將依次介紹Redis高可用相關(guān)的知識——持久化、復(fù)制(及讀寫分離)、哨兵、以及集群。 本文將先說明...
    不變甄心閱讀 736評論 0 4

友情鏈接更多精彩內(nèi)容