分布式計(jì)數(shù)器:
? ? ? ?在應(yīng)用部署中,項(xiàng)目常常使用多節(jié)點(diǎn)負(fù)載均衡來(lái)實(shí)現(xiàn)容災(zāi),支持較高的并發(fā)等一系列問題。但是多節(jié)點(diǎn)同樣也會(huì)產(chǎn)生一系列常見的問題。比如計(jì)數(shù)問題,分布式鎖問題。
? ? ? ? 首先講講計(jì)數(shù)問題,舉例場(chǎng)景:當(dāng)?shù)谌讲寮O光推送免費(fèi)版支持1分鐘600條推送,如果在1分鐘內(nèi)超過,則會(huì)推送失敗。這個(gè)問題若是單節(jié)點(diǎn)的,完全可以通過類的全局變量來(lái)計(jì)數(shù),當(dāng)一分鐘內(nèi)超過600條則通過隊(duì)列的形式放在下一分鐘去推,做法可類似于snowFlake。同時(shí)還需關(guān)注另一個(gè)問題是當(dāng)?shù)较乱环昼姇r(shí),把計(jì)數(shù)器置為0。這樣我們不得不開啟一個(gè)守護(hù)進(jìn)程,sleep1分鐘,然后執(zhí)行把計(jì)數(shù)器置為0。
? ? ? ? 以上場(chǎng)景在1個(gè)應(yīng)用節(jié)點(diǎn)是適用的,但是如果是多個(gè)節(jié)點(diǎn),則行不通。所以我們需要找到一個(gè)能作為分布式計(jì)數(shù)器的存儲(chǔ)器,這個(gè)存儲(chǔ)器必須要滿足2個(gè)條件:1,線程安全,能保證在高并發(fā)下請(qǐng)求得出的結(jié)果是穩(wěn)定+1并且不會(huì)出現(xiàn)重復(fù)。
????????????????????????2,速度夠快,能夠快速的響應(yīng)結(jié)果。
? ? ? ?redis性能極高,讀取速度為110000次/s,寫速度為81000次/s。對(duì)于10/s的操作可以說(shuō)是毫無(wú)壓力,加上是單線程處理是絕對(duì)的線程安全。所以是一個(gè)非常不錯(cuò)的分布式計(jì)數(shù)器選擇。這個(gè)點(diǎn),oracle的sequence作為計(jì)數(shù)倒是也不錯(cuò),同樣滿足快和線程安全。在許多項(xiàng)目中的確也用到了sequence作為計(jì)數(shù)器生成訂單編號(hào)。
? ? ? ? 下面我先介紹一些redis的計(jì)數(shù)方式:
????????首先我先介紹一下redis的incr函數(shù):INCR key。將?key?中儲(chǔ)存的數(shù)字值增一。如果?key?不存在,那么?key?的值會(huì)先被初始化為?0?,然后再執(zhí)行?INCR操作。如果值包含錯(cuò)誤的類型,或字符串類型的值不能表示為數(shù)字,那么返回一個(gè)錯(cuò)誤。本操作的值限制在 64 位(bit)有符號(hào)數(shù)字表示之內(nèi)。64位剛好是Long類型。支持-2^64“ 到”2^64 -1。
? ? ? ? 操作方式如圖:

? ? ? ? ? 接下來(lái),我們只需要考慮超過時(shí)間范圍把值置為0的問題。首先來(lái)看這步操作只需要規(guī)定時(shí)間范圍內(nèi)執(zhí)行一次,如果是多應(yīng)用節(jié)點(diǎn)也只能是啟動(dòng)其中一個(gè)應(yīng)用節(jié)點(diǎn)的守護(hù)線程去跑。但是使用redis不用擔(dān)心這個(gè)問題!可以直接使用指令expire完成這步驟操作,在頻率控制的業(yè)務(wù)場(chǎng)景里,sequence是比不上redis的。
? ? ? ? ??EXPIRE key seconds:為給定?key?設(shè)置生存時(shí)間,當(dāng)?key?過期時(shí)(生存時(shí)間為?0?),它會(huì)被自動(dòng)刪除。在 Redis 中,帶有生存時(shí)間的?key?被稱為『易失的』(volatile)。

? ? ? ? ?這是redis內(nèi)置的定時(shí)任務(wù),原理也是redis內(nèi)置的守護(hù)進(jìn)程監(jiān)聽執(zhí)行。這樣我們的需求就可以使用EXPIRE key? 60(1分鐘)去實(shí)現(xiàn)了!
分布式鎖:
? ? ? ? 該功能產(chǎn)生原因與分布式計(jì)數(shù)器一樣。比如項(xiàng)目中某一個(gè)方法是需要排隊(duì)的,比如計(jì)數(shù)器++操作。我們需要用synchronized去保證操作是上鎖的。但是如果是多節(jié)點(diǎn)。方法該如何鎖呢?當(dāng)然也是可以用到redis,并且redis的高讀取高寫入并線程安全是分布式鎖不二選擇。比如我們想鎖住方法A,我們只需要定義redis里的一個(gè)String類型的方法A的key,定義該key的屬性為1。當(dāng)我們請(qǐng)求A方法的時(shí)候我們都會(huì)去redis獲取到該key值是否被定義,如果是則等待,直到該key的屬性為null,才進(jìn)行對(duì)A進(jìn)行鎖的獲取,并同時(shí)把該key值設(shè)置為1,如果獲取鎖成功redis會(huì)返回1。使用的redis指令為:SETNX函數(shù)。同時(shí)還可以集成redis的expire函數(shù)。保證不會(huì)被鎖死,如果超過規(guī)定時(shí)間把該key刪了即可。我推薦該方面非常優(yōu)秀的分布式鎖插件Redisson,項(xiàng)目中可以用到該插件完美解決分布式鎖的問題。
? ? ? ?SETNX 全稱:SET If NOT EXIST,如若成功則返回1,失敗返回0??梢砸罁?jù)返回值判斷是否成功獲取鎖。redis內(nèi)置對(duì)鎖支持的函數(shù),無(wú)線程安全問題。
分布式計(jì)數(shù)器java示例:
? ? ? ? 首先定義一個(gè)redis計(jì)數(shù)器的注解:

? ? ? ? 針對(duì)想要控制的方法上添加注解:

? ? ????aop核心思想代碼:
