7年Java老后端不能說的秘密:怎么樣更好的優(yōu)化Redis性能?
大家好,我是一名Java后端程序員,每天開心的擼CRUD;
你猜這次我又要寫個啥沒有卵用的知識點呢?
不好意思措译,問的稍微有點早了,啥提示都沒給杂数,咋猜呢年叮,對吧?
今天早上老板把我叫到辦公室,對我說纬纪,“公司最近接了個電商小程序單子蚓再,你和王二狗,張SD參與下需求分析和設(shè)計包各,然后下個月開發(fā)摘仅,3個月內(nèi)完成測試,上線交付”问畅。
WC娃属,WC,WC
护姆。矾端。。卵皂。秩铆。。灯变。
“老板豺旬,老板,我沒學(xué)過微信小程序柒凉,我是個Java后端程序員族阅,你再招一個前端微信小程序開發(fā)吧”,我很低聲的跟老板說膝捞。
老板很大聲的吼道坦刀,“不會的東西,不會自己學(xué)嗎?招新不要錢嗎?你知道今年行情有多差嗎蔬咬,接單子容易嗎?不想干就G?”
我平很想發(fā)火懟老板鲤遥,但是突然想到;
上有農(nóng)村年邁父母,下有襁褓小兒林艘,媳婦還辭職在出租房帶孩子盖奈。
我就低聲回復(fù):“噢,噢狐援,好的钢坦,好的究孕,我學(xué)〉迹”
屌絲的人生就是這樣厨诸,總得向生活低頭。
努力學(xué)習(xí)吧!!!等我技術(shù)牛逼了禾酱,把老板炒了微酬。
下面直接上超重量級干貨:
回歸正題:怎么樣更好的優(yōu)化Redis性能?
一、優(yōu)化的一些建議
1颤陶、盡量使用短的key
當(dāng)然在精簡的同時颗管,不要為了key的“見名知意”。對于value有些也可精簡滓走,比如性別使用0忙上、1。
2闲坎、避免使用keys
keys , 這個命令是阻塞的,即操作執(zhí)行期間茬斧,其它任何命令在你的實例中都無法執(zhí)行腰懂。當(dāng)redis中key數(shù)據(jù)量小時到無所謂,數(shù)據(jù)量大就很糟糕了项秉。所以我們應(yīng)該避免去使用這個命令绣溜。可以去使用SCAN,來代替娄蔼。
3怖喻、在存到Redis之前先把你的數(shù)據(jù)壓縮下
redis為每種數(shù)據(jù)類型都提供了兩種內(nèi)部編碼方式,在不同的情況下redis會自動調(diào)整合適的編碼方式岁诉。
4锚沸、設(shè)置key有效期
我們應(yīng)該盡可能的利用key有效期。比如一些臨時數(shù)據(jù)(短信校驗碼)涕癣,過了有效期Redis就會自動為你清除!
5哗蜈、選擇回收策略(maxmemory-policy)
當(dāng)Redis的實例空間被填滿了之后,將會嘗試回收一部分key坠韩。根據(jù)你的使用方式距潘,強烈建議使用 volatile-lru(默認(rèn)) 策略——前提是你對key已經(jīng)設(shè)置了超時。但如果你運行的是一些類似于 cache 的東西只搁,并且沒有對 key 設(shè)置超時機制音比,可以考慮使用 allkeys-lru 回收機制,具體講解查看 氢惋。maxmemory-samples 3 是說每次進行淘汰的時候 會隨機抽取3個key 從里面淘汰最不經(jīng)常使用的(默認(rèn)選項)洞翩。
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n68" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">maxmemory-policy 六種方式 :
volatile-lru:只對設(shè)置了過期時間的key進行LRU(默認(rèn)值)
allkeys-lru : 是從所有key里 刪除 不經(jīng)常使用的key
volatile-random:隨機刪除即將過期key
allkeys-random:隨機刪除
volatile-ttl : 刪除即將過期的
noeviction : 永不過期稽犁,返回錯誤 </pre>
6、使用bit位級別操作和byte字節(jié)級別操作來減少不必要的內(nèi)存使用
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n75" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">bit位級別操作:GETRANGE, SETRANGE, GETBIT and SETBIT
byte字節(jié)級別操作:GETRANGE and SETRANGE </pre>
7菱农、盡可能地使用hashes哈希存儲
8缭付、當(dāng)業(yè)務(wù)場景不需要數(shù)據(jù)持久化時,關(guān)閉所有的持久化方式可以獲得最佳的性能
數(shù)據(jù)持久化時需要在持久化和延遲/性能之間做相應(yīng)的權(quán)衡.
9循未、想要一次添加多條數(shù)據(jù)的時候可以使用管道
10陷猫、限制redis的內(nèi)存大小
(64位系統(tǒng)不限制內(nèi)存,32位系統(tǒng)默認(rèn)最多使用3GB內(nèi)存)
數(shù)據(jù)量不可預(yù)估的妖,并且內(nèi)存也有限的話绣檬,盡量限制下redis使用的內(nèi)存大小,這樣可以避免redis使用swap分區(qū)或者出現(xiàn)OOM錯誤嫂粟。(使用swap分區(qū)娇未,性能較低,如果限制了內(nèi)存星虹,當(dāng)?shù)竭_指定內(nèi)存之后就不能添加數(shù)據(jù)了零抬,否則會報OOM錯誤】碛浚可以設(shè)置maxmemory-policy平夜,內(nèi)存不足時刪除數(shù)據(jù))
11、SLOWLOG [get/reset/len]
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n84" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">slowlog-log-slower-than 它決定要對執(zhí)行時間大于多少微秒(microsecond卸亮,1秒 = 1,000,000 微秒)的命令進行記錄忽妒。
slowlog-max-len 它決定 slowlog 最多能保存多少條日志,當(dāng)發(fā)現(xiàn)redis性能下降的時候可以查看下是哪些命令導(dǎo)致的兼贸。 </pre>
二段直、管道測試
redis的管道功能在命令行中沒有,但是redis是支持管道的溶诞,在java的客戶端(jedis)中是可以使用的:
示例代碼:
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n93" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">//注:具體耗時鸯檬,和自身電腦有關(guān)(博主是在虛擬機中運行的數(shù)據(jù))
/**
- 不使用管道初始化1W條數(shù)據(jù)
- 耗時:3079毫秒
- @throws Exception
*/
@Test
public void NOTUsePipeline() throws Exception {
Jedis jedis = JedisUtil.getJedis();
long start_time = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
jedis.set("aa_"+i, i+"");
}
System.out.println(System.currentTimeMillis()-start_time);
}
/**
- 使用管道初始化1W條數(shù)據(jù)
- 耗時:255毫秒
- @throws Exception
*/
@Test
public void usePipeline() throws Exception {
Jedis jedis = JedisUtil.getJedis();
long start_time = System.currentTimeMillis();
Pipeline pipelined = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
pipelined.set("cc_"+i, i+"");
}
pipelined.sync();//執(zhí)行管道中的命令
System.out.println(System.currentTimeMillis()-start_time);
} </pre>
hash的應(yīng)用
示例:我們要存儲一個用戶信息對象數(shù)據(jù),包含以下信息: key為用戶ID螺垢,value為用戶對象(姓名京闰,年齡,生日等)如果用普通的key/value結(jié)構(gòu)來存儲甩苛,主要有以下2種存儲方式:
1蹂楣、將用戶ID作為查找key,把其他信息封裝成一個對象以序列化的方式存儲 缺點:增加了序列化/反序列化的開銷,引入復(fù)雜適應(yīng)系統(tǒng)(Complex adaptive system)修改其中一項信息時讯蒲,需要把整個對象取回痊土,并且修改操作需要對并發(fā)進行保護。
2墨林、用戶信息對象有多少成員就存成多少個key-value對 雖然省去了序列化開銷和并發(fā)問題赁酝,但是用戶ID為重復(fù)存儲犯祠。
Redis提供的Hash很好的解決了這個問題,提供了直接存取這個Map成員的接口酌呆。Key仍然是用戶ID, value是一個Map衡载,這個Map的key是成員的屬性名,value是屬性值隙袁。( 內(nèi)部實現(xiàn):Redis Hashd的Value內(nèi)部有2種不同實現(xiàn)痰娱,Hash的成員比較少時Redis為了節(jié)省內(nèi)存會采用類似一維數(shù)組的方式來緊湊存儲,而不會采用真正的HashMap結(jié)構(gòu)菩收,對應(yīng)的value redisObject的encoding為zipmap,當(dāng)成員數(shù)量增大時會自動轉(zhuǎn)成真正的HashMap,此時encoding為ht )梨睁。
Instagram內(nèi)存優(yōu)化
Instagram可能大家都已熟悉,當(dāng)前火熱的拍照App娜饵,月活躍用戶3億坡贺。四年前Instagram所存圖片3億多時需要解決一個問題:想知道每一張照片的作者是誰(通過圖片ID反查用戶UID),并且要求查詢速度要相當(dāng)?shù)膲K箱舞,如果把它放到內(nèi)存中使用String結(jié)構(gòu)做key-value:
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n107" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">HSET "mediabucket:1155" "1155315" "939"
HGET "mediabucket:1155" "1155315"
"939" </pre>
測試:1百萬數(shù)據(jù)會用掉70MB內(nèi)存遍坟,3億張照片就會用掉21GB的內(nèi)存。當(dāng)時(四年前)最好是一臺EC2的 high-memory 機型就能存儲(17GB或者34GB的晴股,68GB的太浪費了),想把它放到16G機型中還是不行的愿伴。
Instagram的開發(fā)者向Redis的開發(fā)者之一Pieter Noordhuis詢問優(yōu)化方案,得到的回復(fù)是使用Hash結(jié)構(gòu)队魏。具體的做法就是將數(shù)據(jù)分段,每一段使用一個Hash結(jié)構(gòu)存儲. 由于Hash結(jié)構(gòu)會在單個Hash元素在不足一定數(shù)量時進行壓縮存儲万搔,所以可以大量節(jié)約內(nèi)存胡桨。這一點在上面的String結(jié)構(gòu)里是不存在的。而這個一定數(shù)量是由配置文件中的hash-zipmap-max-entries參數(shù)來控制的瞬雹。經(jīng)過實驗昧谊,將hash-zipmap-max-entries設(shè)置為1000時,性能比較好酗捌,超過1000后HSET命令就會導(dǎo)致CPU消耗變得非常大呢诬。
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n113" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">HSET "mediabucket:1155" "1155315" "939"
HGET "mediabucket:1155" "1155315"
"939" </pre>
測試:1百萬消耗16MB的內(nèi)存∨昼停總內(nèi)存使用也降到了5GB尚镰。當(dāng)然我們還可以優(yōu)化,去掉mediabucket:key長度減少了12個字節(jié)哪廓。
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n118" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">HSET "1155" "315" "939"
HGET "1155" "315"
"939" </pre>
三狗唉、優(yōu)化案例
1、修改linux中TCP監(jiān)聽的最大容納數(shù)量
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n124" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">/proc/sys/net/core/somaxconn is set to the lower value of 128. </pre>
在高并發(fā)環(huán)境下你需要一個高backlog值來避免慢客戶端連接問題涡真。注意Linux內(nèi)核默默地將這個值減小到/proc/sys/net/core/somaxconn的值分俯,所以需要確認(rèn)增大somaxconn和tcp_max_syn_backlog兩個值來達到想要的效果肾筐。 echo 511 > /proc/sys/net/core/somaxconn 注意:這個參數(shù)并不是限制redis的最大鏈接數(shù)。如果想限制redis的最大連接數(shù)需要修改maxclients缸剪,默認(rèn)最大連接數(shù)為10000
2吗铐、修改linux內(nèi)核內(nèi)存分配策略
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n130" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">錯誤日志:WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.
To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or
run the command 'sysctl vm.overcommit_memory=1 </pre>
redis在備份數(shù)據(jù)的時候,會fork出一個子進程杏节,理論上child進程所占用的內(nèi)存和parent是一樣的唬渗,比如parent占用的內(nèi)存為8G,這個時候也要同樣分配8G的內(nèi)存給child,如果內(nèi)存無法負(fù)擔(dān)拢锹,往往會造成redis服務(wù)器的down機或者IO負(fù)載過高谣妻,效率下降。所以內(nèi)存分配策略應(yīng)該設(shè)置為 1(表示內(nèi)核允許分配所有的物理內(nèi)存卒稳,而不管當(dāng)前的內(nèi)存狀態(tài)如何)蹋半。 內(nèi)存分配策略有三種 可選值:0、1充坑、2减江。 0, 表示內(nèi)核將檢查是否有足夠的可用內(nèi)存供應(yīng)用進程使用;如果有足夠的可用內(nèi)存捻爷,內(nèi)存申請允許;否則辈灼,內(nèi)存申請失敗,并把錯誤返回給應(yīng)用進程也榄。 1巡莹, 不管需要多少內(nèi)存,都允許申請甜紫。 2降宅, 只允許分配物理內(nèi)存和交換內(nèi)存的大小(交換內(nèi)存一般是物理內(nèi)存的一半)。
3囚霸、關(guān)閉Transparent Huge Pages(THP)
THP會造成內(nèi)存鎖影響redis性能腰根,建議關(guān)閉
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n135" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Transparent HugePages :用來提高內(nèi)存管理的性能
Transparent Huge Pages在32位的RHEL 6中是不支持的
執(zhí)行命令 echo never > /sys/kernel/mm/transparent_hugepage/enabled
把這條命令添加到這個文件中/etc/rc.local </pre>