redis 小結(jié)

1 redis概述

1.1 什么是redis

??Redis的全稱是REmote Dictionary Server。redis 是一種基于對(duì)(key-value)的NoSQL數(shù)據(jù)庫(kù)。redis所有數(shù)據(jù)都存放在內(nèi)存中,所以Redis的讀寫速度非常驚人,還可以將內(nèi)存中的數(shù)據(jù)以快走和日志的形式保存在硬盤上,這樣斷電等機(jī)器故障不會(huì)使內(nèi)存中的數(shù)據(jù)“丟失”。redis還提供鍵過(guò)期、發(fā)布訂閱、事務(wù)、流水線、Lua腳本等功能。

1.2 redis的作者是誰(shuí)

?? redis的作者Salvatore Sanfilippo 在開(kāi)發(fā)一個(gè)叫LLOOGG的網(wǎng)站時(shí)需要實(shí)現(xiàn)一個(gè)高性能的隊(duì)列功能,一開(kāi)始是使用MySql 來(lái)現(xiàn)實(shí),后來(lái)發(fā)現(xiàn)無(wú)論怎么優(yōu)化SQL語(yǔ)句都不能使網(wǎng)站的性能提高上去,于是他決定自己做一個(gè)專屬于LLOOGG的數(shù)據(jù)庫(kù),這個(gè)就是Redis的前身。后來(lái),Salvatore Sanfilippo將Redis1.0的源碼開(kāi)放到GitHub上,可能連他自己都沒(méi)想到,Redis后來(lái)如此受歡迎。

1.3 有誰(shuí)在用redis

??從Redis的官方公司統(tǒng)計(jì)來(lái)看,有很多重量級(jí)的公司都在使用Redis,如國(guó)外的Twitter、Instagram、Stack Overflow、GitHub等,國(guó)內(nèi)就更多了,如果單單從體量來(lái)統(tǒng)計(jì),新浪微博可以說(shuō)是全球最大的Redis使用者,除了新浪微博,還有像阿里巴巴、騰訊、百度、搜狐、優(yōu)酷土豆、美團(tuán)、小米、唯品會(huì)等公司都是Redis的使用者。

2 redis 和 mySQL的區(qū)別

2.1 什么是mySQL 數(shù)據(jù)庫(kù)

  1. mySQL是一個(gè)關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),由瑞典MySQL AB公司開(kāi)發(fā),目前屬于Oracle公司。
  2. mySQL是一種關(guān)聯(lián)數(shù)據(jù)庫(kù)管理系統(tǒng),關(guān)聯(lián)數(shù)據(jù)庫(kù)將數(shù)據(jù)保存在不同的表中,而不是將所有數(shù)據(jù)放在一個(gè)大倉(cāng)庫(kù)內(nèi),這樣就增加了速度并提高了靈活性。
  3. mySQL是開(kāi)源的,Mysql支持大型的數(shù)據(jù)庫(kù)。可以處理?yè)碛猩锨f(wàn)條記錄的大型數(shù)據(jù)庫(kù)。MySQL使用標(biāo)準(zhǔn)的SQL數(shù)據(jù)語(yǔ)言形式。
  4. mySQL可以允許于多個(gè)系統(tǒng)上,并且支持多種語(yǔ)言。這些編程語(yǔ)言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等。

2.2 兩者的區(qū)別

??redis是內(nèi)存數(shù)據(jù)庫(kù),數(shù)據(jù)保存在內(nèi)存中,讀寫速度快。
??mySQL是關(guān)系型數(shù)據(jù)庫(kù),功能強(qiáng)大,數(shù)據(jù)訪問(wèn)也就慢。

3 redis特性

3.1 速度快

??正常情況下,Redis執(zhí)行命令的速度非常快,官方給出的數(shù)字是讀寫性能可以達(dá)到10萬(wàn)/秒。原因可以大致歸納為以下四點(diǎn):
??1. Redis的所有數(shù)據(jù)都是存放在內(nèi)存中的,所以把數(shù)據(jù)放在內(nèi)存中是Redis速度快的最主要原因。
??2. Redis是用C語(yǔ)言實(shí)現(xiàn)的,一般來(lái)說(shuō)C語(yǔ)言實(shí)現(xiàn)的程序“距離”操作系統(tǒng)更近,執(zhí)行速度相對(duì)會(huì)更快。
??3. Redis使用了單線程架構(gòu),預(yù)防了多線程可能產(chǎn)生的競(jìng)爭(zhēng)問(wèn)題。

3.2 基于鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu)服務(wù)器

??Redis的全稱是REmote Dictionary Server,它主要提供了5種數(shù)據(jù)結(jié)構(gòu):字符串、哈希、列表、集合、有序集合,同時(shí)在字符串的基礎(chǔ)之上演變出了位字符串、哈希、列表、集合、有序集合,同時(shí)在字符串的基礎(chǔ)之上演變出了位圖(Bitmaps)和HyperLogLog兩種神奇的“數(shù)據(jù)結(jié)構(gòu)”,并且隨著LBS(Location Based Service,基于位置服務(wù))的不斷發(fā)展,Redis3.2版本中加入有關(guān)GEO(地理信息定位)的功能。

