Redis應(yīng)用

1.分布式鎖

RedissonLock分布式鎖
1)加鎖是通過Lua腳本實(shí)現(xiàn)的彰阴,如果分布式鎖不存在瘾敢,則會(huì)通過hset 分布式鎖 uuid+threadid標(biāo)識(shí) 1進(jìn)行加鎖,并設(shè)超時(shí)時(shí)間30s
2)如果是重入加鎖尿这,則將加鎖次數(shù)+1
3)如果加鎖失敗簇抵,則返回該鎖到過期還要多長(zhǎng)時(shí)間ttl,然后再嘗試加鎖射众,最后使用信號(hào)量指定等待ttl時(shí)間
4)加鎖成功后碟摆,會(huì)有一個(gè)續(xù)命邏輯,每隔10s重新設(shè)置超時(shí)時(shí)間為30s
5)對(duì)于加鎖失敗的客戶端叨橱,會(huì)通過Redis發(fā)布訂閱機(jī)制典蜕,訂閱分布式鎖的Channel。分布式鎖釋放鎖的時(shí)候罗洗,會(huì)發(fā)布喚醒這些客戶端讓其重新去獲取分布式鎖愉舔。

2.緩存

2.1 緩存穿透

緩存穿透是指查詢一個(gè)根本不存在的數(shù)據(jù), 緩存層和存儲(chǔ)層都不會(huì)命中伙菜, 通常出于容錯(cuò)的考慮轩缤, 如果從存儲(chǔ)層查不到數(shù)據(jù)則不寫入緩存層。

緩存穿透將導(dǎo)致不存在的數(shù)據(jù)每次請(qǐng)求都要到存儲(chǔ)層去查詢贩绕, 失去了緩存保護(hù)后端存儲(chǔ)的意義火的。

造成緩存穿透的基本原因有兩個(gè):
第一, 自身業(yè)務(wù)代碼或者數(shù)據(jù)出現(xiàn)問題淑倾。
第二卫玖, 一些惡意攻擊、 爬蟲等造成大量空命中踊淳。

緩存穿透問題解決方案:

1、緩存空對(duì)象
2、布隆過濾器

對(duì)于惡意攻擊迂尝,向服務(wù)器請(qǐng)求大量不存在的數(shù)據(jù)造成的緩存穿透脱茉,還可以用布隆過濾器先做一次過濾,對(duì)于不存在的數(shù)據(jù)布隆過濾器一般都能夠過濾掉垄开,不讓請(qǐng)求再往后端發(fā)送琴许。當(dāng)布隆過濾器說某個(gè)值存在時(shí),這個(gè)值可能不存在溉躲;當(dāng)它說不存在時(shí)榜田,那就肯定不存在。

布隆過濾器就是一個(gè)大型的位數(shù)組和幾個(gè)不一樣的無(wú)偏 hash 函數(shù)锻梳。所謂無(wú)偏就是能夠把元素的 hash 值算得比較均勻箭券。

向布隆過濾器中添加 key 時(shí),會(huì)使用多個(gè) hash 函數(shù)對(duì) key 進(jìn)行 hash 算得一個(gè)整數(shù)索引值然后對(duì)位數(shù)組長(zhǎng)度進(jìn)行取模運(yùn)算得到一個(gè)位置疑枯,每個(gè) hash 函數(shù)都會(huì)算得一個(gè)不同的位置辩块。再把位數(shù)組的這幾個(gè)位置都置為 1 就完成了 add 操作。

向布隆過濾器詢問 key 是否存在時(shí)荆永,跟 add 一樣废亭,也會(huì)把 hash 的幾個(gè)位置都算出來(lái),看看位數(shù)組中這幾個(gè)位置是否都為 1具钥,只要有一個(gè)位為 0豆村,那么說明布隆過濾器中這個(gè)key 不存在。如果都是 1骂删,這并不能說明這個(gè) key 就一定存在掌动,只是極有可能存在,因?yàn)檫@些位被置為 1 可能是因?yàn)槠渌?key 存在所致桃漾。如果這個(gè)位數(shù)組長(zhǎng)度比較大坏匪,存在概率就會(huì)很大,如果這個(gè)位數(shù)組長(zhǎng)度比較小撬统,存在概率就會(huì)降低适滓。

這種方法適用于數(shù)據(jù)命中不高、 數(shù)據(jù)相對(duì)固定恋追、 實(shí)時(shí)性低(通常是數(shù)據(jù)集較大) 的應(yīng)用場(chǎng)景凭迹, 代碼維護(hù)較為復(fù)雜, 但是緩存空間占用很少苦囱。

注意:布隆過濾器不能刪除數(shù)據(jù)嗅绸,如果要?jiǎng)h除得重新初始化數(shù)據(jù)。

2.2 緩存失效(擊穿)

由于大批量緩存在同一時(shí)間失效可能導(dǎo)致大量請(qǐng)求同時(shí)穿透緩存直達(dá)數(shù)據(jù)庫(kù)撕彤,可能會(huì)造成數(shù)據(jù)庫(kù)瞬間壓力過大甚至掛掉鱼鸠,對(duì)于這種情況我們?cè)谂吭黾泳彺鏁r(shí)最好將這一批數(shù)據(jù)的緩存過期時(shí)間設(shè)置為一個(gè)時(shí)間段內(nèi)的不同時(shí)間猛拴。

2.3 緩存雪崩

緩存雪崩指的是緩存層支撐不住或宕掉后, 流量會(huì)像奔逃的野牛一樣蚀狰, 打向后端存儲(chǔ)層愉昆。

