一哗总、前言
在互聯(lián)網(wǎng)應(yīng)用中,緩存成為高并發(fā)架構(gòu)的關(guān)鍵組件脸候。這篇博客主要介紹緩存使用的典型場(chǎng)景穷娱、實(shí)操案例分析、Redis使用規(guī)范及常規(guī) Redis 監(jiān)控运沦。
二泵额、常見緩存對(duì)比
常見的緩存方案,有本地緩存携添,包括HashMap/ConcurrentHashMap嫁盲、Ehcache、Memcache烈掠、Guava Cache等羞秤,緩存中間件包括Redis、Tair等左敌。
三瘾蛋、Redis使用場(chǎng)景
1. 計(jì)數(shù)
Redis實(shí)現(xiàn)快速計(jì)數(shù)及緩存功能。
例如:視頻或直播在線觀看人數(shù)矫限,用戶每播放一次哺哼,就會(huì)自增1。
2. Session集中管理
Session可以存儲(chǔ)在應(yīng)用服務(wù)是JVM中叼风,但這一種方案會(huì)有一致性的問題取董,還有高并發(fā)下,會(huì)引發(fā)JVM內(nèi)存溢出咬扇。Redis將用戶的Session集中管理甲葬,這種情況下只要保證Redis的高可用和擴(kuò)展性,每次用戶更新或查詢登錄都直接從Redis中信息獲取懈贺。
3.限速
例如:業(yè)務(wù)要求用戶一分鐘內(nèi)经窖,只能獲取5次驗(yàn)證碼坡垫。
4.排行榜
關(guān)系型數(shù)據(jù)庫在排行榜方面查詢速度普遍偏慢,所以可以借助redis的SortedSet進(jìn)行熱點(diǎn)數(shù)據(jù)的排序画侣。
比如在項(xiàng)目中冰悠,如果需要統(tǒng)計(jì)主播的吸金排行榜,可以以主播的id作為member, 當(dāng)天打賞的活動(dòng)禮物對(duì)應(yīng)的熱度值作為 score, 通過zrangebyscore就可以獲取主播活動(dòng)日榜配乱。
5.分布式鎖
在實(shí)際的多進(jìn)程并發(fā)場(chǎng)景下溉卓,使用分布式鎖來限制程序的并發(fā)執(zhí)行。多用于防止高并發(fā)場(chǎng)景下搬泥,緩存被擊穿的可能桑寨。
分布式鎖的實(shí)際就是"占坑",當(dāng)另一個(gè)進(jìn)程來執(zhí)行setnx時(shí)忿檩,發(fā)現(xiàn)標(biāo)識(shí)位已經(jīng)為1尉尾,只好放棄或者等待。
四燥透、案例解析
1沙咏、【案例】過期設(shè)置- set命令會(huì)去掉過期時(shí)間
Redis所有的數(shù)據(jù)結(jié)構(gòu),都可以設(shè)置過期時(shí)間班套。如果一個(gè)字符串已經(jīng)設(shè)置了過期時(shí)間肢藐,然后重新設(shè)置它,會(huì)導(dǎo)致過期時(shí)間消失吱韭。所以在項(xiàng)目中需要合理評(píng)估Redis容量吆豹,避免因?yàn)轭l繁set導(dǎo)致沒有過期策略,間接導(dǎo)致內(nèi)存被占滿杉女。
如下是 Redis 源碼截圖:
2瞻讽、【案例】關(guān)于 Jedis 2.9.0 及以下版本過期設(shè)置 BUG
發(fā)現(xiàn)Jedis在進(jìn)行expiredAt命令調(diào)用時(shí)有bug,最終調(diào)用的是pexpire命令熏挎,這個(gè)bug會(huì)導(dǎo)致key過期時(shí)間很長(zhǎng)速勇,導(dǎo)致Redis內(nèi)存溢出等問題。建議升級(jí)到Jedis 2.9.1及以上版本坎拐。
BinaryJedisCluster.java源碼如下:
@Override
public Long pexpireAt(final byte[] key, final long millisecondsTimestamp) {
return new JedisClusterCommand<Long>(connectionHandler, maxAttempts) {
@Override
public Long execute(Jedis connection) {
return connection.pexpire(key, millisecondsTimestamp); //此處pexpire應(yīng)該是pexpireAt
}
}.runBinary(key);
}
對(duì)比pexpire和pexpireAt:
比如我們當(dāng)前使用的時(shí)間是2018-06-14 17:00:00烦磁,它的unix時(shí)間戳為1528966800000毫秒,當(dāng)我們使用PEXPIREAT命令時(shí)哼勇,由于是過去的時(shí)間都伪,相應(yīng)的key會(huì)立即過期。
而我們誤用了PEXPIRE命令時(shí)积担,key不會(huì)立即過期陨晶,而是等到1528966800000毫秒后才過期,key過期時(shí)間會(huì)相當(dāng)長(zhǎng),約幾W天后先誉,從而可能導(dǎo)致Redis內(nèi)存溢出湿刽、服務(wù)器崩潰等問題。
3褐耳、【案例】緩存被擊穿
緩存的key有過期策略诈闺,如果恰好在這個(gè)時(shí)間點(diǎn)對(duì)這個(gè)Key有大量的并發(fā)請(qǐng)求,這些請(qǐng)求發(fā)現(xiàn)緩存過期一般都會(huì)從后端DB回源數(shù)據(jù)并回設(shè)到緩存铃芦,這個(gè)時(shí)候大并發(fā)的請(qǐng)求可能會(huì)瞬間把后端DB壓掛雅镊。
業(yè)界常用優(yōu)化方案有兩種:
第一種:使用分布式鎖,保證高并發(fā)下刃滓,僅有一個(gè)線程能回源后端DB仁烹。
第二種:保證高并發(fā)的請(qǐng)求到的Redis key始終是有效的,使用非用戶請(qǐng)求回源后端咧虎,改成主動(dòng)回源晃危。一般可以使用異步任務(wù)進(jìn)行緩存的主動(dòng)刷新。
4老客、【案例】Redis-standalone架構(gòu)禁止使用非0庫
Redis執(zhí)行命令select 0和select 1切換,造成性能損耗震叮。
RedisTemplate在執(zhí)行execute方法的時(shí)候會(huì)先獲取鏈接胧砰。
執(zhí)行到RedisConnectionUtils.java,會(huì)有一段獲取鏈接的方法苇瓣。
JedisConnectionFactory.java 會(huì)調(diào)用JedisConnection構(gòu)造器尉间,注意這邊的dbIndex就是數(shù)據(jù)庫編號(hào),如:1
繼續(xù)跟進(jìn)JedisConnection代碼击罪,當(dāng)選擇庫大于1時(shí)哲嘲,會(huì)有select db操作。如果一直使用0庫是不需要額外執(zhí)行切庫命令的媳禁。知道了第一個(gè)切庫select 1的地方眠副,那么select 0是哪來的呢?
其實(shí)客戶端使用Redis也會(huì)是要釋放鏈接的竣稽,只不過RedisTemplate已經(jīng)幫我們自動(dòng)釋放了囱怕,讓我們?cè)倩氐揭婚_始RedisTemplate執(zhí)行execute(...)方法的地方。
下面還是RedisConnectionUtils.java毫别,執(zhí)行鏈接關(guān)閉的代碼娃弓。
按代碼注釋的意思,如果選擇庫編號(hào)不為0岛宦,spring-data-redis框架每次都會(huì)執(zhí)行重置select 0台丛!
筆者在vivo商城業(yè)務(wù)中,商品詳情頁接口經(jīng)過上面的調(diào)優(yōu)砾肺,性能提高了3倍多挽霉。
進(jìn)一步驗(yàn)證數(shù)據(jù)庫切換至少影響性能3倍左右(視具體業(yè)務(wù)而定)防嗡。
Rediscluster集群數(shù)據(jù)庫,默認(rèn)0庫炼吴,無法選擇其他數(shù)據(jù)庫本鸣,也就避免了這個(gè)問題。
5硅蹦、【案例】當(dāng)心時(shí)間復(fù)雜度o(n)Redis命令
Redis是單線程的荣德,所以線程安全的。
Redis使用非阻塞IO童芹,并且大部分命令的時(shí)間復(fù)雜度O(1)涮瞻。
使用高耗時(shí)的命令是非常危險(xiǎn)的,會(huì)占用唯一的一個(gè)線程的大量處理時(shí)間假褪,導(dǎo)致所有的請(qǐng)求都被拖慢署咽。
例如:獲取所有set集合中的元素 smembers myset,返回指定Hash中所有的member生音,時(shí)間復(fù)雜度O(N)宁否。
緩存的Value集合變大,當(dāng)高并接口請(qǐng)求時(shí)缀遍,會(huì)從Redis讀取相關(guān)數(shù)據(jù)慕匠,每個(gè)請(qǐng)求讀取的時(shí)間變長(zhǎng),不斷的疊加域醇,導(dǎo)致出現(xiàn)熱點(diǎn)KEY情況台谊,Redis某個(gè)分片處于阻塞,CPU使用率達(dá)到100%譬挚。
6锅铅、【案例】緩存熱key
在Redis中,訪問頻率高的key稱為熱點(diǎn)key减宣,當(dāng)某一熱點(diǎn)key的請(qǐng)求到Server主機(jī)時(shí)盐须,由于請(qǐng)求量特別大,導(dǎo)致主機(jī)資源不足蚪腋,甚至宕機(jī)丰歌,影響正常的服務(wù)。
熱key問題的產(chǎn)生屉凯,有如下兩種原因:
用戶消費(fèi)的數(shù)據(jù)遠(yuǎn)大于生產(chǎn)的數(shù)據(jù)立帖,比如熱賣商品或秒殺商品、熱點(diǎn)新聞悠砚、熱點(diǎn)評(píng)論等晓勇,這些典型的讀多寫少的場(chǎng)景會(huì)產(chǎn)生熱點(diǎn)問題。
請(qǐng)求分片集中,超過單Server的性能極限绑咱,比如 固定名稱key绰筛,哈希落入一臺(tái)Server,訪問量極大的情況描融,超過Server極限時(shí)铝噩,就會(huì)導(dǎo)致熱點(diǎn)Key問題的產(chǎn)生。
那么在實(shí)際業(yè)務(wù)中窿克,如何識(shí)別到熱點(diǎn)key呢骏庸?
憑借業(yè)務(wù)經(jīng)驗(yàn),進(jìn)行預(yù)估哪些是熱key年叮;
客戶端統(tǒng)計(jì)收集具被,本地統(tǒng)計(jì)或者上報(bào);
如果服務(wù)端有代理層只损,可以在代理層進(jìn)行收集上報(bào)一姿;
當(dāng)我們識(shí)別到熱key,如何解決熱key問題跃惫?
Redis集群擴(kuò)容:增加分片副本叮叹,均衡讀流量;
進(jìn)一步對(duì)熱key進(jìn)行散列爆存,比如將一個(gè)key備份為key1,key2……keyN衬横,同樣的數(shù)據(jù)N個(gè)備份,N個(gè)備份分布到不同分片终蒂,訪問時(shí)可隨機(jī)訪問N個(gè)備份中的一個(gè),進(jìn)一步分擔(dān)讀流量遥诉。
使用二級(jí)緩存拇泣,即本地緩存。
當(dāng)發(fā)現(xiàn)熱key后矮锈,將熱key對(duì)應(yīng)數(shù)據(jù)首先加載到應(yīng)用服務(wù)器本地緩存中霉翔,減少對(duì)Redis的讀請(qǐng)求。
五苞笨、Redis 規(guī)范
1债朵、禁止使用非database 0
說明:
Redis-standalone架構(gòu),禁止使用Redis中的其他database瀑凝。
原由:
為以后業(yè)務(wù)遷移 Redis Cluster 保持兼容性.
多個(gè) database 用 select 切換時(shí)序芦,更消耗CPU資源。
更易自動(dòng)化運(yùn)維管理粤咪,如 scan/dbsize 命令只用于當(dāng)database谚中。
部分 Redis Clients 因線程安全問題,不支持單實(shí)例多 database。
2宪塔、Key設(shè)計(jì)規(guī)范
按業(yè)務(wù)功能命名key前綴磁奖,防止key沖突覆蓋,推薦 用冒號(hào)分隔某筐,例如比搭,業(yè)務(wù)名:表名:id:,如 live:rank:user:weekly:1:202003南誊。
Key的長(zhǎng)度小于30個(gè)字符身诺,Key名字本身是String對(duì)象,Redis硬編碼限制最大長(zhǎng)度512MB弟疆。
在Redis緩存場(chǎng)景戚长,推薦Key都設(shè)置TTL值,保證不使用的Key能被及時(shí)清理或淘汰怠苔。
Key設(shè)計(jì)時(shí)禁止包含特殊字符同廉,如空格、換行柑司、單雙引號(hào)以及其他轉(zhuǎn)義字符迫肖。
3、Value設(shè)計(jì)規(guī)范
單個(gè)Value大小必須控制10KB以內(nèi)攒驰,單實(shí)例鍵個(gè)數(shù)過大蟆湖,可能導(dǎo)致過期鍵的回收不及時(shí)。
set玻粪、hash隅津、list等復(fù)雜數(shù)據(jù)類型,要盡量降低數(shù)據(jù)結(jié)構(gòu)中的元素個(gè)數(shù)劲室,建議個(gè)數(shù)不要超過1000伦仍。
4、關(guān)注命令時(shí)間復(fù)雜度
推薦使用O(1)命令很洋,如get scard等充蓝。
O(N)命令關(guān)注N的數(shù)量,如下命令需要對(duì)N值在業(yè)務(wù)層面做控制喉磁。
hgetall
lrange
smembers
zrange
例如:smember命令時(shí)間復(fù)雜度為O(n)谓苟,當(dāng)n持續(xù)增加時(shí),會(huì)導(dǎo)致 Redis CPU 持續(xù)飆高协怒,阻塞其他命令的執(zhí)行涝焙;
5、Pipeline使用
說明:
Pipeline是Redis批量提交的一種方式,也就是把多個(gè)命令操作建立一次連接發(fā)給Redis去執(zhí)行,會(huì)比循環(huán)的單次提交性能更優(yōu)孕暇。
Redis客戶端執(zhí)行一條命令分4個(gè)過程:發(fā)送命令 -> 命令排隊(duì) ->命令執(zhí)行 -> 返回結(jié)果纱皆。
常用的mget湾趾、mset命令,有效節(jié)約RTT(命令執(zhí)行往返時(shí)間)派草,但hgetall并沒有mhgetall搀缠,是不支持批量操作的。此時(shí)近迁,需要使用Pipeline命令
例如:直播中臺(tái)項(xiàng)目中艺普,需要同時(shí)查詢主播日、周鉴竭、月排行榜歧譬,使用PIPELINE一次提交多個(gè)命令,同時(shí)返回三個(gè)榜單數(shù)據(jù)搏存。
6瑰步、線上禁用命令
- 禁止使用Monitor
禁止生產(chǎn)環(huán)境使用monitor命令,monitor命令在高并發(fā)條件下璧眠,會(huì)存在內(nèi)存暴增和影響Redis性能的隱患
- 禁止使用Keys
keys操作是遍歷所有的key缩焦,如果key非常多的情況下導(dǎo)致慢查詢,會(huì)阻塞其他命令责静。所以禁止使用keys及keys pattern命令袁滥。
建議線上使用scan命令代替keys命令。
- 禁止使用Flushall灾螃、Flushdb
刪除Redis中所有數(shù)據(jù)庫中的所有記錄题翻,并且該命令是原子性的,不會(huì)終止執(zhí)行腰鬼,一旦執(zhí)行嵌赠,將不會(huì)執(zhí)行失敗。
- 禁止使用Save
阻塞當(dāng)前redis服務(wù)器熄赡,直到持久化操作完成為止猾普,對(duì)于內(nèi)存較大的實(shí)例會(huì)造成長(zhǎng)時(shí)間的阻塞。
- BGREWRITEAOF
手動(dòng)AOF本谜,手動(dòng)持久化對(duì)于內(nèi)存較大的實(shí)例會(huì)造成長(zhǎng)時(shí)間的阻塞。
- Config
Config是客戶端配置方式偎窘,不利于Redis運(yùn)維乌助。建議在Redis配置文件中設(shè)置。
六陌知、Redis 監(jiān)控
1他托、慢查詢
方法一:slowlog獲取慢查詢?nèi)罩?/p>
127.0.0.1:{port}> slowlog get 5
(integer) 47
(integer) 1533810300
(integer) 175833
"DEL"
"spring:session:expirations:1533810300000"
(integer) 46
(integer) 1533810300
(integer) 117400
- "SMEMBERS"
方法二:更全面的慢查詢可以通過CacheCloud工具監(jiān)控。
路徑:"應(yīng)用列表"-點(diǎn)擊相關(guān)應(yīng)用名-點(diǎn)擊"慢查詢"Tab頁仆葡。
點(diǎn)擊"慢查詢"赏参,重點(diǎn)關(guān)注慢查詢個(gè)數(shù)及相關(guān)命令志笼。
2、監(jiān)控Redis實(shí)例綁定的CPU核心使用率
由于Redis是單線程把篓,重點(diǎn)監(jiān)控Redis實(shí)例綁定的CPU核心使用率纫溃。
一般CPU資源使用率為10%左右,如果使用率高于20%時(shí)韧掩,考慮是否使用了RDB持久化紊浩。
3、Redis分片負(fù)載均衡
當(dāng)前redis-cluster架構(gòu)模式疗锐,3個(gè)master和3個(gè)Slave組成的集群坊谁,關(guān)注 Redis-cluster每個(gè)分片requests流量均衡情況;
通過命令獲然:redis-cli -p{port} -h{host} --stat
一般情況口芍,超過12W需要告警。
4雇卷、關(guān)注大key BigKey
通過Redis提供的工具鬓椭,redis-cli定時(shí)掃描相應(yīng)Redis大Key,進(jìn)行優(yōu)化聋庵。
具體命令如下:redis-cli -h 127.0.0.1 -p {port} --bigkeys或 redis-memory-for-key -s {IP} -p {port} XXX_KEY
一般超過10K為大key膘融,需要重點(diǎn)關(guān)注,建議從業(yè)務(wù)層面優(yōu)化祭玉。
5氧映、監(jiān)控Redis占用內(nèi)存大小
Info memory 命令查看,避免在高并發(fā)場(chǎng)景下脱货,由于分配的MaxMemory被耗盡岛都,帶來的性能問題。
重點(diǎn)關(guān)注 used_memory_human 配置項(xiàng)對(duì)應(yīng)的value值振峻,增量過高時(shí)臼疫,需要重點(diǎn)評(píng)估。
七扣孟、總結(jié)
結(jié)合具體業(yè)務(wù)特性烫堤,合理評(píng)估Redis所需內(nèi)存容量、選擇數(shù)據(jù)類型凤价、設(shè)置單key大小鸽斟,才能更好地服務(wù)于業(yè)務(wù),為業(yè)務(wù)提供高性能的保障利诺。
作者:Jessica Chen