3.3 豐富的功能

??除了5種數(shù)據(jù)結(jié)構(gòu),Redis還提供了許多額外的功能:
????·提供了鍵過(guò)期功能,可以用來(lái)實(shí)現(xiàn)緩存。
????·提供了發(fā)布訂閱功能,可以用來(lái)實(shí)現(xiàn)消息系統(tǒng)。
????·支持Lua腳本功能,可以利用Lua創(chuàng)造出新的Redis命令。
????·提供了簡(jiǎn)單的事務(wù)功能,能在一定程度上保證事務(wù)特性。
????·提供了流水線(Pipeline)功能,這樣客戶端能將一批命令一次性傳到Redis,減少了網(wǎng)絡(luò)的開(kāi)銷。

3.4 簡(jiǎn)單穩(wěn)定

??Redis的簡(jiǎn)單主要表現(xiàn)在三個(gè)方面。首先,Redis的源碼很少,早期版本的代碼只有2萬(wàn)行左右,3.0版本以后由于添加了集群特性,代碼增至5萬(wàn)行左右。其次,Redis使用單線程模型,這樣不僅使得Redis服務(wù)端處理模型變得簡(jiǎn)單,而且也使得客戶端開(kāi)發(fā)變得簡(jiǎn)單。最后,Redis不需要依賴于操作系統(tǒng)中的類庫(kù),Redis自己實(shí)現(xiàn)了事件處理的相關(guān)功能。
??Redis雖然很簡(jiǎn)單,但是不代表它不穩(wěn)定。以筆者維護(hù)的上千個(gè)Redis為例,沒(méi)有出現(xiàn)過(guò)因?yàn)镽edis自身bug而宕掉的情況。

3.5 客戶端語(yǔ)言多

??Redis提供了簡(jiǎn)單的TCP通信協(xié)議,很多編程語(yǔ)言可以很方便地接入到Redis,并且由于Redis受到社區(qū)和各大公司的廣泛認(rèn)可,所以支持Redis的客戶端語(yǔ)言也非常多,幾乎涵蓋了主流的編程語(yǔ)言,例如Java、PHP、Python、C、C++、Nodejs等。

3.6 持久化

