redis基礎和原理

存儲結構

大家一定對字典類型的數據結構非常熟悉,比如map ,通過key value的方式存儲的結構。 redis的全稱是remote dictionary server(遠程字典服務器),它以字典結構存儲數據,並允許其他應用通過TCP協議讀寫字典中的內容。

 

Redis的安裝

redis約定次版本號(第一個小數點後的數字)爲偶數版本是穩定版,如2.8、3.0,4.0 奇數版本爲非穩定版,生產環

境需要使用穩定版;

安裝配置:

    **需安裝tcl yum install tcl 、 yum install gcc

  1. 下載redis的安裝包
  2. tar -zxvf 解壓
  3. cd 到解壓後的目錄
  4. 執行make 完成編譯
  5. make test 測試編譯狀態
  6.  make install {PREFIX=/path}

Redis包含的可執行文件

Redis-server

Redis服務器

Redis-cli

Redis命令行客戶端

Redis-benchmark

Redis性能測試工具

Redis-check-aof

Aof文件修復工具

Redis-check-dump

Rdb文件檢查工具

Redis-sentinel

Sentinel服務器(2.8以後)

常用的命令是redis-server和redis-cli

1. 直接啓動

redis-server ../redis.conf

服務器啓動後默認使用的是6379的端口 ,通過--port可以自定義端口 ; 6379在手機鍵盤上MERZ對應,MERZ是一個人名

Redis-server --port 6380

以守護進程的方式啓動,需要修改redis.conf配置文件中daemonize yes

2. 停止redis

redis-cli SHUTDOWN

考慮到redis有可能正在將內存的數據同步到硬盤中,強行終止redis進程可能會導致數據丟失,正確停止redis的方 式應該是向Redis發送SHUTDOW命令

當redis收到SHUTDOWN命令後,會先斷開所有客戶端連接,然後根據配置執行持久化,最終完成退出

數據類型:

字符串類型

字符串類型是redis中最基本的數據類型,它能存儲任何形式的字符串,包括二進制數據。你可以用它存儲用戶的

郵箱、json化的對象甚至是圖片。一個字符類型鍵允許存儲的最大容量是512M

內部數據結構

在Redis內部,String類型通過 int、SDS(simple dynamic string)作爲結構存儲,int用來存放整型數據,sds存放字 節/字符串和浮點型數據。在C的標準字符串結構下進行了封裝,用來提升基本操作的性能,同時也充分利用已有的 C的標準庫,簡化實現邏輯。我們可以在redis的源碼中【sds.h】中看到sds的結構:

typedef char *sds;

列表類型

列表類型(list)可以存儲一個有序的字符串列表,常用的操作是向列表兩端添加元素或者獲得列表的某一個片段。

list可以從左側或右側進行添加 命令大家可以自行百度嘗試。

內部數據結構:
redis3.2之前,List類型的value對象內部以linkedlist或者ziplist來實現, 當list的元素個數和單個元素的長度比較小的時候,Redis會採用ziplist(壓縮列表)來實現來減少內存佔用。否則就會採用linkedlist(雙向鏈表)結構。 redis3.2之後,採用的一種叫quicklist的數據結構來存儲list,列表的底層都由quicklist實現。

這兩種存儲方式都有優缺點,雙向鏈表在鏈表兩端進行push和pop操作,在插入節點上複雜度比較低,但是內存開 銷比較大; ziplist存儲在一段連續的內存上,所以存儲效率很高,但是插入和刪除都需要頻繁申請和釋放內存;

quicklist仍然是一個雙向鏈表,只是列表的每個節點都是一個ziplist,其實就是linkedlist和ziplist的結合,quicklist 中每個節點ziplist都能夠存儲多個數據元素,在源碼中的文件爲【quicklist.c】,在源碼第一行中有解釋爲:A doubly linked list of ziplists意思爲一個由ziplist組成的雙向鏈表;

列表類型內部使用雙向鏈表實現,所以向列表兩端添加元素的時間複雜度爲O(1), 獲取越接近兩端的元素速度就越 快。這意味着即使是一個有幾千萬個元素的列表,獲取頭部或尾部的10條記錄也是很快的

