(五)redis性能問題
A. redis客戶端
- redis客戶端通信
- redis新版本對(duì)于網(wǎng)絡(luò)請(qǐng)求進(jìn)行多線程處理,收到請(qǐng)求后redis實(shí)際處理數(shù)據(jù)依然為單線程模式。
- redis客戶端同服務(wù)端間的通信基于tcp協(xié)議。
- jedis客戶端
- 開發(fā)階段將jedis的jar包加入project中,或使用maven添加依賴。
- jedis直連模式
- 是默認(rèn)方式,但每次使用都會(huì)新建tcp連接。
- jedis直連簡(jiǎn)單方便,但存在對(duì)象線程不安全的問題,適用于少量連接的情況。
- jedisPool模式
- 生產(chǎn)多采用該方式,預(yù)先生成幾個(gè)jedis對(duì)象于pool中,這些對(duì)象保持長(zhǎng)連接狀態(tài)。
- 具體使用時(shí),用getJedisFromPool方法獲取對(duì)象,再完成后續(xù)實(shí)際操作。
- 向jedisPool借用jedis對(duì)象是本地操作,并無(wú)網(wǎng)絡(luò)開銷??刂苝ool的大小可以有效確定并發(fā)的壓力,遠(yuǎn)小于新建tcp的方式。
B. redis內(nèi)存理解
- 概述:redis所有實(shí)時(shí)使用的數(shù)據(jù)都存儲(chǔ)于內(nèi)存中,持久化文件用于備份恢復(fù)。本小節(jié)九內(nèi)存消耗、內(nèi)存管理 & 優(yōu)化進(jìn)行闡述分析。
1. 內(nèi)存消耗
-
內(nèi)存使用統(tǒng)計(jì)
info memory:查看內(nèi)存相關(guān)指標(biāo),重點(diǎn)需要關(guān)注的是used_memory_rss, used_memory & 它們的比值mem_fragmentation_ratio。-
閾值注意點(diǎn)
- mem_fragmentation_ratio > 1時(shí),說明redis占用OS的內(nèi)存并非全是數(shù)據(jù)存儲(chǔ)消耗的,是內(nèi)存碎片消耗的,兩者相差很大則說明碎片率很嚴(yán)重;
- mem_fragmentation_ratio < 1時(shí),說明內(nèi)存不足時(shí)OS進(jìn)行swap,讓硬盤臨時(shí)替代成內(nèi)存給redis使用,這導(dǎo)致redis性能急劇下降。
-
核心指標(biāo)說明
屬性名 屬性說明 used_memory_human used_memory可讀顯示 used_memory_rss 操作系統(tǒng)下,redis進(jìn)程占用的物理內(nèi)存總量 mem_fragmentation_ratio used_memory_rss / used_memory,大于1就有碎片;小于1則說明有swap給redis用
-
內(nèi)存消耗分類
- 自身內(nèi)存:redis空進(jìn)程自身消耗內(nèi)存很少,通常used_memory_rss在3M左右,used_memory則約800kb。
- 對(duì)象內(nèi)存:最主要消耗內(nèi)存的部分,存儲(chǔ)用戶所有數(shù)據(jù)。一個(gè)對(duì)象由key和value兩部分組成。
- 緩沖內(nèi)存:緩沖內(nèi)存包括客戶端緩沖、復(fù)制積壓緩沖區(qū) & AOF緩沖區(qū)。在客戶端連接數(shù)過大時(shí),可能會(huì)造成redis內(nèi)存飆升。
- 內(nèi)存碎片:redis默認(rèn)內(nèi)存分配器是jemalloc,一般采用固定范圍的內(nèi)存塊分配,將內(nèi)存分為小、大、巨大三個(gè)范圍。頻繁的更新操作(append、setrange等)、大量過期key刪除易導(dǎo)致碎片出現(xiàn)。建議重啟節(jié)點(diǎn)完成碎片整理。
-
子進(jìn)程內(nèi)存消耗
- 場(chǎng)景:在AOF或RDB時(shí),fork的子進(jìn)程理論上會(huì)需要和父進(jìn)程一樣的內(nèi)存空間?;趌inux的copy-on-write技術(shù),父子進(jìn)程可共享物理內(nèi)存頁(yè)。但在接受寫請(qǐng)求時(shí),父進(jìn)程仍然需要對(duì)修改內(nèi)容所在的內(nèi)存頁(yè)進(jìn)行復(fù)制以完成寫操作。
- THP:transparent huge pages機(jī)制在linux2.6.38后出現(xiàn),默認(rèn)開啟,導(dǎo)致復(fù)制內(nèi)存頁(yè)從4kb變成2mb。若有大量寫請(qǐng)求,這會(huì)導(dǎo)致內(nèi)存消耗急劇上升。
2. 內(nèi)存管理
設(shè)置內(nèi)存上限目的:防止所用內(nèi)存超出服務(wù)器物理內(nèi)存,一般情況下設(shè)置合理的
maxmemory,保證機(jī)器預(yù)留20 - 30%閑置內(nèi)存。動(dòng)態(tài)調(diào)整內(nèi)存上限:
config set maxmemory 10g&config rewrite。注意,一旦使用內(nèi)存上限調(diào)整就會(huì)激活內(nèi)存回收。-
內(nèi)存回收策略
-
過期對(duì)象回收:精準(zhǔn)維護(hù)每個(gè)key過期會(huì)消耗大量cpu資源,對(duì)單線程的redis來說成本過高,一般有兩種模式解決。
- 惰性刪除:客戶端讀取到expired的key時(shí),會(huì)執(zhí)行刪除操作并返回空值。該方式雖然節(jié)約cpu,但存在內(nèi)存泄漏問題。若expired的key一直未被讀取就不會(huì)被刪除,內(nèi)存無(wú)法釋放。
- 定時(shí)任務(wù)刪除:redis內(nèi)部維護(hù)一個(gè)定時(shí)任務(wù),默認(rèn)每秒運(yùn)行10次,通過配置
hz完成。默認(rèn)使用慢模式運(yùn)行,每個(gè)數(shù)據(jù)庫(kù)空間隨機(jī)找20個(gè)key檢查,發(fā)現(xiàn)過期時(shí)刪除對(duì)應(yīng)的key-value。若本次檢查中超過25%的key過期,則繼續(xù)檢查20個(gè)key,直到低于25%或者運(yùn)行超時(shí)才結(jié)束回收的循環(huán)。
-
內(nèi)存溢出控制策略:基于
mexmemory-policy進(jìn)行策略的選取 & 確認(rèn),可使用config set maxmemory-policy xxx進(jìn)行動(dòng)態(tài)配置。策略名 策略具體內(nèi)容 noeviction 默認(rèn)策略,不刪除任何數(shù)據(jù),拒絕所有寫入操作并向客戶端返回OOM錯(cuò)誤信息。 volatile-lru 根據(jù)LRU算法刪除expired的key,直到空間足夠?yàn)橹?。若沒有可刪除對(duì)象,則回退成noeviction策略。 allkeys-lru 根據(jù)LRU刪除key,不管有無(wú)設(shè)置超時(shí)屬性。 allkeys-random 隨機(jī)刪除所有key。 volatile-random 隨機(jī)刪除expired的key。 volatile-ttl 根據(jù)key-value的ttl屬性,刪除最近將要過期的數(shù)據(jù),若沒有復(fù)合的對(duì)象回退noeviction策略。
-
4. 內(nèi)存優(yōu)化
- 概述:redis所有存儲(chǔ)數(shù)據(jù)都是用redisObject封裝,有下方幾個(gè)字段
| 字段 | 含義 |
|---|---|
| type | 當(dāng)前對(duì)象數(shù)據(jù)類型,主要為string,hash,list,set & zset。注意,key都是string類型。 |
| encoding | redis內(nèi)部編碼類型,代表其內(nèi)部采用的數(shù)據(jù)結(jié)構(gòu)。 |
| lru | 記錄對(duì)象最后一次被訪問的時(shí)間。 |
| refcount | 記錄當(dāng)前對(duì)象被引用的次數(shù),若refcount=0就可以被安全回收。 |
| *ptr | 和對(duì)象的數(shù)據(jù)內(nèi)容相關(guān),若為整數(shù)直接存儲(chǔ)數(shù)據(jù);若非整數(shù)則表示指向數(shù)據(jù)的指針。 |
- 縮減對(duì)象:縮減key和value的長(zhǎng)度是最直接的方法。
- 共享對(duì)象池:redis內(nèi)部維護(hù)0-9999的整數(shù)對(duì)象池,可通過
object refcount查看引用次數(shù)驗(yàn)證是否啟動(dòng)整數(shù)對(duì)象池技術(shù)。- 效果:使用該技術(shù)后,數(shù)據(jù)內(nèi)存使用率可降低30%以上。
- 限制:使用maxmemory-policy中的lru策略時(shí)(無(wú)論allkeys或volatile),redis禁止使用共享對(duì)象池。因?yàn)楣蚕頃r(shí),lru字段也需要共享,導(dǎo)致無(wú)法獲取每個(gè)對(duì)象最后的訪問時(shí)間。
- 其他:字符串的優(yōu)化(預(yù)分配機(jī)制、字符串重構(gòu)),encoding類型優(yōu)化 & 控制key數(shù)量。
C. redis緩存設(shè)計(jì)
- 概述
- 緩存收益
- 加速讀寫。
- 降低后端負(fù)載壓力,例如減少數(shù)據(jù)庫(kù)訪問量和復(fù)雜計(jì)算。
- 成本代價(jià)
- 數(shù)據(jù)不一致性:緩存層和存儲(chǔ)層數(shù)據(jù)在一定時(shí)間窗口內(nèi)有數(shù)據(jù)不一致問題,需要存儲(chǔ)層更新至緩存層。
- 代碼維護(hù)成本:新增對(duì)于緩存層的代碼 & 緩存與存儲(chǔ)邏輯的代碼。
- 運(yùn)維成本增加,維護(hù)集群狀態(tài)。
- 緩存收益
1. 緩存更新策略
- LRU、LFU或FIFO算法
- 使用場(chǎng)景:key數(shù)量過多,消耗內(nèi)存達(dá)到設(shè)置的maxmemory閾值時(shí),會(huì)對(duì)key-value的剔除。
- 問題:產(chǎn)生一致性問題較嚴(yán)重,刪除后導(dǎo)致緩存層數(shù)據(jù)缺失且運(yùn)維人員無(wú)法及時(shí)得知?jiǎng)h除信息。
- 配置方法:設(shè)置maxmemory大小 & maxmemory-policy算法即可。
- 超時(shí)剔除
- 使用場(chǎng)景:緩存數(shù)據(jù)配置了expire命令,保障key-value在一段時(shí)間后失效刪除。
- 問題:在一定窗口內(nèi)有一致性問題。
- 主動(dòng)更新
- 使用場(chǎng)景:對(duì)數(shù)據(jù)一致性要求較高時(shí)使用,若真實(shí)數(shù)據(jù)更新立即更新緩存層。
- 問題:維護(hù)成本較高,需開發(fā)正完成更新邏輯。
- 案例實(shí)踐建議
- 低一致性業(yè)務(wù):使用最大內(nèi)存淘汰算法即可。
- 高一致性業(yè)務(wù):超時(shí)剔除+主動(dòng)更新,結(jié)合使用。
2. redis雪崩
- 現(xiàn)象:redis的大量的key在某個(gè)時(shí)間段失效或redis服務(wù)不可用,導(dǎo)致需要直接訪問數(shù)據(jù)庫(kù),大量請(qǐng)求沖擊database。
- 解決方案
- 不要設(shè)置固定過期時(shí)間,盡量在setex時(shí)采用隨機(jī)的ttl。
- 保證緩存層服務(wù)高可用性,基于分布式集群架構(gòu)。
3. redis緩存穿透
- 現(xiàn)象:redis和database都不存在該key對(duì)應(yīng)的數(shù)據(jù),但用戶不斷發(fā)起對(duì)該類數(shù)據(jù)的請(qǐng)求導(dǎo)致database掛掉,比如持續(xù)大量請(qǐng)求uid的-1的對(duì)應(yīng)數(shù)據(jù)。
- 解決方案
- 緩存空對(duì)象,后續(xù)再訪問該key可基于緩存層返回null值,存在問題是導(dǎo)致緩存了更多的無(wú)用key & 數(shù)據(jù)不一致問題。該策略適用于數(shù)據(jù)命中不高,數(shù)據(jù)頻繁變化實(shí)時(shí)性高的場(chǎng)景。
- 設(shè)置database的并發(fā)鎖,防止大量請(qǐng)求在database上進(jìn)行。
- 設(shè)置攔截器,類似bitmaps或bloom filter,不存在的uid直接攔截在redis層,不可達(dá)database。適用于數(shù)據(jù)命中不高,數(shù)據(jù)相對(duì)固定的場(chǎng)景。
4. redis擊穿
- 現(xiàn)象:redis中沒有該key但是database中有,一般出現(xiàn)場(chǎng)景為某個(gè)hot key過期,大量用戶并發(fā)查詢?cè)搆ey導(dǎo)致直接沖擊database。
- 解決方案
- hot key重建
- 定義:緩存失效的瞬間,有大量線程來重建緩存。
- 負(fù)面效應(yīng):重建時(shí)后段附在加大,所以需要減少重建緩存的次數(shù)。
- 設(shè)置互斥鎖
- 互斥鎖通過競(jìng)爭(zhēng)對(duì)資源獨(dú)占使用,執(zhí)行順序是亂序的。該方法利用setex或setnx實(shí)現(xiàn),確保只有一個(gè)線程重建緩存,其他線程都在等待狀態(tài)。
- 同步鎖更高級(jí),能保證執(zhí)行順序。
- 設(shè)置hot key永不過期
- 直接不設(shè)置過期時(shí)間,或者設(shè)置一個(gè)邏輯過期時(shí)間,超過該周期后單獨(dú)使用一個(gè)線程重建緩存。
- 但容易出現(xiàn)數(shù)據(jù)一致性問題。
- hot key重建
D. pipeline & transaction
- pipeline
- 概述:管道技術(shù)是客戶端行為,對(duì)redis server透明,server不知道發(fā)過來的請(qǐng)求是普通的還是pipeline形式的。
- 使用效果:類似批量的方式缺失降低了網(wǎng)絡(luò)開銷,利用多個(gè)命令一次網(wǎng)絡(luò)發(fā)送提升了效率?;趆getall的操作并沒有對(duì)應(yīng)的mhgetall可用,此時(shí)pipeline可派上用場(chǎng)。
- transaction
- 概述:事務(wù)是redis server行為,當(dāng)客戶端使用了MULTI命令后把客戶端對(duì)象設(shè)置為特殊狀態(tài)??蛻舳税l(fā)出的命令會(huì)被緩存到server,等到EXEC再執(zhí)行。
- 使用效果:客戶端在exec前的命令,都會(huì)被server返回一個(gè)queued,事務(wù)模式能提高網(wǎng)絡(luò)使用效率,但不支持回滾。
E. 異常排查 & 配置優(yōu)化
1. Jedis客戶端常見異常
-
無(wú)法從連接池獲取到連接:jedisPool的jedis個(gè)數(shù)有限(默認(rèn)8個(gè)),若他們都被使用時(shí),那么第9個(gè)來調(diào)用的就會(huì)報(bào)錯(cuò)。
- 報(bào)錯(cuò)信息:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get get a resource from the pool - 高并發(fā)場(chǎng)景下,連接池的jedis個(gè)數(shù)可進(jìn)行調(diào)整。若我們認(rèn)為一個(gè)jedis每條命令處理時(shí)間5ms,1s單個(gè)jedis對(duì)象極限能處理200個(gè)命令,8個(gè)jedis的qps極限約為200*8=1600。若qps預(yù)計(jì)在1.6w的話,需將jedis數(shù)量調(diào)整至80。
- 若存在慢查詢,那么這個(gè)jedis也會(huì)被長(zhǎng)時(shí)間占用不歸還,可能造成該異常。
- 報(bào)錯(cuò)信息:
-
讀寫超時(shí)
- 報(bào)錯(cuò)信息:
redis.clients.jedis.exceptions.JedisConnectionException & java.net.SocketTimeoutException: read timed out - 讀寫超時(shí)設(shè)置的太短。
- 讀寫操作耗時(shí)太久,需優(yōu)化具體邏輯。
- 報(bào)錯(cuò)信息:
-
連接超時(shí)
- 報(bào)錯(cuò)信息:
redis.clients.jedis.exceptions.JedisConnectionException & java.net.SocketTimeoutException: connect timed out - 連接超時(shí)設(shè)置太短,修改參數(shù)
jedis.getClients().setConnectionTimeout(A larger value)。 - redis發(fā)生阻塞,tcp-backlog滿了導(dǎo)致新連接失敗。
- 服務(wù)端與客戶端間的網(wǎng)絡(luò)存在問題。
- 報(bào)錯(cuò)信息:
-
客戶端緩沖區(qū)異常
- 報(bào)錯(cuò)信息:
redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream - jedis輸出的緩沖區(qū)大小偏小,若set bigkey,可能就會(huì)報(bào)錯(cuò).
- jedis不正常并發(fā)讀寫,被多個(gè)線程操作。
- 報(bào)錯(cuò)信息:
-
其他場(chǎng)景異常
lua腳本正在執(zhí)行:正在運(yùn)行l(wèi)ua腳本時(shí)會(huì)報(bào)錯(cuò)。
加載持久化文件:正在加載rdb文件時(shí)會(huì)報(bào)錯(cuò)。
內(nèi)存使用情況超出maxmemory:若redis使用內(nèi)存過多,超出預(yù)設(shè)的maxmemory會(huì)報(bào)錯(cuò)OOM,需要擴(kuò)容調(diào)整。
-
客戶端連接數(shù)過大:超出預(yù)設(shè)的maxclients時(shí),會(huì)報(bào)錯(cuò),具體解決手段有兩個(gè)大方向
客戶端:若maxclients設(shè)置的閾值不小,則一般是客戶端使用不當(dāng)導(dǎo)致的,可下線部分應(yīng)用節(jié)點(diǎn)先降低總連接數(shù)。
服務(wù)端:基于服務(wù)端一般是高可用模式,可采用故障轉(zhuǎn)移機(jī)制,下線有問題的redis節(jié)點(diǎn),完成主從切換后再排查。
-
實(shí)際案例
- redis內(nèi)存陡增:master內(nèi)存狂增幾乎打滿maxmemory,但slave內(nèi)存幾乎無(wú)變化,導(dǎo)致無(wú)法寫入新數(shù)據(jù),報(bào)出OOM的錯(cuò)誤。
- 確有大量key寫入:基于slave內(nèi)存無(wú)變化的現(xiàn)象,可初步判定主從間復(fù)制出現(xiàn)問題,比較兩側(cè)dbsize進(jìn)行復(fù)核。
- 其他原因:造成master壓力大可能是jedis等客戶端緩沖區(qū)導(dǎo)致的,比如客戶端使用monitor命令,可使用info clients確認(rèn)。
- 客戶端周期性超時(shí):redis服務(wù)端無(wú)明顯異常,可發(fā)現(xiàn)部分慢查詢。
- 網(wǎng)絡(luò)原因?qū)е略摤F(xiàn)象出現(xiàn)。
- redis服務(wù)端自身問題,需查log文件再確認(rèn)。
- 查詢慢查詢時(shí)間點(diǎn),查看客戶端日志中是否存在
redis.clients.jedis.exceptions.JedisConnectionException&java.net.SocketTimeoutException: connect timed out的錯(cuò)誤信息。
- redis內(nèi)存陡增:master內(nèi)存狂增幾乎打滿maxmemory,但slave內(nèi)存幾乎無(wú)變化,導(dǎo)致無(wú)法寫入新數(shù)據(jù),報(bào)出OOM的錯(cuò)誤。
2. 運(yùn)維配置優(yōu)化
a. Linux - 內(nèi)存分配控制overcommit
定義:Linux對(duì)于大部分申請(qǐng)內(nèi)存的請(qǐng)求回復(fù)為yes,便于運(yùn)行更多的程序。大多數(shù)情況下,申請(qǐng)完內(nèi)存后并不會(huì)馬上使用內(nèi)存,該技術(shù)即為overcommit。本處內(nèi)存代表,物理內(nèi)存+swap之和。
-
參數(shù)具體可選值
值 含義 0 表示內(nèi)核將檢查是否有足夠的可用內(nèi)存,若有足夠內(nèi)存,則申請(qǐng)通過;若沒有足夠的可用內(nèi)存,則申請(qǐng)失敗。 1 表示內(nèi)核允許超量使用內(nèi)存直到耗盡。 2 表示內(nèi)核絕不過量使用內(nèi)存(never overcommit),系統(tǒng)整個(gè)內(nèi)存地址空間不超過swap+50%RAM值。 常見場(chǎng)景:redis啟動(dòng)時(shí),日志中出現(xiàn)
WARNING overcommit is set to 0!...-
redis中的解決方案:上述日志提示修改
vm.overcommit_memory=1,便于持久化操作的fork能在低內(nèi)存下執(zhí)行成功。- 獲取參數(shù):
cat /proc/sys/vm/overcommit_memory - 設(shè)置參數(shù):
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf,并進(jìn)行sysctl vm.overcommit_memory=1
- 獲取參數(shù):
b. Linux - 內(nèi)存交換swappiness
定義:物理內(nèi)存不足時(shí)將一部分內(nèi)存頁(yè)進(jìn)行swap操作——將硬盤暫時(shí)作內(nèi)存使用。若遇到高并發(fā)、高吞吐的應(yīng)用,此時(shí)磁盤IO會(huì)成為系統(tǒng)瓶頸。在Linux 下,并不是物理內(nèi)存使用完才會(huì)使用swap,具體何時(shí)使用swap基于swappiness參數(shù)。
-
參數(shù)含義:swappiness取值范圍在 0 -100間,默認(rèn)值為60,該值越大意味著操作系統(tǒng)使用swap的概率越高。
值 策略 0 Linux3.5及更新版本,寧愿OOM killer也不用swap。 1 Linux3.5及更新版本,寧愿用swap也不用OOM killer。 60 默認(rèn)值。 100 Linux會(huì)主動(dòng)使用swap。 -
設(shè)置方法
- 配置swappiness:使用
echo {bestvalue} > /proc/sys/vm/swapiness - 確保重啟依然生效:使用
echo vm.swappiness={bestvalue} >> /etc/sysctl.conf
- 配置swappiness:使用
-
常見場(chǎng)景
- redis在物理內(nèi)存充足時(shí)運(yùn)行極快,物理內(nèi)存不足時(shí)該配置可避免redis死掉。
- 若redis時(shí)高可用的情況下,寧愿死掉也不要使用swap,因?yàn)檫@會(huì)導(dǎo)致阻塞。
- 除了直接free -h查看swap情況,亦可參考mem_fragmentation_ratio,若 < 1則存在redis使用swap的情況。
c. Linux - THP
- 定義:Linux kernel在2.6.38后增加了THP特性,默認(rèn)開啟。全稱Transparent Huge Pages,支持大內(nèi)存頁(yè)2MB分配,可加快fork子進(jìn)程的速度,但fork操作后每個(gè)內(nèi)存頁(yè)從4KB增加到2MB,會(huì)大幅度增加重寫期間父進(jìn)程的內(nèi)存消耗,每次寫命令引起的復(fù)制內(nèi)存頁(yè)單位放大了512倍。
- 參數(shù)設(shè)置
- 禁用THP特性:使用命令
echo never > /sys/kernel/mm/transparent_hugepage/enabled - 確保重啟依然生效:編輯開機(jī)配置文件/etc/rc.local,追加禁用THP特性命令——
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- 禁用THP特性:使用命令
- 常見場(chǎng)景
- redis啟動(dòng)時(shí)日志中出現(xiàn)
WARNING you have Transparent Huge Pages (THP) support enabled in your kernel...,需手動(dòng)禁用THP。 - 部分版本的Linux 未把THP放入/sys/kernel/mm/transparent_hugepage/enabled,例如Red Hat 6以上的版本即將THP配置放入/sys/kernel/mm/redhat_transparent_hugepage/enabled,但redis將檢查THP的位置寫死導(dǎo)致不能成功檢測(cè)THP問題并將之暴露于日志中,但THP的問題依舊存在需要手動(dòng)調(diào)整。
- redis啟動(dòng)時(shí)日志中出現(xiàn)
d. Linux - OOM Killer
- 定義:在內(nèi)存不足時(shí)選擇性地殺掉用戶進(jìn)程,OOM Killer會(huì)為每個(gè)用戶進(jìn)程設(shè)置一個(gè)權(quán)值,該值越高越容易被優(yōu)先干掉。
- 參數(shù)設(shè)置
- 參數(shù)位置:每個(gè)進(jìn)程的權(quán)值位于/proc/{pid}/oom_score中,受到/proc/{pid}/oom_adj的控制。
- oom_adj
- 當(dāng)oom_adj設(shè)置為最小值時(shí),該進(jìn)程不會(huì)被干掉。不同Linux 版本的最小值不同,可參考Linux 源碼中的oom.h(-15到-17)。
- 設(shè)置方法為
echo {value} > /proc/{pid}/oom_adj
- 適用場(chǎng)景:為保證redis在主機(jī)內(nèi)存使用率接近滿載時(shí)仍然能存活下來,可將redis進(jìn)程對(duì)應(yīng)的oom_adj設(shè)置成最小值。
e. Linux - 其他參數(shù)
-
NTP
- 定義:網(wǎng)絡(luò)時(shí)間協(xié)議,全稱network time protocol,保證不同機(jī)器的時(shí)鐘一致性。
- 場(chǎng)景:redis集群有多節(jié)點(diǎn)組成,涉及多主機(jī)問題。時(shí)鐘不一致會(huì)導(dǎo)致redis集群內(nèi)問題排查難度提升,例如無(wú)法有效判別clutser故障轉(zhuǎn)移。
- 解決手段:建議每小時(shí)使用一次時(shí)鐘同步,保障其集群內(nèi)時(shí)鐘一致性。具體為
0 * * * * /usr/sbin/ntpdate ntp.xx.com > /dev/null 2>&1
-
ulimit
-
定義:查看和設(shè)置系統(tǒng)當(dāng)前用戶進(jìn)程的資源數(shù),常用命令
ulimit -a,其展示效果可見下方。category unit value core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 61370 open files (-n) 4096 POSIX message queues (bytes, -q) 819200 max user processes (-u) 61370 -
場(chǎng)景
redis允許同時(shí)多個(gè)客戶端通過網(wǎng)絡(luò)進(jìn)行連接,可通過服務(wù)端配置
maxclients參數(shù)來進(jìn)行具體客戶端數(shù)的限制。redis除了供給客戶端連接數(shù)所需的10000FD句柄,還需要32個(gè)FD供redis自身內(nèi)部使用。
-
Linux系統(tǒng)下,所有網(wǎng)絡(luò)連接都是文件句柄,若當(dāng)前open files為4096,則啟動(dòng)redis時(shí)會(huì)有下方日志
You requested maxclients of 10000 requiring at least 10032 max file descriptors...Current maximum open files is 4096...
解決手段:將open files的值設(shè)置為65535,通過命令
ulimit -Sn 65535實(shí)現(xiàn)。
-
-
TCP backlog
- 定義
- backlog是一個(gè)連接隊(duì)列,在Linux內(nèi)核2.2之前,backlog包括半連接狀態(tài)和全連接狀態(tài)兩種隊(duì)列大小;在Linux內(nèi)核2.2之后,分離為兩個(gè)backlog來分別限制半連接(SYN_RCVD狀態(tài))隊(duì)列大小和全連接(ESTABLISHED狀態(tài))隊(duì)列大小。
- 半連接狀態(tài):服務(wù)器處于Listen狀態(tài)時(shí)收到客戶端SYN報(bào)文時(shí)放入半連接隊(duì)列中,即SYN queue(服務(wù)器端口狀態(tài)為:SYN_RCVD)。
- 全連接狀態(tài):TCP的連接狀態(tài)從服務(wù)器(SYN+ACK)響應(yīng)客戶端后,到客戶端的ACK報(bào)文到達(dá)服務(wù)器之前,則一直保留在半連接狀態(tài)中;當(dāng)服務(wù)器接收到客戶端的ACK報(bào)文后,該條目將從半連接隊(duì)列搬到全連接隊(duì)列尾部,即 accept queue (服務(wù)器端口狀態(tài)為:ESTABLISHED)。
- 場(chǎng)景:redis默認(rèn)tcp-backlog值為511,若Linux系統(tǒng)的值小于511則會(huì)在啟動(dòng)日志中出現(xiàn)
WARNING: THE TCP backlog setting of 511 cannot be ... - 解決手段
- 查看系統(tǒng)值:通過命令
cat /proc/sys/net/core/somaxconn查看accept queue具體值,redis不涉及修改syn queue的問題,若想查看可通過cat /proc/sys/net/ipv4/tcp_max_syn_backlog進(jìn)行查詢。 - 修改:使用
echo 511 > /proc/sys/net/core/somaxconn進(jìn)行變更。
- 查看系統(tǒng)值:通過命令
- 定義
f. 數(shù)據(jù)恢復(fù)手段
- AOF機(jī)制恢復(fù)
- 場(chǎng)景:若誤操作flushall或flushdb導(dǎo)致數(shù)據(jù)丟失,需借助持久化文件進(jìn)行恢復(fù)。若開啟了
appendonly yes,則誤操作僅在AOF文件中追加了一條操作記錄。 - 解決手段
- 若AOF文件重寫了,則之前的數(shù)據(jù)就無(wú)法順利找回,所以需要調(diào)整
auto-aof-rewrite-percentage&auto-aof-rewrite-min-size阻止AOF自動(dòng)重寫,并拒絕手動(dòng)bgrewriteaof。 - 確保上方參數(shù)設(shè)置后,將AOF文件中的flush操作去掉,并確保AOF文件格式正常以保障數(shù)據(jù)的順利恢復(fù)。
- 若AOF文件重寫了,則之前的數(shù)據(jù)就無(wú)法順利找回,所以需要調(diào)整
- 場(chǎng)景:若誤操作flushall或flushdb導(dǎo)致數(shù)據(jù)丟失,需借助持久化文件進(jìn)行恢復(fù)。若開啟了
- RDB機(jī)制恢復(fù)
- 前置配置:若rdb持久化設(shè)置中有自動(dòng)策略,例如
save 900 1這類,則因?yàn)閒lush一般涉及key value都較多,會(huì)導(dǎo)致RDB恢復(fù)基本無(wú)望。除非并無(wú)開啟rdb自動(dòng)策略,否則面對(duì)flush誤操作,rdb不能有效恢復(fù)數(shù)據(jù)。 - 解決手段:若redis沒有進(jìn)行rdb自動(dòng)策略,僅有手動(dòng)的bgsave所得的rdb文件,那么即可使用rdb文件恢復(fù)數(shù)據(jù),但相對(duì)數(shù)據(jù)完整性不如aof機(jī)制。
- 前置配置:若rdb持久化設(shè)置中有自動(dòng)策略,例如
F. redis阻塞問題
- 概述
- redis屬于典型的單線程架構(gòu),讀寫操作皆于唯一的主線程中完成。
- redis處于高并發(fā)場(chǎng)景時(shí),主線程若出現(xiàn)阻塞則問題嚴(yán)重,阻塞時(shí)jedis會(huì)拋出JedisConnectionException異常。
- 阻塞內(nèi)因:不合理使用api或數(shù)據(jù)結(jié)構(gòu),cpu飽和、持久化阻塞(basave的fork瞬間或save持久化)。
- 阻塞外因:cpu競(jìng)爭(zhēng)、內(nèi)存交換(swap)、網(wǎng)絡(luò)問題。
1. 內(nèi)因詳解
a. api或數(shù)據(jù)結(jié)構(gòu)使用不合理
場(chǎng)景案例:redis正常場(chǎng)景下執(zhí)行命令速度極快(微妙級(jí)),若執(zhí)行
hgetall命令且該hash類型的key是big key,則執(zhí)行速度就會(huì)很慢,屬于是典型的不合理使用api & 數(shù)據(jù)結(jié)構(gòu)。-
慢查詢
- 定義:默認(rèn)配置
slowlog-log-slower-than 10000,即10ms以上的操作被判定為慢查詢,slowlog-max-len 128默認(rèn)存儲(chǔ)慢查詢?nèi)罩鹃L(zhǎng)度為128條。 - 獲取結(jié)果:
slowlog get 10,即獲得最近的10條慢查詢命令。
- 定義:默認(rèn)配置
-
解決方案
定制慢查詢閾值:線上一般可設(shè)置1ms為慢查詢閾值,即
slowlog-log-slower-than 1000,該速度下單機(jī)redis的qps將被限制為1000左右。拓展慢查詢?nèi)罩鹃L(zhǎng)度:增加日志長(zhǎng)度幫助排查故障的原因,將
slowlog-log-slower-than可設(shè)置為1000。其他:使用快速命令
hgetall改成hmget,并禁用keys、sort命令,并將big key拆分。
-
大鍵big key問題
- 定義
- big key指value所占空間很大的key,一般地,string類型的key,若其value大于10KB即為big key。其他類型的big key,是因?yàn)榘脑剡^多。
- 一個(gè)string類型的key,value最大為512MB;一個(gè)list類型的key,其value最多可存儲(chǔ)(2的32次方-1)個(gè)元素。
- 危害
- 內(nèi)存空間不均:若在redis集群模式下,bigkey必須被分配至某個(gè)節(jié)點(diǎn),導(dǎo)致內(nèi)存空間使用不均勻。
- 阻塞:因?yàn)椴僮鱞ig key容易耗時(shí)過高,導(dǎo)致阻塞時(shí)間偏長(zhǎng)。
- 網(wǎng)絡(luò)擁塞:每次獲取big key產(chǎn)生的網(wǎng)絡(luò)流量較大,占用帶寬。
- 檢測(cè)big key
- 被動(dòng)收集:big key在被訪問時(shí),出現(xiàn)異常日志。通過這種情況探測(cè)出的big key,即為被動(dòng)收集的模式。
- 主動(dòng)檢測(cè):使用
redis-cli -h xxx.xxx.xxx.xxx -p 6379 --bigkeys探測(cè),其本質(zhì)是使用scan漸進(jìn)式遍歷,rdr也是很好的redis分析工具,在從節(jié)點(diǎn)上操作時(shí)更合適的手段。
- 刪除big key
- string類型:該類的big key一般可以直接
del key即可,不會(huì)阻塞。 - hash、list、set & sorted set:直接刪除依然會(huì)導(dǎo)致阻塞,若為hash類型的場(chǎng)景下,可結(jié)合hscan逐步獲取進(jìn)行刪除。
- Redis4.0后特性:使用lazy delete free模式,刪除big key不會(huì)阻塞。
- string類型:該類的big key一般可以直接
- 定義
-
熱鍵hot key問題
- 常見場(chǎng)景:熱點(diǎn)突發(fā)新聞、熱銷大賣商品會(huì)給系統(tǒng)帶來巨大流量,在集群模式下存儲(chǔ)這些信息的redis節(jié)點(diǎn)會(huì)出現(xiàn)流量不均勻的情況,存儲(chǔ)熱點(diǎn)消息的節(jié)點(diǎn)出現(xiàn)QPS壓力偏大的問題。
- 判別方法
- 代理端:若客戶端對(duì)redis服務(wù)端的請(qǐng)求,是基于Twemproxy/Codis這樣的分布式架構(gòu),則所有的請(qǐng)求都通過代理端再達(dá)到服務(wù)端,可在代理處完成數(shù)據(jù)統(tǒng)計(jì)分析big key。
- 服務(wù)端:使用monitor在server處分析客戶端大量請(qǐng)求中的hot key,但是使用monitor會(huì)影響redis性能,且該方法是針對(duì)redis單節(jié)點(diǎn)進(jìn)行的,若為集群模式還需要聚合統(tǒng)計(jì)。
- 解決方案
- 拆分復(fù)雜數(shù)據(jù)結(jié)構(gòu):若為二級(jí)結(jié)構(gòu)(list、hash等),等可拆分成若干key-value,分散至redis集群的各個(gè)節(jié)點(diǎn)上。
- 遷移:將hot key所在的slot單獨(dú)放置到一個(gè)redis節(jié)點(diǎn)。
- 本地緩存:將hot key放在業(yè)務(wù)端的本地緩存中,處理速度更快且降低redis壓力,但需要注意數(shù)據(jù)一致性問題。
b. cpu飽和
- 簡(jiǎn)述:redis處理命令時(shí)為單線程,僅有主線程處理業(yè)務(wù),所以可用cpu數(shù)為1。若單核cpu的主機(jī),則使用率接近100%,可使用top命令或
redis-cli -h xxx.xxx.xxx.xxx -p 6379 --stat查看使用情況,stat中connections是歷史總連接數(shù)累計(jì),實(shí)時(shí)數(shù)據(jù)可看lsof -i:6379 | wc -l。 - 解決方案
- 若看到qps已經(jīng)足夠高(一般可達(dá)6w),那么此時(shí)垂直優(yōu)化較困難,建議通過集群化完成水平擴(kuò)展。
- 若發(fā)現(xiàn)qps僅數(shù)千甚至更低,那么大概率使用了高時(shí)間復(fù)雜度的命令,需修改業(yè)務(wù)方法。
- 內(nèi)存優(yōu)化過度,可用
info commandstats檢查,比如hset耗時(shí)過長(zhǎng),為節(jié)約內(nèi)存空間放寬ziplist的使用條件,可修改hash-max-ziplist-entries&hash-max-ziplist-values。
c. 持久化導(dǎo)致堵塞
前置說明:不考慮使用
save的場(chǎng)景,這種模式會(huì)導(dǎo)致長(zhǎng)時(shí)間redis堵塞。fork阻塞:rdb和aof重寫恢復(fù)數(shù)據(jù)時(shí),主線程會(huì)fork生成子進(jìn)程,子進(jìn)程再完成實(shí)際的重寫。若fork耗時(shí)過長(zhǎng),則可能導(dǎo)致阻塞出現(xiàn);
aof阻塞:開啟aof后,文件刷盤的方式一般為1s一次,后臺(tái)線程對(duì)aof文件進(jìn)行fsync操作,當(dāng)磁盤壓力大時(shí)fsync會(huì)需要等待,其主要緣由是磁盤壓力偏大。
2. 外因詳解
- cpu競(jìng)爭(zhēng)
- 進(jìn)程競(jìng)爭(zhēng):redis屬于cpu密集型應(yīng)用,雖然一般情況下cpu不是性能瓶頸,但是不建議和其他服務(wù)部署在一臺(tái)主機(jī)上。
- 綁定cpu:redis為盡可能地充分利用多核cpu,通常會(huì)一臺(tái)機(jī)器部署多個(gè)redis實(shí)例。此場(chǎng)景下,不同redis進(jìn)程綁定對(duì)應(yīng)的cpu可降低上下文切換的開銷,但是某個(gè)實(shí)例進(jìn)行rdb或者aof持久化時(shí),可能因?yàn)榻壎╟pu會(huì)導(dǎo)致壓力過大。
- 內(nèi)存交換swap
- 簡(jiǎn)述:swap空間即內(nèi)存空間不足時(shí),將磁盤充當(dāng)內(nèi)存使用。
- 甄別手段
- 找到redis pid,通過
redis-cli -p 6379 info server | grep process_id; - 接著根據(jù)id查詢swap信息,使用
cat /proc/xxxx/smaps | grep swap。正常情況下應(yīng)該都是0kb或者少量4kb。
- 找到redis pid,通過
- 網(wǎng)絡(luò)問題
- 拒絕連接:網(wǎng)絡(luò)割接或帶寬耗盡時(shí)的網(wǎng)絡(luò)閃斷(
sar -n DEV查看),redis超過默認(rèn)1w的maxclients連接數(shù),或者是進(jìn)程限制打開文件數(shù)(ulimit -n)、backlog隊(duì)列溢出。 - 網(wǎng)絡(luò)延遲:客戶端到redis服務(wù)器之間網(wǎng)絡(luò)延遲,可使用
redis-cli -h xxx.xxx.xxx.xxx -p 6379 --latency核驗(yàn)。 - 網(wǎng)卡軟中斷:高并發(fā)下單個(gè)網(wǎng)卡隊(duì)列只能使用一個(gè)cpu,新版redis已經(jīng)改進(jìn)網(wǎng)絡(luò)方面效率問題。
- 拒絕連接:網(wǎng)絡(luò)割接或帶寬耗盡時(shí)的網(wǎng)絡(luò)閃斷(