由于緩存層承載著大量請(qǐng)求, 有效地保護(hù)了存儲(chǔ)層麻蹋, 但是如果緩存層由于某些原因不能提供服務(wù)(比如超大并發(fā)過來(lái)跛溉,緩存層支撐不住,或者由于緩存設(shè)計(jì)不好扮授,類似大量請(qǐng)求訪問bigkey芳室,導(dǎo)致緩存能支撐的并發(fā)急劇下降), 于是大量請(qǐng)求都會(huì)打到存儲(chǔ)層刹勃, 存儲(chǔ)層的調(diào)用量會(huì)暴增堪侯, 造成存儲(chǔ)層也會(huì)級(jí)聯(lián)宕機(jī)的情況。

預(yù)防和解決緩存雪崩問題深夯, 可以從以下三個(gè)方面進(jìn)行著手抖格。

1) 保證緩存層服務(wù)高可用性,比如使用Redis Sentinel或Redis Cluster咕晋。

2) 依賴隔離組件為后端限流熔斷并降級(jí)雹拄。比如使用Sentinel或Hystrix限流降級(jí)組件。

比如服務(wù)降級(jí)掌呜,我們可以針對(duì)不同的數(shù)據(jù)采取不同的處理方式滓玖。當(dāng)業(yè)務(wù)應(yīng)用訪問的是非核心數(shù)據(jù)(例如電商商品屬性,用戶信息等)時(shí)质蕉,暫時(shí)停止從緩存中查詢這些數(shù)據(jù)势篡,而是直接返回預(yù)定義的默認(rèn)降級(jí)信息、空值或是錯(cuò)誤提示信息模暗;當(dāng)業(yè)務(wù)應(yīng)用訪問的是核心數(shù)據(jù)(例如電商商品庫(kù)存)時(shí)禁悠,仍然允許查詢緩存,如果緩存缺失兑宇,也可以繼續(xù)通過數(shù)據(jù)庫(kù)讀取碍侦。

3) 提前演練。 在項(xiàng)目上線前隶糕, 演練緩存層宕掉后瓷产, 應(yīng)用以及后端的負(fù)載情況以及可能出現(xiàn)的問題, 在此基礎(chǔ)上做一些預(yù)案設(shè)定枚驻。

熱點(diǎn)緩存key重建優(yōu)化

開發(fā)人員使用“緩存+過期時(shí)間”的策略既可以加速數(shù)據(jù)讀寫濒旦, 又保證數(shù)據(jù)的定期更新, 這種模式基本能夠滿足絕大部分需求再登。 但是有兩個(gè)問題如果同時(shí)出現(xiàn)尔邓, 可能就會(huì)對(duì)應(yīng)用造成致命的危害:

  • 當(dāng)前key是一個(gè)熱點(diǎn)key(例如一個(gè)熱門的娛樂新聞)晾剖,并發(fā)量非常大。
  • 重建緩存不能在短時(shí)間完成铃拇, 可能是一個(gè)復(fù)雜計(jì)算钞瀑, 例如復(fù)雜的SQL、 多次IO慷荔、 多個(gè)依賴等。

在緩存失效的瞬間缠俺, 有大量線程來(lái)重建緩存显晶, 造成后端負(fù)載加大, 甚至可能會(huì)讓應(yīng)用崩潰壹士。

要解決這個(gè)問題主要就是要避免大量線程同時(shí)重建緩存磷雇。

我們可以利用互斥鎖來(lái)解決,此方法只允許一個(gè)線程重建緩存躏救, 其他線程等待重建緩存的線程執(zhí)行完唯笙, 重新從緩存獲取數(shù)據(jù)即可。

2.4 緩存與數(shù)據(jù)庫(kù)雙寫不一致

在大并發(fā)下盒使,同時(shí)操作數(shù)據(jù)庫(kù)與緩存會(huì)存在數(shù)據(jù)不一致性問題

1崩掘、雙寫不一致情況


2、讀寫并發(fā)不一致


解決方案:

1少办、對(duì)于并發(fā)幾率很小的數(shù)據(jù)(如個(gè)人維度的訂單數(shù)據(jù)苞慢、用戶數(shù)據(jù)等),這種幾乎不用考慮這個(gè)問題英妓,很少會(huì)發(fā)生緩存不一致挽放,可以給緩存數(shù)據(jù)加上過期時(shí)間,每隔一段時(shí)間觸發(fā)讀的主動(dòng)更新即可蔓纠。

2辑畦、就算并發(fā)很高,如果業(yè)務(wù)上能容忍短時(shí)間的緩存數(shù)據(jù)不一致(如商品名稱腿倚,商品分類菜單等)纯出,緩存加上過期時(shí)間依然可以解決大部分業(yè)務(wù)對(duì)于緩存的要求。

3猴誊、如果不能容忍緩存數(shù)據(jù)不一致潦刃,可以通過加分布式讀寫鎖保證并發(fā)讀寫或?qū)憣懙臅r(shí)候按順序排好隊(duì),讀讀的時(shí)候相當(dāng)于無(wú)鎖懈叹。

4乖杠、也可以用阿里開源的canal通過監(jiān)聽數(shù)據(jù)庫(kù)的binlog日志及時(shí)的去修改緩存,但是引入了新的中間件澄成,增加了系統(tǒng)的復(fù)雜度胧洒。

總結(jié):

以上我們針對(duì)的都是讀多寫少的情況加入緩存提高性能畏吓,如果寫多讀多的情況又不能容忍緩存數(shù)據(jù)不一致,那就沒必要加緩存了卫漫,可以直接操作數(shù)據(jù)庫(kù)菲饼。當(dāng)然,如果數(shù)據(jù)庫(kù)抗不住壓力列赎,還可以把緩存作為數(shù)據(jù)讀寫的主存儲(chǔ)宏悦,異步將數(shù)據(jù)同步到數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)只是作為數(shù)據(jù)的備份包吝。