hash類型

內部數據結構:

map提供兩種結構來存儲,一種是hashtable、另一種是前面講的ziplist,數據量小的時候用ziplist. 在redis中,哈 希表分爲三層,分別是,源碼在【dict.h】

集合類型 (set)

集合類型中,每個元素都是不同的,也就是不能有重複數據,同時集合類型中的數據是無序的。一個集合類型鍵可

以存儲至多232-1個 。集合類型和列表類型的最大的區別是有序性和唯一性 集合類型的常用操作是向集合中加入或刪除元素、判斷某個元素是否存在。由於集合類型在redis內部是使用的值

爲空的散列表(hash table),所以這些操作的時間複雜度都是O(1).

數據結構 Set在的底層數據結構以intset或者hashtable來存儲。當set中只包含整數型的元素時,採用intset來存儲,否則,

採用hashtable存儲,但是對於set來說,該hashtable的value值用於爲NULL。通過key來存儲元素

有序集合類型(sortted-set)

在集合類型的基礎上,有序集合類型爲集合中的每個元素都關聯了一個分數,這使得我們不僅可以完成插入、刪除 和判斷元素是否存在等集合類型支持的操作,還能獲得分數最高(或最低)的前N個元素、獲得指定分數範圍內的元 素等與分數有關的操作。雖然集合中每個元素都是不同的,但是他們的分數卻可以相同

數據結構:

zset類型的數據結構就比較複雜一點,內部是以ziplist或者skiplist+hashtable來實現,這裏面最核心的一個結構就 是skiplist,也就是跳躍表

原理

1 過期時間設置

在Redis中提供了Expire命令設置一個鍵的過期時間,到期以後Redis會自動刪除它。這個在我們實際使用過程中用 得非常多。

EXPIRE命令的使用方法爲

EXPIRE key seconds

其中seconds 參數表示鍵的過期時間,單位爲秒。

EXPIRE 返回值爲1表示設置成功,0表示設置失敗或者鍵不存在

如果向知道一個鍵還有多久時間被刪除,可以使用TTL命令

TTL key

當鍵不存在時,TTL命令會返回-2

而對於沒有給指定鍵設置過期時間的,通過TTL命令會返回-1

如果向取消鍵的過期時間設置(使該鍵恢復成爲永久的),可以使用PERSIST命令,如果該命令執行成功或者成功 清除了過期時間,則返回1 。 否則返回0(鍵不存在或者本身就是永久的)

EXPIRE命令的seconds命令必須是整數,所以最小單位是1秒,如果向要更精確的控制鍵的過期時間可以使用 PEXPIRE命令,當然實際過程中用秒的單位就夠了。 PEXPIRE命令的單位是毫秒。即PEXPIRE key 1000與EXPIRE key 1相等;對應的PTTL以毫秒單位獲取鍵的剩餘有效時間

還有一個針對字符串獨有的過期時間設置方式

setex(String key,int seconds,String value)

過期刪除的原理

Redis 中的主鍵失效是如何實現的,即失效的主鍵是如何刪除的?實際上,Redis 刪除失效主鍵的方法主要有兩 種:

消極方法(passive way) 在主鍵被訪問時如果發現它已經失效,那麼就刪除它

積極方法(active way) 週期性地從設置了失效時間的主鍵中選擇一部分失效的主鍵刪除

對於那些從未被查詢的key,即便它們已經過期,被動方式也無法清除。因此Redis會週期性地隨機測試一些key, 已過期的key將會被刪掉。Redis每秒會進行10次操作,具體的流程:

  1.  隨機測試 20 個帶有timeout信息的key;
  2. 刪除其中已經過期的key;
  3.  如果超過25%的key被刪除,則重複執行步驟1;

這是一個簡單的概率算法(trivial probabilistic algorithm),基於假設我們隨機抽取的key代表了全部的key空

間。

 

Redis發佈訂閱

