redis 集合結(jié)構(gòu)介紹
redis集合(Set)結(jié)構(gòu)類似java中的set,可以保證元素的唯一性,而sadd命令用于向集合中添加一個(gè)或多個(gè)成員,已經(jīng)在集合中的元素將被忽略,key不存在則創(chuàng)建集合。
場景描述
要求創(chuàng)建一個(gè)用戶集合,這個(gè)集合是每天從其他數(shù)據(jù)源獲取,構(gòu)造任務(wù)處理這些數(shù)據(jù),提供給下游來判斷某個(gè)用戶是否在這個(gè)集合中。集合內(nèi)容是一些用戶標(biāo)識,32位的md5值,數(shù)據(jù)量較大,文本大概800MB,行數(shù)約2700萬,但還不算特別大的數(shù)據(jù)量,所以計(jì)劃放到redis中。
遇到的問題
首先很明顯, redis的set結(jié)構(gòu)是可以滿足需求的,開始也考慮到不能全部都放到一個(gè)key里。簡單了解到redis set 單個(gè)key可以支持2^32-1 的個(gè)數(shù),且根據(jù)md5值的特征,每一位都是16進(jìn)制,所以取最后一位生成key, 比如2020-03-17:e。這樣就可以有16個(gè)key,可以分擔(dān)key的數(shù)據(jù)量,減少壓力。同時(shí)程序中,使用MultiMap,在超過10000大小后,再開始批量寫入redis,最后給這些固定的key設(shè)置過期時(shí)間為2天,本地根據(jù)樣本數(shù)據(jù)跑通了整個(gè)流程,然后開始根據(jù)實(shí)際數(shù)據(jù)量進(jìn)行。
問題是在部署取到實(shí)際的數(shù)據(jù)后,執(zhí)行任務(wù),執(zhí)行近2小時(shí),依然沒有將數(shù)據(jù)全部導(dǎo)入到redis中,按理說800MB文本不算很大量的。 同時(shí)觀察redis集群監(jiān)控,CPU從2%-3%升高到10%左右,此時(shí)不得不中斷任務(wù)。
回顧現(xiàn)狀,總的數(shù)據(jù)量約2700萬,現(xiàn)有16個(gè)key, 每個(gè)key會(huì)有167萬左右的數(shù)據(jù),其中每個(gè)value的值是32位md5值,空間占32B,每個(gè)key占的空間大約54MB(167W*32B)。首先想到還是單個(gè)key太大了,才導(dǎo)致壓力升高,于是考慮增加key的數(shù)量,來減少set中單個(gè)key的元素?cái)?shù)量。同時(shí)也了解到,一般情況set的單個(gè)key下最好不要超過1MB,當(dāng)前肯定是大大超過的。1MB按單個(gè)值32B計(jì)算,大約可以存3.2萬個(gè)key,當(dāng)然這是非常理想的情況,還有其他一些必須的內(nèi)容也要占用空間,所以建議的單個(gè)key中元素也就在2-3萬左右。
引申
這里介紹幾個(gè)Redis相關(guān)命令,用于查詢當(dāng)前key狀態(tài)情況和排查問題:
1. scard
獲取set 某個(gè)key下的元素個(gè)數(shù),比如
10.118.xx.xx:xxx> scard users:2020-03-17:aff
2. memory usage
查看某個(gè)key所占的內(nèi)存,返回內(nèi)存大小,單位是字節(jié)。比如。
10.136.xxx.xxx:xxx> memory usage users:2020-03-18:000
(integer) 457389
3. pttl 和 ttl
pttl獲取毫秒級別的有效時(shí)間,注意是實(shí)際精度的,不是秒級別換算的值;ttl獲取秒級別的有效時(shí)間
解決方法
目標(biāo)確定是減少單個(gè)key的數(shù)據(jù)量,還是從md5本身出發(fā)。之前取最后一位作為鍵后綴,有16個(gè)key,如果再往前取就可以增加key的個(gè)數(shù),總的數(shù)據(jù)量是一定的,從而可以減少單個(gè)key的數(shù)據(jù)量。 這里改成取后三位來做key后綴,會(huì)有0x000-0xfff共4096(16*16*16)個(gè)key。而單個(gè)key中元素?cái)?shù)量驟減,變成2700W/4096,大約6500左右。(從167萬降到6500)。
后續(xù)追蹤
修改后開始用實(shí)際數(shù)據(jù)驗(yàn)證,開始任務(wù)后,監(jiān)控到redis CPU升高僅1%,從3%到4%左右,任務(wù)最終持續(xù)時(shí)間45分鐘,至此整個(gè)任務(wù)開發(fā)完成。這里有幾個(gè)點(diǎn)可以進(jìn)一步優(yōu)化,首先是沒有引入多線程寫入redis,其次可以對文件進(jìn)行拆分,來運(yùn)用并發(fā)讀取,還有redis查詢方面,可以換bitmap,當(dāng)然這樣整個(gè)邏輯是要變的,后續(xù)如果數(shù)據(jù)量更大可以再考慮。我們看到對這種數(shù)據(jù)量較大的問題的處理,前期的調(diào)研和計(jì)算還是非常重要的,很多功能組件在數(shù)據(jù)量較少和大量的情況下,表現(xiàn)可能是完全不一樣的,大數(shù)據(jù)量的處理會(huì)有更多未知的問題。