放入緩存的數(shù)據(jù)應(yīng)該是對(duì)實(shí)時(shí)性饼煞、一致性要求不是很高的數(shù)據(jù)。切記不要為了用緩存诗越,同時(shí)又要保證絕對(duì)的一致性做大量的過度設(shè)計(jì)和控制砖瞧,增加系統(tǒng)復(fù)雜性!

3.開發(fā)規(guī)范

3.1 鍵值設(shè)計(jì)

3.1.1 key名設(shè)計(jì)

(1)【建議】: 可讀性和可管理性
以業(yè)務(wù)名(或數(shù)據(jù)庫(kù)名)為前綴(防止key沖突)嚷狞,用冒號(hào)分隔块促,比如業(yè)務(wù)名:表名:id
trade:order:1

(2)【建議】:簡(jiǎn)潔性
保證語(yǔ)義的前提下,控制key的長(zhǎng)度床未,當(dāng)key較多時(shí)竭翠,內(nèi)存占用也不容忽視,例如:
user:{uid}:friends:messages:{mid} 簡(jiǎn)化為 u:{uid}:fr:m:{mid}

(3)【強(qiáng)制】:不要包含特殊字符
反例:包含空格即硼、換行逃片、單雙引號(hào)以及其他轉(zhuǎn)義字符

3.1.2 value設(shè)計(jì)

(1)【強(qiáng)制】:拒絕bigkey(防止網(wǎng)卡流量、慢查詢)
在Redis中只酥,一個(gè)字符串最大512MB褥实,一個(gè)二級(jí)數(shù)據(jù)結(jié)構(gòu)(例如hash、list裂允、set损离、zset)可以存儲(chǔ)大約40億個(gè)(2^32-1)個(gè)元素,但實(shí)際中如果下面兩種情況绝编,我就會(huì)認(rèn)為它是bigkey僻澎。
字符串類型:它的big體現(xiàn)在單個(gè)value值很大,一般認(rèn)為超過10KB就是bigkey十饥。
非字符串類型:哈希窟勃、列表、集合逗堵、有序集合秉氧,它們的big體現(xiàn)在元素個(gè)數(shù)太多。
一般來(lái)說蜒秤,string類型控制在10KB以內(nèi)汁咏,hash亚斋、list、set攘滩、zset元素個(gè)數(shù)不要超過5000帅刊。
反例:一個(gè)包含200萬(wàn)個(gè)元素的list。
非字符串的bigkey漂问,不要使用del刪除赖瞒,使用hscan、sscan级解、zscan方式漸進(jìn)式刪除冒黑,同時(shí)要注意防止bigkey過期時(shí)間自動(dòng)刪除問題(例如一個(gè)200萬(wàn)的zset設(shè)置1小時(shí)過期,會(huì)觸發(fā)del操作勤哗,造成阻塞)
bigkey的危害:

  • 1.導(dǎo)致redis阻塞
  • 2.網(wǎng)絡(luò)擁塞
    bigkey也就意味著每次獲取要產(chǎn)生的網(wǎng)絡(luò)流量較大,假設(shè)一個(gè)bigkey為1MB掩驱,客戶端每秒訪問量為1000芒划,那么每秒產(chǎn)生1000MB的流量,對(duì)于普通的千兆網(wǎng)卡(按照字節(jié)算是128MB/s)的服務(wù)器來(lái)說簡(jiǎn)直是滅頂之災(zāi)欧穴,而且一般服務(wù)器會(huì)采用單機(jī)多實(shí)例的方式來(lái)部署民逼,也就是說一個(gè)bigkey可能會(huì)對(duì)其他實(shí)例也造成影響,其后果不堪設(shè)想涮帘。
    1. 過期刪除
      有個(gè)bigkey拼苍,它安分守己(只執(zhí)行簡(jiǎn)單的命令,例如hget调缨、lpop疮鲫、zscore等),但它設(shè)置了過期時(shí)間弦叶,當(dāng)它過期后俊犯,會(huì)被刪除,如果沒有使用Redis 4.0的過期異步刪除(lazyfree-lazy-expire yes)伤哺,就會(huì)存在阻塞Redis的可能性燕侠。
      bigkey的產(chǎn)生:
      一般來(lái)說,bigkey的產(chǎn)生都是由于程序設(shè)計(jì)不當(dāng)立莉,或者對(duì)于數(shù)據(jù)規(guī)模預(yù)料不清楚造成的绢彤,來(lái)看幾個(gè)例子:
      (1) 社交類:粉絲列表,如果某些明星或者大v不精心設(shè)計(jì)下蜓耻,必是bigkey茫舶。
      (2) 統(tǒng)計(jì)類:例如按天存儲(chǔ)某項(xiàng)功能或者網(wǎng)站的用戶集合,除非沒幾個(gè)人用媒熊,否則必是bigkey奇适。
      (3) 緩存類:將數(shù)據(jù)從數(shù)據(jù)庫(kù)load出來(lái)序列化放到Redis里坟比,這個(gè)方式非常常用,但有兩個(gè)地方需要注意嚷往,第一葛账,是不是有必要把所有字段都緩存;第二皮仁,有沒有相關(guān)關(guān)聯(lián)的數(shù)據(jù)籍琳,有的同學(xué)為了圖方便把相關(guān)數(shù)據(jù)都存一個(gè)key下,產(chǎn)生bigkey贷祈。