Redis提供了發佈訂閱功能,可以用於消息的傳輸,Redis提供了一組命令可以讓開發者實現“發佈/訂閱”模式 (publish/subscribe) . 該模式同樣可以實現進程間的消息傳遞,它的實現原理是

發佈/訂閱模式包含兩種角色,分別是發佈者和訂閱者。訂閱者可以訂閱一個或多個頻道,而發佈者可以向指定的 頻道發送消息,所有訂閱此頻道的訂閱者都會收到該消息

發佈者發佈消息的命令是PUBLISH, 用法是 PUBLISH channel message 比如向channel.1發一條消息:hello PUBLISH channel.1 “hello”

這樣就實現了消息的發送,該命令的返回值表示接收到這條消息的訂閱者數量。因爲在執行這條命令的時候還沒有 訂閱者訂閱該頻道,所以返回爲0. 另外值得注意的是消息發送出去不會持久化,如果發送之前沒有訂閱者,那麼後 續再有訂閱者訂閱該頻道,之前的消息就收不到了

訂閱者訂閱消息的命令是

SUBSCRIBE channel [channel ...] 該命令同時可以訂閱多個頻道,比如訂閱channel.1的頻道。 SUBSCRIBE channel.1 執行SUBSCRIBE命令後客戶端會進入訂閱狀態

 

Redis的數據持久化

Redis支持兩種方式的持久化,一種是RDB方式、另一種是AOF(append-only-file)方式。前者會根據指定的規 則“定時”將內存中的數據存儲在硬盤上,而後者在每次執行命令後將命令本身記錄下來。兩種持久化方式可以單獨 使用其中一種,也可以將這兩種方式結合使用,當兩種持久化存儲都打開時默認會優先加載AOF的數據。

RDB方式(快照方式)

當符合一定條件時,Redis會單獨創建(fork)一個子進程來進行持久化,會先將數據寫入到一個臨時文件中,等 到持久化過程都結束了,再用這個臨時文件替換上次持久化好的文件。整個過程中,主進程是不進行任何IO操作 的,這就確保了極高的性能。如果需要進行大規模數據的恢復,且對於數據恢復的完整性不是非常敏感,那RDB方 式要比AOF方式更加的高效。RDB的缺點是最後一次持久化後的數據可能丟失

rdb如果使用save方式會導致備份時 無法被正常使用,如果使用bgsave時需要服務器內存充足,至少是redis設置內存的二倍。

bgsave方式備份會後臺自動備份,但是這個過程中如果數據量大會導致備份過程中 產生的數據變化會丟失。

AOF方式(日誌記錄方式)

當使用Redis存儲非臨時數據時,一般需要打開AOF持久化來降低進程終止導致的數據丟失。AOF可以將Redis執行 的每一條寫命令追加到硬盤文件中,這一過程會降低Redis的性能,但大部分情況下這個影響是能夠接受的,另外 使用較快的硬盤可以提高AOF的性能

開啓AOF

默認情況下Redis沒有開啓AOF(append only file)方式的持久化,可以通過appendonly參數啓用,在redis.conf 中找到 appendonly yes

開啓AOF持久化後每執行一條會更改Redis中的數據的命令後,Redis就會將該命令寫入硬盤中的AOF文件。AOF文 件的保存位置和RDB文件的位置相同,都是通過dir參數設置的,默認的文件名是apendonly.aof. 可以在redis.conf 中的屬性 appendfilename appendonlyh.aof修改

AOF的重寫原理

Redis 可以在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。

重寫的流程是這樣,主進程會fork一個子進程出來進行AOF重寫,這個重寫過程並不是基於原有的aof文件來做 的,而是有點類似於快照的方式,全量遍歷內存中的數據,然後逐個序列到aof文件中。在fork子進程這個過程 中,服務端仍然可以對外提供服務,那這個時候重寫的aof文件的數據和redis內存數據不一致了怎麼辦?不用擔 心,這個過程中,主進程的數據更新操作,會緩存到aof_rewrite_buf中,也就是單獨開闢一塊緩存來存儲重寫期間 收到的命令,當子進程重寫完以後再把緩存中的數據追加到新的aof文件。