??通常看,將數(shù)據(jù)放在內(nèi)存中是不安全的,一旦發(fā)生斷電或者機(jī)器故障,重要的數(shù)據(jù)可能就會(huì)丟失,因此Redis提供了兩種持久化方式:RDB和AOF,即可以用兩種策略將內(nèi)存的數(shù)據(jù)保存到硬盤中(,這樣就保證了數(shù)據(jù)的可持久性。

3.7 主從復(fù)制

??Redis提供了復(fù)制功能,實(shí)現(xiàn)了多個(gè)相同數(shù)據(jù)的Redis副本,復(fù)制功能是分布式Redis的基礎(chǔ)。

3.8 高可用和分布式

??Redis從2.8版本正式提供了高可用實(shí)現(xiàn)Redis Sentinel,它能夠保證Redis節(jié)點(diǎn)的故障發(fā)現(xiàn)和故障自動(dòng)轉(zhuǎn)移。Redis從3.0版本正式提供了分布式實(shí)現(xiàn)Redis Cluster,它是Redis真正的分布式實(shí)現(xiàn),提供了高可用、讀寫和容量的擴(kuò)展性。

4 安裝Redis

??安裝參考 http://m.itdecent.cn/p/150307f7e43f

4.1 安裝依賴

yum install gcc gcc-c++ make
yum install wget

4.2 下載redis軟件安裝包

wget http://download.redis.io/redis-stable.tar.gz

4.3 解壓安裝

tar xvzf redis-stable.tar.gz
cd redis-stable
mv redis-stable /usr/local/redis
cd /usr/local/redis
make
make install

編譯過(guò)程中可能會(huì)有如下報(bào)錯(cuò):

在包含自 adlist.c:34 的文件中:
zmalloc.h:50:31: 錯(cuò)誤:jemalloc/jemalloc.h:沒(méi)有那個(gè)文件或目錄
zmalloc.h:55:2: 錯(cuò)誤:#error "Newer version of jemalloc required"
make[1]: *** [adlist.o] 錯(cuò)誤 1
make[1]: Leaving directory `/usr/local/redis/src'

需要執(zhí)行如下命令:

make MALLOC=libc && make install

2.4 修改配置文件

復(fù)制配置文件

cp redis.conf /etc/

修改配置文件

vim /etc/redis.conf

配置修改如下:

    pidfile /var/run/redis.pid  
    bind 127.0.0.1 本機(jī)ip #bind ip,綁定本機(jī)ip即可
    daemonize yes       #后臺(tái)運(yùn)行
    loglevel notice 
    logfile /data/logs/redis/redis.log  
    #設(shè)置log目錄,需手動(dòng)創(chuàng)建/data/logs/redis/目錄,mkdir /data/logs/redis/
    dir /data/redis/
    #設(shè)置文件目錄,需手動(dòng)創(chuàng)建mkdir /data/redis/
    databases 16 
    save 900 1 
    save 300 10 
    save 60 10000 
    rdbcompression yes 
    dbfilename dump.rdb #設(shè)置db文件
    appendonly no

創(chuàng)建數(shù)據(jù)目錄 并更改權(quán)限

mkdir /data/redis/
chmod -R 755 /data

4.5 驗(yàn)證是否安裝成功

使用 /etc/redis.conf 配置文件啟動(dòng)redis

redis-server /etc/redis.conf 

查看端口:

netstat -ntlp |grep 6379
image.png

如果顯示端口正在使用則說(shuō)明安裝成功,沒(méi)有就沒(méi)安裝成功。
鏈接redis 測(cè)試:

/usr/local/redis/src/redis-cli

存值

set keyTest "hello"

取值

get keyTest
image.png

關(guān)閉redis:

/usr/local/redis/src/redis-cli shutdown

5 redis API

5.1 常用命令

  1. 查看所有鍵
    keys pattern
    注:keys 后面接查找表示式,在庫(kù)中有大量key時(shí),使用keys * 需要謹(jǐn)慎,可能會(huì)引發(fā)線程阻塞。
  2. 查看所有鍵
    dbsize
  3. 查看所有鍵
    exists key
  4. 刪除鍵
    del key [key ...]
    可同時(shí)刪除多個(gè)key,返回成功刪除鍵的個(gè)數(shù)
  5. 鍵過(guò)期
    expire key seconds
    設(shè)置鍵的過(guò)期時(shí)間,當(dāng)鍵過(guò)期后,用 get key 取鍵值,會(huì)返回nil。
  6. 鍵過(guò)期查詢命令
    ttl key
    返回值為大于等于0的整數(shù):鍵過(guò)期的剩余時(shí)間。
    返回值為-1: 鍵沒(méi)有過(guò)期時(shí)間。
    返回值為-2: 鍵以過(guò)期或不存在。
  7. 鍵的結(jié)構(gòu)類型
    type key
  8. 設(shè)置鍵值
    set key value [ex seconds] [px milliseconds] [nx|xx]
    [ex seconds] 為秒級(jí)過(guò)期時(shí)間
    [px milliseconds] 為毫秒級(jí)過(guò)期時(shí)間
    [nx] 當(dāng)鍵不存在時(shí)才設(shè)置,用于添加
    [xx] 當(dāng)鍵存在時(shí)才設(shè)置,用于更新
  9. 獲取鍵值
    get key
  10. incr 自增計(jì)數(shù)
    incr key
    注:僅對(duì)整數(shù)有效,否則返回錯(cuò)誤
  11. decr 自減計(jì)數(shù)
    decr key
    注:僅對(duì)整數(shù)有效,否則返回錯(cuò)誤
  12. 鍵重命名
    rename oldkey newkey
  13. 遷移鍵
    move key db
    把鍵移動(dòng)db庫(kù)

6 主從復(fù)制

6.1 建立主從方式

建立主從復(fù)制方式有三種:

  1. 在從redis配置文件中加入 slaveof {masterHost} {masterHost},redis啟動(dòng)生效。
  2. 啟動(dòng)從redis后,在命令中執(zhí)行 --slaveof {masterHost} {masterHost}。
  3. 直接使用 slaveof {masterHost} {masterHost}。

查看主從復(fù)制狀態(tài):info replication

6.2 斷開(kāi)復(fù)制

在從節(jié)點(diǎn)上執(zhí)行命令 :slaveof no one
切換主方式:slaveof {newMaster}
注:切換主節(jié)點(diǎn)后從節(jié)點(diǎn)上的數(shù)據(jù)會(huì)被清空,線上慎用 slaveof命令。

6.3 復(fù)制原理

復(fù)制流程大致分為一下步驟:

  1. 保存master信息
    slave 上執(zhí)行slaveof 命令后,便把master 的信息保存起來(lái)。 master 的 master_link_status 變?yōu)橄戮€狀態(tài)。
  2. slave 內(nèi)部通過(guò)每秒運(yùn)行的定時(shí)任務(wù)維護(hù)復(fù)制相關(guān)邏輯任務(wù),發(fā)現(xiàn)新master 節(jié)點(diǎn)后,會(huì)嘗試與該節(jié)點(diǎn)建立網(wǎng)絡(luò)鏈接。
  3. 發(fā)送ping命令
    發(fā)送ping命令,當(dāng)master 回復(fù) pong 時(shí),說(shuō)明建立成功。
  4. 權(quán)限驗(yàn)證
  5. 數(shù)據(jù)集同步
    在和master 正常通信后,首次建立鏈接,master會(huì)把持有的所有數(shù)據(jù)發(fā)給slave,這次為全量同步。
  6. 命令持續(xù)復(fù)制
    在首次與master 同步完成后,后續(xù)master會(huì)持續(xù)把寫命令發(fā)送給slave,保證數(shù)據(jù)一致性,為部分同步。

6.4 心跳

在主從建立復(fù)制后,他們之間維護(hù)著長(zhǎng)鏈接并彼此發(fā)送心跳命令。

  1. 主從彼此都有心跳檢測(cè)機(jī)制,可以通過(guò)client list命令查看信息,主節(jié)點(diǎn)鏈接狀態(tài)為 flags=N,從節(jié)點(diǎn)鏈接狀態(tài)為flags=S。
  2. 主節(jié)點(diǎn)默認(rèn)每 10s 對(duì)從節(jié)點(diǎn)發(fā)送ping命令??赏ㄟ^(guò) repl-ping-slave-period 控制發(fā)送頻率。
  3. 從節(jié)點(diǎn)每 1s 給主節(jié)點(diǎn)發(fā)送 replconf ack {offset} 命令,上報(bào)自身當(dāng)前的復(fù)制偏移量。

7 內(nèi)存管理與優(yōu)化

7.1 內(nèi)存管理

7.1.1 設(shè)置內(nèi)存上限

maxmemory 參數(shù)可限制最大可使用內(nèi)存,主要目的:

  1. 用于緩存場(chǎng)景,當(dāng)超出內(nèi)存上限時(shí),使用LRU等刪除策略釋放空間。
  2. 防止所用內(nèi)存超過(guò)服務(wù)器內(nèi)存。

注:由于內(nèi)存碎片率的存在,實(shí)際消耗的內(nèi)存可能會(huì)比maxmemor設(shè)置的大。

7.1.2 動(dòng)態(tài)調(diào)整內(nèi)存上限

config set maxmemory 1GB
該命令可動(dòng)態(tài)調(diào)整內(nèi)存上限

注:為了防止極端情況下導(dǎo)致系統(tǒng)內(nèi)存被耗盡,建議每個(gè)redis進(jìn)程都設(shè)置maxmemory參數(shù),默認(rèn)無(wú)限大。

7.1.3 內(nèi)存回收策略

內(nèi)存回收策略體現(xiàn)在以下兩個(gè)方面:
. 刪除到期鍵。
. 內(nèi)存使用達(dá)到maxmemory上限觸發(fā)控制策略。

  1. 刪除過(guò)期鍵
    1)惰性刪除
    ??redis 讀取帶過(guò)期屬性的鍵時(shí),當(dāng)該鍵已過(guò)期,會(huì)執(zhí)行刪除命令并返回空。
    2)定時(shí)任務(wù)刪除
    ?? redis 內(nèi)部維護(hù)一個(gè)定時(shí)任務(wù),默認(rèn)每秒運(yùn)行10次,可通過(guò) hz 參數(shù)配置。每次任務(wù)隨機(jī)檢查20個(gè)鍵,當(dāng)發(fā)現(xiàn)過(guò)期鍵時(shí)刪除對(duì)應(yīng)鍵;
    ?? 如果超過(guò)25%的鍵過(guò)期,循環(huán)執(zhí)行回收邏輯直到不足25%或超時(shí)為止,默認(rèn)超時(shí)時(shí)間為25ms;該次為慢模式。
    ??如果回收邏輯超時(shí),則在觸發(fā)內(nèi)部事件之前再次以快模式回收過(guò)期鍵。
    ??快慢兩種模式刪除邏輯相同,只是執(zhí)行超時(shí)時(shí)間不同。慢模式超時(shí)為25ms,快模式超時(shí)為 1ms 且每 2 s內(nèi)只能運(yùn)行一次。
  2. 內(nèi)存溢出控制策略
    當(dāng)redis內(nèi)存使用達(dá)到maxmemory上限觸發(fā)控制策略。
    redis 支持 6種控制策略,由maxmemory-policy參數(shù)控制。
    1)noeviction:默認(rèn)策略。不刪除任何數(shù)據(jù),redis變?yōu)橹蛔x模式,寫入報(bào)錯(cuò)。
    2)volatile-lru:根據(jù)LRU算法刪除設(shè)置超時(shí)屬性的鍵,直到空間足夠?yàn)橹?。沒(méi)有可刪除鍵時(shí),回退到noeviction策略。
    3)allkeys-lru:根據(jù)LRU算法刪除鍵,不管鍵有沒(méi)有設(shè)置超時(shí)屬性,直到空間足夠?yàn)橹埂?br> 4)allkeys-random:隨機(jī)刪除所有鍵,直到空間足夠?yàn)橹埂?br> 5)volatile-random:隨機(jī)刪除過(guò)期鍵,直到空間足夠?yàn)橹埂?br> 6)volatile-ttl:根據(jù)值的ttl屬性,刪除將要過(guò)期的數(shù)據(jù)。沒(méi)有可刪除鍵,回退到noeviction策略。

內(nèi)存溢出控制策略 可采用 config set maxmemory-policy {policy} 動(dòng)態(tài)設(shè)置。

7.2 內(nèi)存優(yōu)化

redis 內(nèi)存優(yōu)化的常用的方法有:

  1. redisObject對(duì)象
  2. 縮減鍵值對(duì)象
  3. 共享對(duì)象池
  4. 字符串優(yōu)化
  5. 編碼優(yōu)化
  6. 控制鍵的數(shù)量

8 redis cluster 集群

8.1 cluster 數(shù)據(jù)分布

8.1.1 分布式數(shù)據(jù)分區(qū)規(guī)則

??分布式數(shù)據(jù)庫(kù)會(huì)把整個(gè)數(shù)據(jù)集按照分區(qū)規(guī)則將數(shù)據(jù)集劃分到多個(gè)節(jié)點(diǎn)上,每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分?jǐn)?shù)據(jù)集。
??常用的分區(qū)規(guī)則由哈希分區(qū)和順序分區(qū)。redis cluster 采用的是哈希分區(qū)規(guī)則。
常見(jiàn)的哈希分區(qū)規(guī)則有以下幾種:

  1. 節(jié)點(diǎn)取余分區(qū)
    ??將key(鍵數(shù)量)% N(節(jié)點(diǎn)數(shù))計(jì)算出哈希值,決定該數(shù)據(jù)映射到哪個(gè)節(jié)點(diǎn)上。
    ??優(yōu)點(diǎn):簡(jiǎn)單。
    ??缺點(diǎn):當(dāng)節(jié)點(diǎn)數(shù) N 發(fā)生變化時(shí),映射關(guān)系會(huì)重新計(jì)算,可能會(huì)導(dǎo)致數(shù)據(jù)遷移。
    為了避免數(shù)據(jù)遷移,通常節(jié)點(diǎn)數(shù)擴(kuò)張或縮減會(huì)成倍數(shù)關(guān)系。
  2. 一致性哈希分區(qū)
    ??思路是為系統(tǒng)中每個(gè)節(jié)點(diǎn)分配一個(gè)token,范圍一般在0~2^{32},這寫token構(gòu)成一個(gè)哈希環(huán)。數(shù)據(jù)讀寫時(shí),先根據(jù)key計(jì)算hash值,然后順時(shí)針找到第一個(gè)大于等于該哈希值的token節(jié)點(diǎn)。
  3. 虛擬槽分區(qū)
    ??虛擬槽分區(qū)使用哈??臻g,用哈希函數(shù)把所有數(shù)據(jù)映射到一個(gè)固定的范圍整數(shù)集合中,整數(shù)定義為槽(slot)。這個(gè)范圍遠(yuǎn)大于節(jié)點(diǎn)數(shù)。槽是集群內(nèi)數(shù)據(jù)管理和遷移的基本單位。
    ??redis cluster 槽的范圍 0~16383。當(dāng)有五個(gè)節(jié)點(diǎn)時(shí),每個(gè)節(jié)點(diǎn)大約負(fù)責(zé)管理3276個(gè)槽。

8.1.2 redis cluster 數(shù)據(jù)分區(qū)

??redis cluster采用虛擬槽分區(qū)規(guī)則,所有鍵根據(jù)哈希函數(shù)映射到0~16383個(gè)槽上。計(jì)算公式:slot=CRC16(key)&16383。

redis 虛擬槽分區(qū)特點(diǎn):

  1. 數(shù)據(jù)和節(jié)點(diǎn)解藕,簡(jiǎn)化節(jié)點(diǎn)擴(kuò)容和縮減難度。
  2. 不需要客戶端或代理服務(wù)維護(hù)槽元數(shù)據(jù),自身就能完成。
  3. 支持節(jié)點(diǎn)、槽、鍵之間的映射查詢。

8.2 redis cluster 功能限制

相對(duì)單例,cluster上存在以下功能限制:

  1. 只支持相同slot值的key 執(zhí)行批量操作,如mset、mget。
  2. 只支持多key 在同一個(gè)節(jié)點(diǎn)上的事務(wù)操作。
  3. 不能將大鍵值對(duì)象如hash、list映射到不同節(jié)點(diǎn)上。
  4. 不支持多數(shù)據(jù)庫(kù)空間,只有一個(gè)db0 數(shù)據(jù)庫(kù)。單例有db0~db15 共16個(gè)數(shù)據(jù)空間。
  5. 復(fù)制結(jié)構(gòu)只支持一層。

8.3 redis cluster 創(chuàng)建

8.3.1 服務(wù)器配置說(shuō)明

以三主三從為例:
    只有三臺(tái)服務(wù)器,所以每臺(tái)服務(wù)上創(chuàng)建兩個(gè)redis 實(shí)例,用作一主一從。

注:需每臺(tái)服務(wù)器上先安裝好redis服務(wù),可參考步驟4 安裝Redis

8.3.2 安裝依賴

安裝ruby
    yum install ruby
安裝 gem
    yum install rubygems
安裝 gem  
    gem install redis

注:redis 安裝需要ruby 版本大于 2.2.2

升級(jí)ruby方法可參考 http://m.itdecent.cn/p/a1a4d59490d7

8.3.3 創(chuàng)建實(shí)例

注:以一臺(tái)服務(wù)器為例,主從實(shí)例的端口分別為 6379、6479
復(fù)制配置文件

cp /usr/local/redis/redis.conf /etc/redis_6379.conf
cp /usr/local/redis/redis.conf /etc/redis_6479.conf

注:每臺(tái)服務(wù)器應(yīng)先裝好redis服務(wù),

修改配置文件,修改項(xiàng)如下:

port 6379       #對(duì)應(yīng)實(shí)例的端口
pidfile /var/run/redis.pid  
bind 127.0.0.1 本機(jī)ip #bind ip,綁定本機(jī)ip即可
daemonize yes

loglevel notice 

logfile /data/logs/redis/redis_6379.log 
#設(shè)置log目錄,需手動(dòng)創(chuàng)建/data/logs/redis目錄,mkdir /data/logs/redis

dir /data/redis/redis_6379/
#設(shè)置文件目錄,需手動(dòng)創(chuàng)建mkdir /data/redis/redis_6379/

databases 16 
save 900 1 
save 300 10 
save 60 10000 
rdbcompression yes 
dbfilename dump_6379.rdb
#設(shè)置db文件
appendonly no

8.3.4 啟動(dòng)實(shí)例

使用對(duì)應(yīng)的配置文件啟動(dòng)

redis-server /etc/redis_6379.conf
redis-server /etc/redis_6479.conf

查看線程,端口驗(yàn)證是否啟動(dòng)正好

ps -ef|grep redis
netstat -tnlp | grep redis

8.3.5 創(chuàng)建集群

/usr/local/redis/src/redis-trib.rb create --replicas 1 主機(jī)1:6379 主機(jī)2:6379 主機(jī)3:6379 主機(jī)1:6479 主機(jī)2:6479 主機(jī)3:6479

注:只需在一臺(tái)服務(wù)器上創(chuàng)建集群。redis-trib.rb 會(huì)盡可能的將主從節(jié)點(diǎn)分配在不同的機(jī)器上,所以順序會(huì)重新分配。

8.3.6 驗(yàn)證集群

鏈接集群,加 –c 參數(shù)意味鏈接集群

redis-cli -h 172.31.78.3 -c -p 6379

8.3.7 集群完整性檢查

redis-trib.rb check 172.31.78.3:6379

該命令可檢查集群的完整性,只需檢查任意一個(gè)節(jié)點(diǎn)。返回結(jié)果會(huì)列出集群節(jié)點(diǎn)信息

8.4 節(jié)點(diǎn)通信

8.4.1 通信流程

  1. 集群每個(gè)節(jié)點(diǎn)都會(huì)開(kāi)辟一個(gè)TCP通道,用于節(jié)點(diǎn)間通信。通信端口為基礎(chǔ)端口上加10000.
  2. 每個(gè)節(jié)點(diǎn)在固定周期內(nèi)通過(guò)特定規(guī)則選擇幾個(gè)節(jié)點(diǎn)發(fā)送ping消息。
  3. 收到ping消息的節(jié)點(diǎn)回復(fù)pong消息作為響應(yīng)。

注:因?yàn)橥ㄐ哦丝?= 基礎(chǔ)端口 + 10000。所以如果基礎(chǔ)端口為 6379,這防火墻需要放開(kāi) 6379 和 16379 這兩個(gè)端口才可讓集群正常通信。

8.4.2 節(jié)點(diǎn)選擇

??redis cluster 節(jié)點(diǎn)內(nèi)通信采用固定頻率,每秒執(zhí)行10次定時(shí)任務(wù)。ping/pong消息攜帶當(dāng)前節(jié)點(diǎn)和其他節(jié)點(diǎn)的狀態(tài)數(shù)據(jù),頻繁通信勢(shì)必會(huì)加重計(jì)算和帶寬的負(fù)擔(dān)。所以每次定時(shí)任務(wù)選擇通信的節(jié)點(diǎn)非常重要。

  1. 選擇發(fā)送信息的節(jié)點(diǎn)數(shù)量
    ??集群每秒會(huì)隨機(jī)選取5個(gè)節(jié)點(diǎn),找出最久沒(méi)通信的節(jié)點(diǎn)發(fā)送ping消息。每100毫秒會(huì)掃描本地節(jié)點(diǎn)列表,發(fā)現(xiàn)節(jié)點(diǎn)最近接受pong消息的消息大于cluster_node_timeout/2 時(shí)會(huì)立即發(fā)送ping消息。所以每個(gè)節(jié)點(diǎn)每秒發(fā)送的ping消息量 = 1 + 10 * num (node.pong_receives>cluster_node_timeout/2);
  2. 消息數(shù)據(jù)量
    ??每個(gè)ping消息的數(shù)據(jù)體現(xiàn)在消息頭和消息體中,消息頭的大小為 2KB,消息體需看偽代碼。

8.5 伸縮

新 node 加入集群方法。
可使用redis-trib.rb 工具直接添加。

// 添加新節(jié)點(diǎn)
redis-trib.rb add-node newhost1:port newhost2:port 
//添加新節(jié)點(diǎn)并設(shè)置為master-id的從節(jié)點(diǎn)
redis-trib.rb add-node newhost:port --slave --master-id  

8.6 請(qǐng)求重定向

??redis 接受到key命令時(shí)先計(jì)算key對(duì)應(yīng)的槽,再找出槽對(duì)應(yīng)的節(jié)點(diǎn)。如果節(jié)點(diǎn)是本身就處理命令,否則回復(fù)MOVED重定向錯(cuò)誤,通知客戶端請(qǐng)求正確節(jié)點(diǎn)。


image.png

8.6 故障轉(zhuǎn)移

8.6.1 故障發(fā)現(xiàn)

??redis 通過(guò)ping/pong消息實(shí)現(xiàn)節(jié)點(diǎn)通信,同時(shí)攜帶節(jié)點(diǎn)的消息,如主從狀態(tài)、節(jié)點(diǎn)故障、槽信息等。

??每個(gè)節(jié)點(diǎn)定期會(huì)給其他節(jié)點(diǎn)發(fā)送ping消息,如果在 cluster-node-timeout時(shí)間內(nèi)收不到其他節(jié)點(diǎn)回復(fù)的pong消息,則會(huì)將該節(jié)點(diǎn)標(biāo)記為主觀下線狀態(tài)(pfail)。
??當(dāng)某個(gè)節(jié)點(diǎn)判斷另一個(gè)節(jié)點(diǎn)為主觀下線后,會(huì)將相應(yīng)節(jié)點(diǎn)狀態(tài)信息在集群內(nèi)傳播。當(dāng)超過(guò)半數(shù)的節(jié)點(diǎn)的任務(wù)該節(jié)點(diǎn)下線時(shí),會(huì)將該節(jié)點(diǎn)標(biāo)記為客觀下線狀態(tài)。這時(shí)該節(jié)點(diǎn)就會(huì)下線。

注:如果在cluster-node-time*2 時(shí)間內(nèi)無(wú)法收集到一半以上節(jié)點(diǎn)的下線報(bào)告,那么之前的下線報(bào)告將會(huì)過(guò)期。也就是說(shuō)主觀下線報(bào)告永遠(yuǎn)趕不上客觀下線報(bào)告,那么故障節(jié)點(diǎn)就不會(huì)被標(biāo)記客觀下線而導(dǎo)致故障轉(zhuǎn)移失敗。所以cluster-node-time不建議設(shè)置過(guò)小。

8.6.2 故障恢復(fù)

?? 當(dāng)故障節(jié)點(diǎn)變?yōu)榭陀^下線后,如果該節(jié)點(diǎn)為主節(jié)點(diǎn)則需要在從節(jié)點(diǎn)中選出一個(gè)節(jié)點(diǎn)替補(bǔ)上。
恢復(fù)流程如下:

  1. 資格檢查
    如果從節(jié)點(diǎn)與主節(jié)點(diǎn)斷線的時(shí)間超過(guò) cluster-node-time*cluster-slave-validity-factor,則該節(jié)點(diǎn)沒(méi)有資格。cluster-slave-validity-factor 為從節(jié)點(diǎn)有效因子,默認(rèn)為10。
  2. 準(zhǔn)備選舉時(shí)間
    當(dāng)從節(jié)點(diǎn)擁有資格后,更新觸發(fā)選舉時(shí)間(failover_auth_time),達(dá)到該時(shí)間繼續(xù)后續(xù)流程。
  3. 發(fā)起選舉
    發(fā)起選舉流程:
    1)更新配置紀(jì)元
    2)廣播選舉消息
  4. 選舉投票
    只有主節(jié)點(diǎn)才會(huì)處理故障選舉消息。


    image.png
  5. 替換主節(jié)點(diǎn)
    1)從節(jié)點(diǎn)取消復(fù)制變?yōu)橹鞴?jié)點(diǎn)
    2)執(zhí)行clusterDelSlot操作撤銷故障主節(jié)點(diǎn)負(fù)責(zé)的槽,并執(zhí)行clustrAddSlot把這些槽給自己
    3)在集群中廣播自己pong消息,說(shuō)自己登基了,不是備胎了。

8.6.3 故障轉(zhuǎn)移時(shí)間

故障發(fā)現(xiàn)到故障恢復(fù)花銷的時(shí)間大致可以由以下估計(jì):

  1. 主觀下線識(shí)別時(shí)間 = cluster-node-timeout
  2. 主觀下線傳播時(shí)間 <= cluster-node-timeout/2
  3. 從節(jié)點(diǎn)轉(zhuǎn)移時(shí)間 <= 1000毫秒

注:cluster-node-timeout 的默認(rèn)時(shí)間為 15秒

8.7 集群運(yùn)維

8.7.1 集群完整性

集群上每個(gè)槽(slot)都需要有一個(gè)非故障的主節(jié)點(diǎn)管理負(fù)責(zé),否則該集群會(huì)變?yōu)椴豢捎谩?/p>

8.7.2 集群傾斜

集群傾斜是指不同節(jié)點(diǎn)間數(shù)據(jù)量和請(qǐng)求量出現(xiàn)明顯差異,這會(huì)增大負(fù)載均衡和開(kāi)發(fā)運(yùn)維難度。

  1. 數(shù)據(jù)傾斜
    1)節(jié)點(diǎn)和槽分配嚴(yán)重不均。
    2)槽對(duì)應(yīng)鍵的數(shù)量差異過(guò)大。
    3)集合對(duì)象包含大量元素。
    4)內(nèi)存相關(guān)配置不一致。
    針對(duì) 1)可使用redis-trib.rb info host:ip 命令定位。
    針對(duì) 2)可使用cluster countkeysinslot {slot} 先獲取對(duì)應(yīng)槽的鍵數(shù),在 使用 cluster getkeysinslot {slot} {count} 循環(huán)迭代出槽下所以鍵。
    針對(duì) 3)可使用redis-cli --bigkeys 識(shí)別大集合對(duì)象,在進(jìn)行數(shù)據(jù)轉(zhuǎn)移。

8.7.3 數(shù)據(jù)遷移

將單機(jī)redis數(shù)據(jù)轉(zhuǎn)移到集群環(huán)境下:

redis-trib.rb import host:port --form <arg> --copy --replace

注:

  1. 只能從單機(jī)轉(zhuǎn)移值集群。
  2. 不支持在線轉(zhuǎn)移,數(shù)據(jù)提供方應(yīng)先停止讀寫。
  3. 不支持?jǐn)帱c(diǎn)續(xù)傳。
  4. 單線程進(jìn)行數(shù)據(jù)遷移,大數(shù)據(jù)量時(shí)遷移速度慢。

8.7 springboot + jedis + RedisTemplate java客戶端接入集群demo

配置

// 從配置文件中獲取集群配置信息
   @Value("${spring.redis.cluster.nodes}")
    private String clusterNodes;  // 節(jié)點(diǎn)格式 “host1:port,host2:port,host3:port,host4:port”
    @Value("${spring.redis.timeout}")
    private String timeOut;
    @Value("${spring.redis.password}")  // 集群密碼
    private String password;
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWait;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {

        Map<String, Object> source = new HashMap<String, Object>();
        source.put("spring.redis.cluster.nodes", clusterNodes);
        source.put("spring.redis.cluster.timeout", timeOut);

        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisProperties", source));
        redisClusterConfiguration.setPassword(RedisPassword.of(password));

        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setMaxWaitMillis(maxWait);
        config.setMaxTotal(maxActive);

        JedisConnectionFactory factory = new JedisConnectionFactory(redisClusterConfiguration,config);
        factory.getClientConfiguration();
        factory.afterPropertiesSet();

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericToStringSerializer<String>(String.class));

        redisTemplate.setConnectionFactory(factory);

        return redisTemplate;
    }

使用方法

   @Autowired
    private RedisTemplate redisTemplate; // 自動(dòng)注入

    /**  默認(rèn)過(guò)期時(shí)長(zhǎng),單位:秒 */
    public final static long DEFAULT_EXPIRE = 60 * 60 * 24 * 20;
    /**  當(dāng)天就過(guò)期,單位:秒 */
    public final static long DATE_EXPIRE = 60 * 60 * 24;
    /**  不設(shè)置過(guò)期時(shí)長(zhǎng) */
    public final static long NOT_EXPIRE = -1;

    public void set(String key, String value, long expire){
        valueOperations.set(key, value);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }
    public void set(String key, String value){
        set(key, value, NOT_EXPIRE);
    }
    public boolean del(String key){
        return redisTemplate.delete(key);
    }
    public String get(String key, long expire) {
        String value = valueOperations.get(key);
        if(expire != NOT_EXPIRE){
           redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value;
    }
    public <T> T get(String key, Class<T> clazz, long expire) {
        String value = valueOperations.get(key);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key,expire,TimeUnit.SECONDS);
        }
        return value == null ? null : fromJson(value, clazz);
    }

參考文獻(xiàn):

  1. 《Redis開(kāi)發(fā)與運(yùn)維》作者: 付磊 張益軍
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Redis Cluster Specification 1 設(shè)計(jì)目標(biāo)和理由 1.1 Redis Cluster g...
    近路閱讀 4,412評(píng)論 0 12
  • NOSQL類型簡(jiǎn)介鍵值對(duì):會(huì)使用到一個(gè)哈希表,表中有一個(gè)特定的鍵和一個(gè)指針指向特定的數(shù)據(jù),如redis,volde...
    MicoCube閱讀 4,170評(píng)論 2 27
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,329評(píng)論 2 89
  • 郭相麟 思想是什么? 它靠物質(zhì)來(lái)沉淀 靠精神來(lái)升華 只有物質(zhì)顯得浮華 只有精神顯現(xiàn)蒼白 唯有物質(zhì)和精神互為共生 融...
    郭相麟閱讀 246評(píng)論 0 0
  • 春天一到百花開(kāi), 燕子飛躍來(lái), 敢問(wèn)枝頭誰(shuí)最美? 人間善良笑容顏!
    小文化人閱讀 130評(píng)論 0 0

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