如何優(yōu)化bigkey


    1. big list: list1趋急、list2、...listN
      big hash:可以講數(shù)據(jù)分段存儲(chǔ)势誊,比如一個(gè)大的key呜达,假設(shè)存了1百萬(wàn)的用戶數(shù)據(jù),可以拆分成200個(gè)key粟耻,每個(gè)key下面存放5000個(gè)用戶數(shù)據(jù)
    1. 如果bigkey不可避免查近,也要思考一下要不要每次把所有元素都取出來(lái)(例如有時(shí)候僅僅需要hmget,而不是hgetall)挤忙,刪除也是一樣霜威,盡量使用優(yōu)雅的方式來(lái)處理。

(2)【推薦】:選擇適合的數(shù)據(jù)類型册烈。
例如:實(shí)體類型(要合理控制和使用數(shù)據(jù)結(jié)構(gòu)戈泼,但也要注意節(jié)省內(nèi)存和性能之間的平衡)
反例:
set user:1:name tom
set user:1:age 19
set user:1:favor football
正例:
hmset user:1 name tom age 19 favor football

3.【推薦】:控制key的生命周期,redis不是垃圾桶赏僧。
建議使用expire設(shè)置過期時(shí)間(條件允許可以打散過期時(shí)間大猛,防止集中過期)。

3.2 命令使用

1.【推薦】 O(N)命令關(guān)注N的數(shù)量
例如hgetall次哈、lrange胎署、smembers、zrange窑滞、sinter等并非不能使用琼牧,但是需要明確N的值。有遍歷的需求可以使用hscan哀卫、sscan巨坊、zscan代替。

2.【推薦】:禁用命令
禁止線上使用keys此改、flushall趾撵、flushdb等,通過redis的rename機(jī)制禁掉命令,或者使用scan的方式漸進(jìn)式處理占调。

3.【推薦】合理使用select
redis的多數(shù)據(jù)庫(kù)較弱暂题,使用數(shù)字進(jìn)行區(qū)分,很多客戶端支持較差究珊,同時(shí)多業(yè)務(wù)用多數(shù)據(jù)庫(kù)實(shí)際還是單線程處理薪者,會(huì)有干擾。

4.【推薦】使用批量操作提高效率
原生命令:例如mget剿涮、mset言津。
非原生命令:可以使用pipeline提高效率。
但要注意控制一次批量操作的元素個(gè)數(shù)(例如500以內(nèi)取试,實(shí)際也和元素字節(jié)數(shù)有關(guān))悬槽。
注意兩者不同:

  • 1)原生命令是原子操作,pipeline是非原子操作瞬浓。
  • 2)pipeline可以打包不同的命令初婆,原生命令做不到
  • 3)pipeline需要客戶端和服務(wù)端同時(shí)支持。

5.【建議】Redis事務(wù)功能較弱猿棉,不建議過多使用烟逊,可以用lua替代

3.3 客戶端使用

1.【推薦】
避免多個(gè)應(yīng)用使用一個(gè)Redis實(shí)例
正例:不相干的業(yè)務(wù)拆分,公共數(shù)據(jù)做服務(wù)化铺根。

2.【推薦】
使用帶有連接池的數(shù)據(jù)庫(kù),可以有效控制連接乔宿,同時(shí)提高效率位迂。

優(yōu)化建議:
1)maxTotal:最大連接數(shù),早期的版本叫maxActive
實(shí)際上這個(gè)是一個(gè)很難回答的問題详瑞,考慮的因素比較多:
業(yè)務(wù)希望Redis并發(fā)量
客戶端執(zhí)行命令時(shí)間
Redis資源:例如 nodes(例如應(yīng)用個(gè)數(shù)) * maxTotal 是不能超過redis的最大連接數(shù)maxclients掂林。
資源開銷:例如雖然希望控制空閑連接(連接池此刻可馬上使用的連接),但是不希望因?yàn)檫B接池的頻繁釋放創(chuàng)建連接造成不必靠開銷坝橡。
以一個(gè)例子說明泻帮,假設(shè):
一次命令時(shí)間(borrow|return resource + Jedis執(zhí)行命令(含網(wǎng)絡(luò)) )的平均耗時(shí)約為1ms,一個(gè)連接的QPS大約是1000
業(yè)務(wù)期望的QPS是50000
那么理論上需要的資源池大小是50000 / 1000 = 50個(gè)计寇。但事實(shí)上這是個(gè)理論值锣杂,還要考慮到要比理論值預(yù)留一些資源,通常來(lái)講maxTotal可以比理論值大一些番宁。
但這個(gè)值不是越大越好元莫,一方面連接太多占用客戶端和服務(wù)端資源,另一方面對(duì)于Redis這種高QPS的服務(wù)器蝶押,一個(gè)大命令的阻塞即使設(shè)置再大資源池仍然會(huì)無(wú)濟(jì)于事踱蠢。

2)maxIdle和minIdle
maxIdle實(shí)際上才是業(yè)務(wù)需要的最大連接數(shù),maxTotal是為了給出余量棋电,所以maxIdle不要設(shè)置過小茎截,否則會(huì)有new Jedis(新連接)開銷苇侵。
連接池的最佳性能是maxTotal = maxIdle,這樣就避免連接池伸縮帶來(lái)的性能干擾企锌。但是如果并發(fā)量不大或者maxTotal設(shè)置過高榆浓,會(huì)導(dǎo)致不必要的連接資源浪費(fèi)。一般推薦maxIdle可以設(shè)置為按上面的業(yè)務(wù)期望QPS計(jì)算出來(lái)的理論連接數(shù)霎俩,maxTotal可以再放大一倍哀军。
minIdle(最小空閑連接數(shù)),與其說是最小空閑連接數(shù)打却,不如說是"至少需要保持的空閑連接數(shù)"杉适,在使用連接的過程中,如果連接數(shù)超過了minIdle柳击,那么繼續(xù)建立連接猿推,如果超過了maxIdle,當(dāng)超過的連接執(zhí)行完業(yè)務(wù)后會(huì)慢慢被移出連接池釋放掉捌肴。
如果系統(tǒng)啟動(dòng)完馬上就會(huì)有很多的請(qǐng)求過來(lái)蹬叭,那么可以給redis連接池做預(yù)熱,比如快速的創(chuàng)建一些redis連接状知,執(zhí)行簡(jiǎn)單命令秽五,類似ping(),快速的將連接池里的空閑連接提升到minIdle的數(shù)量饥悴。