當所有的數據全部追加到新的aof文件中後,把新的aof文件重命名爲,此後所有的操作都會被寫入新的aof文件。

如果在rewrite過程中出現故障,不會影響原來aof文件的正常工作,只有當rewrite完成後纔會切換文件。因此這個 rewrite過程是比較可靠的

Redis內存回收策略

Redis中提供了多種內存回收策略,當內存容量不足時,爲了保證程序的運行,這時就不得不淘汰內存中的一些對

象,釋放這些對象佔用的空間,那麼選擇淘汰哪些對象呢? 其中,默認的策略爲noeviction策略,當內存使用達到閾值的時候,所有引起申請內存的命令會報錯

  • allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
  • allkeys-random:隨機移除某個key。
  • volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰。
  • volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰。
  • volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰

實際上Redis實現的LRU並不是可靠的LRU,也就是名義上我們使用LRU算法淘汰內存數據,但是實際上被淘汰的鍵 並不一定是真正的最少使用的數據,這裏涉及到一個權衡的問題,如果需要在所有的數據中搜索最符合條件的數 據,那麼一定會增加系統的開銷,Redis是單線程的,所以耗時的操作會謹慎一些。爲了在一定成本內實現相對的 LRU,早期的Redis版本是基於採樣的LRU,也就是放棄了從所有數據中搜索解改爲採樣空間搜索最優解。Redis3.0 版本之後,Redis作者對於基於採樣的LRU進行了一些優化,目的是在一定的成本內讓結果更靠近真實的LRU。

Redis是單進程單線程,性能爲什麼這麼快

Redis採用了一種非常簡單的做法,單線程來處理來自所有客戶端的併發請求,Redis把任務封閉在一個線程中從而

避免了線程安全問題;redis爲什麼是單線程?

官方的解釋是,CPU並不是Redis的瓶頸所在,Redis的瓶頸主要在機器的內存和網絡的帶寬。那麼Redis能不能處 理高併發請求呢?當然是可以的,至於怎麼實現的,我們來具體瞭解一下。 【注意併發不等於並行,併發性I/O 流,意味着能夠讓一個計算單元來處理來自多個客戶端的流請求。並行性,意味着服務器能夠同時執行幾個事情, 具有多個計算單元】

多路複用

Redis 是跑在單線程中的,所有的操作都是按照順序線性執行的,但是由於讀寫操作等待用戶輸入或輸出都是阻塞 的,所以 I/O 操作在一般情況下往往不能直接返回,這會導致某一文件的 I/O 阻塞導致整個進程無法對其它客戶提 供服務,而 I/O 多路複用就是爲了解決這個問題而出現的。

瞭解多路複用之前,先簡單瞭解下幾種I/O模型

  1. 同步阻塞IO(Blocking IO):即傳統的IO模型。
  2. 同步非阻塞IO(Non-blocking IO):默認創建的socket都是阻塞的,非阻塞IO要求socket被設置爲NONBLOCK。
  3. IO多路複用(IO Multiplexing):即經典的Reactor設計模式,也稱爲異步阻塞IO,Java中的Selector和Linux中的epoll都是這種模型。
  4. 異步IO(Asynchronous IO):即經典的Proactor設計模式,也稱爲異步非阻塞IO。

同步和異步、阻塞和非阻塞,到底是什麼意思,感覺原理都差不多,我來簡單解釋一下 同步和異步,指的是用戶線程和內核的交互方式 阻塞和非阻塞,指用戶線程調用內核IO操作的方式是阻塞還是非阻塞

就像在Java中使用多線程做異步處理的概念,通過多線程去執行一個流程,主線程可以不用等待。而阻塞和非阻塞 我們可以理解爲假如在同步流程或者異步流程中做IO操作,如果緩衝區數據還沒準備好,IO的這個過程會阻塞,這 個在之前講TCP協議的時候有講過.

so就先說到這裏~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章