總之坦喘,要根據(jù)實(shí)際系統(tǒng)的QPS和調(diào)用redis客戶端的規(guī)模整體評(píng)估每個(gè)節(jié)點(diǎn)所使用的連接池大小。

3.【建議】
高并發(fā)下建議客戶端添加熔斷功能(例如sentinel西设、hystrix)

4.【推薦】
設(shè)置合理的密碼瓣铣,如有必要可以使用SSL加密訪問

5.【建議】
Redis對(duì)于過期鍵有三種清除策略:
被動(dòng)刪除:當(dāng)讀/寫一個(gè)已經(jīng)過期的key時(shí),會(huì)觸發(fā)惰性刪除策略贷揽,直接刪除掉這個(gè)過期key
主動(dòng)刪除:由于惰性刪除策略無(wú)法保證冷數(shù)據(jù)被及時(shí)刪掉棠笑,所以Redis會(huì)定期(默認(rèn)每100ms)主動(dòng)淘汰一批已過期的key,這里的一批只是部分過期key禽绪,所以可能會(huì)出現(xiàn)部分key已經(jīng)過期但還沒有被清理掉的情況蓖救,導(dǎo)致內(nèi)存并沒有被釋放
當(dāng)前已用內(nèi)存超過maxmemory限定時(shí),觸發(fā)主動(dòng)清理策略
主動(dòng)清理策略在Redis 4.0 之前一共實(shí)現(xiàn)了 6 種內(nèi)存淘汰策略丐一,在 4.0 之后藻糖,又增加了 2 種策略,總共8種:
a) 針對(duì)設(shè)置了過期時(shí)間的key做處理:
volatile-ttl:在篩選時(shí)库车,會(huì)針對(duì)設(shè)置了過期時(shí)間的鍵值對(duì)巨柒,根據(jù)過期時(shí)間的先后進(jìn)行刪除,越早過期的越先被刪除。
volatile-random:就像它的名稱一樣洋满,在設(shè)置了過期時(shí)間的鍵值對(duì)中晶乔,進(jìn)行隨機(jī)刪除。
volatile-lru:會(huì)使用 LRU 算法篩選設(shè)置了過期時(shí)間的鍵值對(duì)刪除牺勾。
volatile-lfu:會(huì)使用 LFU 算法篩選設(shè)置了過期時(shí)間的鍵值對(duì)刪除畅哑。
b) 針對(duì)所有的key做處理:
allkeys-random:從所有鍵值對(duì)中隨機(jī)選擇并刪除數(shù)據(jù)寝衫。
allkeys-lru:使用 LRU 算法在所有數(shù)據(jù)中進(jìn)行篩選刪除。
allkeys-lfu:使用 LFU 算法在所有數(shù)據(jù)中進(jìn)行篩選刪除。
c) 不處理:
noeviction:不會(huì)剔除任何數(shù)據(jù)渗钉,拒絕所有寫入操作并返回客戶端錯(cuò)誤信息"(error) OOM command not allowed when used memory"瞒瘸,此時(shí)Redis只響應(yīng)讀操作双戳。

LRU 算法(Least Recently Used戏蔑,最近最少使用)
淘汰很久沒被訪問過的數(shù)據(jù),以最近一次訪問時(shí)間作為參考柠硕。

LFU 算法(Least Frequently Used工禾,最不經(jīng)常使用)
淘汰最近一段時(shí)間被訪問次數(shù)最少的數(shù)據(jù),以次數(shù)作為參考蝗柔。

當(dāng)存在熱點(diǎn)數(shù)據(jù)時(shí)闻葵,LRU的效率很好,但偶發(fā)性的癣丧、周期性的批量操作會(huì)導(dǎo)致LRU命中率急劇下降槽畔,緩存污染情況比較嚴(yán)重。這時(shí)使用LFU可能更好點(diǎn)胁编。

根據(jù)自身業(yè)務(wù)類型竟痰,配置好maxmemory-policy(默認(rèn)是noeviction),推薦使用volatile-lru掏呼。如果不設(shè)置最大內(nèi)存,當(dāng) Redis 內(nèi)存超出物理內(nèi)存限制時(shí)铅檩,內(nèi)存的數(shù)據(jù)會(huì)開始和磁盤產(chǎn)生頻繁的交換 (swap)憎夷,會(huì)讓 Redis 的性能急劇下降。
當(dāng)Redis運(yùn)行在主從模式時(shí)昧旨,只有主結(jié)點(diǎn)才會(huì)執(zhí)行過期刪除策略拾给,然后把刪除操作”del key”同步到從結(jié)點(diǎn)刪除數(shù)據(jù)。

3.4 系統(tǒng)內(nèi)核參數(shù)優(yōu)化

vm.swapiness
swap對(duì)于操作系統(tǒng)來(lái)說比較重要兔沃,當(dāng)物理內(nèi)存不足時(shí)蒋得,可以將一部分內(nèi)存頁(yè)進(jìn)行swap到硬盤上,以解燃眉之急乒疏。但世界上沒有免費(fèi)午餐额衙,swap空間由硬盤提供,對(duì)于需要高并發(fā)、高吞吐的應(yīng)用來(lái)說窍侧,磁盤IO通常會(huì)成為系統(tǒng)瓶頸县踢。在Linux中,并不是要等到所有物理內(nèi)存都使用完才會(huì)使用到swap伟件,系統(tǒng)參數(shù)swppiness會(huì)決定操作系統(tǒng)使用swap的傾向程度硼啤。swappiness的取值范圍是0~100,swappiness的值越大斧账,說明操作系統(tǒng)可能使用swap的概率越高谴返,swappiness值越低,表示操作系統(tǒng)更加傾向于使用物理內(nèi)存咧织。swappiness的取值越大嗓袱,說明操作系統(tǒng)可能使用swap的概率越高,越低則越傾向于使用物理內(nèi)存拯爽。
如果linux內(nèi)核版本<3.5索抓,那么swapiness設(shè)置為0,這樣系統(tǒng)寧愿swap也不會(huì)oom killer(殺掉進(jìn)程)
如果linux內(nèi)核版本>=3.5毯炮,那么swapiness設(shè)置為1逼肯,這樣系統(tǒng)寧愿swap也不會(huì)oom killer
一般需要保證redis不會(huì)被kill掉:
cat /proc/version #查看linux內(nèi)核版本
echo 1 > /proc/sys/vm/swappiness
echo vm.swapiness=1 >> /etc/sysctl.conf
PS:OOM killer 機(jī)制是指Linux操作系統(tǒng)發(fā)現(xiàn)可用內(nèi)存不足時(shí),強(qiáng)制殺死一些用戶進(jìn)程(非內(nèi)核進(jìn)程)桃煎,來(lái)保證系統(tǒng)有足夠的可用內(nèi)存進(jìn)行分配篮幢。

vm.overcommit_memory(默認(rèn)0)
0:表示內(nèi)核將檢查是否有足夠的可用物理內(nèi)存(實(shí)際不一定用滿)供應(yīng)用進(jìn)程使用;如果有足夠的可用物理內(nèi)存为迈,內(nèi)存申請(qǐng)?jiān)试S三椿;否則,內(nèi)存申請(qǐng)失敗葫辐,并把錯(cuò)誤返回給應(yīng)用進(jìn)程 
1:表示內(nèi)核允許分配所有的物理內(nèi)存搜锰,而不管當(dāng)前的內(nèi)存狀態(tài)如何
如果是0的話,可能導(dǎo)致類似fork等操作執(zhí)行失敗耿战,申請(qǐng)不到足夠的內(nèi)存空間
Redis建議把這個(gè)值設(shè)置為1蛋叼,就是為了讓fork操作能夠在低內(nèi)存下也執(zhí)行成功。
cat /proc/sys/vm/overcommit_memory
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1

合理設(shè)置文件句柄數(shù)
操作系統(tǒng)進(jìn)程試圖打開一個(gè)文件(或者叫句柄)剂陡,但是現(xiàn)在進(jìn)程打開的句柄數(shù)已經(jīng)達(dá)到了上限狈涮,繼續(xù)打開會(huì)報(bào)錯(cuò):“Too many open files”
ulimit -a #查看系統(tǒng)文件句柄數(shù),看open files那項(xiàng)
ulimit -n 65535 #設(shè)置系統(tǒng)文件句柄數(shù)

慢查詢?nèi)罩荆簊lowlog
Redis慢日志命令說明:
config get slow* #查詢有關(guān)慢日志的配置信息
config set slowlog-log-slower-than 20000 #設(shè)置慢日志使時(shí)間閾值,單位微秒鸭栖,此處為20毫秒歌馍,即超過20毫秒的操作都會(huì)記錄下來(lái),生產(chǎn)環(huán)境建議設(shè)置1000晕鹊,也就是1ms松却,這樣理論上redis并發(fā)至少達(dá)到1000暴浦,如果要求單機(jī)并發(fā)達(dá)到1萬(wàn)以上,這個(gè)值可以設(shè)置為100
config set slowlog-max-len 1024 #設(shè)置慢日志記錄保存數(shù)量玻褪,如果保存數(shù)量已滿肉渴,會(huì)刪除最早的記錄,最新的記錄追加進(jìn)來(lái)带射。記錄慢查詢?nèi)罩緯r(shí)Redis會(huì)對(duì)長(zhǎng)命令做截?cái)嗖僮魍妫⒉粫?huì)占用大量?jī)?nèi)存,建議設(shè)置稍大些窟社,防止丟失日志
config rewrite #將服務(wù)器當(dāng)前所使用的配置保存到redis.conf
slowlog len #獲取慢查詢?nèi)罩玖斜淼漠?dāng)前長(zhǎng)度
slowlog get 5 #獲取最新的5條慢查詢?nèi)罩救住B樵內(nèi)罩居伤膫€(gè)屬性組成:標(biāo)識(shí)ID,發(fā)生時(shí)間戳灿里,命令耗時(shí)关炼,執(zhí)行命令和參數(shù)
slowlog reset #重置慢查詢?nèi)罩?/p>

4.Redis為什么這么快?

Redis 基于 Reactor 模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器 - 文件事件處理器(file event handler匣吊,后文簡(jiǎn)稱為 FEH)儒拂,而該處理器又是單線程的,所以redis設(shè)計(jì)為單線程模型色鸳。
采用I/O多路復(fù)用同時(shí)監(jiān)聽多個(gè)socket社痛,根據(jù)socket當(dāng)前執(zhí)行的事件來(lái)為 socket 選擇對(duì)應(yīng)的事件處理器。
當(dāng)被監(jiān)聽的socket準(zhǔn)備好執(zhí)行accept命雀、read蒜哀、write、close等操作時(shí)吏砂,和操作對(duì)應(yīng)的文件事件就會(huì)產(chǎn)生撵儿,這時(shí)FEH就會(huì)調(diào)用socket之前關(guān)聯(lián)好的事件處理器來(lái)處理對(duì)應(yīng)事件。
所以雖然FEH是單線程運(yùn)行狐血,但通過I/O多路復(fù)用監(jiān)聽多個(gè)socket淀歇,不僅實(shí)現(xiàn)高性能的網(wǎng)絡(luò)通信模型,又能和 Redis 服務(wù)器中其它同樣單線程運(yùn)行的模塊交互匈织,保證了Redis內(nèi)部單線程模型的簡(jiǎn)潔設(shè)計(jì)房匆。

4.1 Redis6中的多線程

1. Redis6.0之前的版本真的是單線程嗎?

Redis在處理客戶端的請(qǐng)求時(shí)报亩,包括獲取 (socket 讀)、解析井氢、執(zhí)行弦追、內(nèi)容返回 (socket 寫) 等都由一個(gè)順序串行的主線程處理,這就是所謂的“單線程”花竞。但如果嚴(yán)格來(lái)講從Redis4.0之后并不是單線程劲件,除了主線程外掸哑,它也有后臺(tái)線程在處理一些較為緩慢的操作,例如清理臟數(shù)據(jù)零远、無(wú)用連接的釋放苗分、大 key 的刪除等等。

2. Redis6.0之前為什么一直不使用多線程牵辣?

官方曾做過類似問題的回復(fù):使用Redis時(shí)摔癣,幾乎不存在CPU成為瓶頸的情況, Redis主要受限于內(nèi)存和網(wǎng)絡(luò)纬向。例如在一個(gè)普通的Linux系統(tǒng)上择浊,Redis通過使用pipelining每秒可以處理100萬(wàn)個(gè)請(qǐng)求,所以如果應(yīng)用程序主要使用O(N)或O(log(N))的命令逾条,它幾乎不會(huì)占用太多CPU琢岩。

使用了單線程后,可維護(hù)性高师脂。多線程模型雖然在某些方面表現(xiàn)優(yōu)異担孔,但是它卻引入了程序執(zhí)行順序的不確定性,帶來(lái)了并發(fā)讀寫的一系列問題吃警,增加了系統(tǒng)復(fù)雜度糕篇、同時(shí)可能存在線程切換、甚至加鎖解鎖汤徽、死鎖造成的性能損耗娩缰。Redis通過AE事件模型以及IO多路復(fù)用等技術(shù),處理性能非常高谒府,因此沒有必要使用多線程拼坎。單線程機(jī)制使得 Redis 內(nèi)部實(shí)現(xiàn)的復(fù)雜度大大降低,Hash 的惰性 Rehash完疫、Lpush 等等 “線程不安全” 的命令都可以無(wú)鎖進(jìn)行泰鸡。

3. Redis6.0為什么要引入多線程呢?

Redis將所有數(shù)據(jù)放在內(nèi)存中壳鹤,內(nèi)存的響應(yīng)時(shí)長(zhǎng)大約為100納秒盛龄,對(duì)于小數(shù)據(jù)包,Redis服務(wù)器可以處理80,000到100,000 QPS芳誓,這也是Redis處理的極限了余舶,對(duì)于80%的公司來(lái)說,單線程的Redis已經(jīng)足夠使用了锹淌。

但隨著越來(lái)越復(fù)雜的業(yè)務(wù)場(chǎng)景匿值,有些公司動(dòng)不動(dòng)就上億的交易量,因此需要更大的QPS赂摆。常見的解決方案是在分布式架構(gòu)中對(duì)數(shù)據(jù)進(jìn)行分區(qū)并采用多個(gè)服務(wù)器挟憔,但該方案有非常大的缺點(diǎn)钟些,例如要管理的Redis服務(wù)器太多,維護(hù)代價(jià)大绊谭;某些適用于單個(gè)Redis服務(wù)器的命令不適用于數(shù)據(jù)分區(qū)政恍;數(shù)據(jù)分區(qū)無(wú)法解決熱點(diǎn)讀/寫問題;數(shù)據(jù)偏斜达传,重新分配和放大/縮小變得更加復(fù)雜等等篙耗。

從Redis自身角度來(lái)說,因?yàn)樽x寫網(wǎng)絡(luò)的read/write系統(tǒng)調(diào)用占用了Redis執(zhí)行期間大部分CPU時(shí)間趟大,瓶頸主要在于網(wǎng)絡(luò)的 IO 消耗, 優(yōu)化主要有兩個(gè)方向:

? 提高網(wǎng)絡(luò) IO 性能鹤树,典型的實(shí)現(xiàn)比如使用 DPDK 來(lái)替代內(nèi)核網(wǎng)絡(luò)棧的方式

? 使用多線程充分利用多核,典型的實(shí)現(xiàn)比如 Memcached逊朽。

協(xié)議棧優(yōu)化的這種方式跟 Redis 關(guān)系不大罕伯,支持多線程是一種最有效最便捷的操作方式。所以總結(jié)起來(lái)叽讳,redis支持多線程主要就是兩個(gè)原因:

? 可以充分利用服務(wù)器 CPU 資源追他,目前主線程只能利用一個(gè)核

? 多線程任務(wù)可以分?jǐn)?Redis 同步 IO 讀寫負(fù)荷

4.Redis6.0默認(rèn)是否開啟了多線程?

Redis6.0的多線程默認(rèn)是禁用的岛蚤,只使用主線程邑狸。如需開啟需要修改redis.conf配置文件:io-threads-do-reads yes

開啟多線程后,還需要設(shè)置線程數(shù)涤妒,否則是不生效的单雾。

關(guān)于線程數(shù)的設(shè)置,官方有一個(gè)建議:4核的機(jī)器建議設(shè)置為2或3個(gè)線程她紫,8核的建議設(shè)置為6個(gè)線程硅堆,線程數(shù)一定要小于機(jī)器核數(shù)。還需要注意的是贿讹,線程數(shù)并不是越大越好渐逃,官方認(rèn)為超過了8個(gè)基本就沒什么意義了。

5.Redis6.0采用多線程后民褂,性能的提升效果如何茄菊?

Redis 作者 antirez 在 RedisConf 2019分享時(shí)曾提到:Redis 6 引入的多線程 IO 特性對(duì)性能提升至少是一倍以上。國(guó)內(nèi)也有大牛曾使用unstable版本在阿里云esc進(jìn)行過測(cè)試赊堪,GET/SET 命令在4線程 IO時(shí)性能相比單線程是幾乎是翻倍了面殖。如果開啟多線程,至少要4核的機(jī)器哭廉,且Redis實(shí)例已經(jīng)占用相當(dāng)大的CPU耗時(shí)的時(shí)候才建議采用脊僚,否則使用多線程沒有意義。

6.Redis6.0多線程的實(shí)現(xiàn)機(jī)制群叶?

流程簡(jiǎn)述如下:

1)主線程負(fù)責(zé)接收建立連接請(qǐng)求吃挑,獲取 socket 放入全局等待讀處理隊(duì)列

2)主線程處理完讀事件之后,通過 RR(Round Robin) 將這些連接分配給這些 IO 線程

3)主線程阻塞等待 IO 線程讀取 socket 完畢

4)主線程通過單線程的方式執(zhí)行請(qǐng)求命令街立,請(qǐng)求數(shù)據(jù)讀取并解析完成舶衬,但并不執(zhí)行回寫 socket

5)主線程阻塞等待 IO 線程將數(shù)據(jù)回寫 socket 完畢

6)解除綁定,清空等待隊(duì)列

該設(shè)計(jì)有如下特點(diǎn):

1)IO 線程要么同時(shí)在讀 socket赎离,要么同時(shí)在寫逛犹,不會(huì)同時(shí)讀或?qū)?/p>

2)IO 線程只負(fù)責(zé)讀寫 socket 解析命令,不負(fù)責(zé)命令處理

7.開啟多線程后梁剔,是否會(huì)存在線程并發(fā)安全問題虽画?

從上面的實(shí)現(xiàn)機(jī)制可以看出,Redis的多線程部分只是用來(lái)處理網(wǎng)絡(luò)數(shù)據(jù)的讀寫和協(xié)議解析荣病,執(zhí)行命令仍然是單線程順序執(zhí)行码撰。所以我們不需要去考慮控制 key、lua个盆、事務(wù)脖岛,LPUSH/LPOP 等等的并發(fā)及線程安全問題。

8.Redis6.0的多線程和Memcached多線程模型進(jìn)行對(duì)比

Memcached 服務(wù)器采用 master-woker 模式進(jìn)行工作颊亮,服務(wù)端采用 socket 與客戶端通訊柴梆。主線程、工作線程 采用 pipe管道進(jìn)行通訊终惑。主線程采用 libevent 監(jiān)聽 listen绍在、accept 的讀事件,事件響應(yīng)后將連接信息的數(shù)據(jù)結(jié)構(gòu)封裝起來(lái)雹有,根據(jù)算法選擇合適的工作線程偿渡,將連接任務(wù)攜帶連接信息分發(fā)出去,相應(yīng)的線程利用連接描述符建立與客戶端的socket連接 并進(jìn)行后續(xù)的存取數(shù)據(jù)操作件舵。

相同點(diǎn):都采用了 master線程-worker 線程的模型

不同點(diǎn):Memcached 執(zhí)行主邏輯也是在 worker 線程里卸察,模型更加簡(jiǎn)單,實(shí)現(xiàn)了真正的線程隔離铅祸,符合我們對(duì)線程隔離的常規(guī)理解坑质。而 Redis 把處理邏輯交還給 master 線程,雖然一定程度上增加了模型復(fù)雜度临梗,但也解決了線程并發(fā)安全等問題

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盟庞,隨后出現(xiàn)的幾起案子吃沪,更是在濱河造成了極大的恐慌,老刑警劉巖什猖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件票彪,死亡現(xiàn)場(chǎng)離奇詭異红淡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)降铸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門在旱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人推掸,你說我怎么就攤上這事桶蝎。” “怎么了谅畅?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵登渣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我毡泻,道長(zhǎng)胜茧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任牙捉,我火速辦了婚禮竹揍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邪铲。我一直安慰自己芬位,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布带到。 她就那樣靜靜地躺著昧碉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揽惹。 梳的紋絲不亂的頭發(fā)上被饿,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音搪搏,去河邊找鬼狭握。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疯溺,可吹牛的內(nèi)容都是我干的论颅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼囱嫩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼恃疯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起墨闲,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤今妄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盾鳞,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犬性,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腾仅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仔夺。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖攒砖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情日裙,我是刑警寧澤吹艇,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站昂拂,受9級(jí)特大地震影響受神,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜格侯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一鼻听、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧联四,春花似錦撑碴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至收苏,卻和暖如春亿卤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹿霸。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工排吴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懦鼠。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓钻哩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親葛闷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子憋槐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354