接上篇 《服務(wù)假死問(wèn)題解決過(guò)程實(shí)記(二)—— C3P0 數(shù)據(jù)庫(kù)連接池配置引發(fā)的血案》
五、04.17—04.21 緩存邏輯修正
這段時(shí)間我一直在優(yōu)化服務(wù)的性能啰扛,主要是從分布式緩存和業(yè)務(wù)邏輯修正兩個(gè)角度出發(fā)進(jìn)行的。首先是將我們的緩存邏輯給修正了一下推盛。
關(guān)于緩存冕房,我們業(yè)務(wù)存在兩個(gè)重要問(wèn)題:
- 集群部署的情況下住册,每個(gè)服務(wù)都用了很多本地 ConcurrentHashMap 緩存;
- 在業(yè)務(wù)邏輯計(jì)算出結(jié)果之后雹拄,直接將計(jì)算出的結(jié)果存在了本地緩存中(即緩存過(guò)程與業(yè)務(wù)邏輯緊密耦合)收奔;
對(duì)于第一個(gè)問(wèn)題,主要有兩個(gè)隱患:首先集群部署滓玖,也就意味著為了提高服務(wù)的性能坪哄,環(huán)境中有多臺(tái)服務(wù),所以對(duì)于相同的數(shù)據(jù)势篡,每個(gè)服務(wù)都要自己記錄一份緩存损姜,這樣對(duì)內(nèi)存是很大的浪費(fèi)。其次多臺(tái)服務(wù)的緩存也很容易出現(xiàn)不同步的問(wèn)題殊霞,極易出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象摧阅。
對(duì)于第二個(gè)問(wèn)題,將結(jié)果存放到緩存中绷蹲,本身與業(yè)務(wù)并沒(méi)有關(guān)系棒卷,不管是否置入緩存顾孽,都不會(huì)對(duì)業(yè)務(wù)結(jié)果不會(huì)有影響。但如果將緩存的一部分放在業(yè)務(wù)邏輯中比规,就相當(dāng)于緩存被強(qiáng)行的綁在了業(yè)務(wù)邏輯之中若厚。所以對(duì)這個(gè)問(wèn)題進(jìn)行優(yōu)化,就是將緩存從業(yè)務(wù)邏輯中解耦蜒什。
筆者是先解決了后一個(gè)問(wèn)題测秸,然后再解決前一個(gè)問(wèn)題。
1. 切面思想的體會(huì)
我認(rèn)為將緩存從業(yè)務(wù)邏輯中解耦灾常,這種工作交給 AOP 后置增強(qiáng)是最合適的霎冯。所以我就開(kāi)始對(duì)業(yè)務(wù)代碼進(jìn)行一通分析,提取出來(lái)他們的共同點(diǎn)钞瀑,將置入緩存的邏輯從業(yè)務(wù)代碼中拆了出來(lái)沈撞,放到了一個(gè)后置切面中。具體思路就是這樣雕什,過(guò)程不表缠俺。
筆者之前只是會(huì)使用 AOP 切面,但在這個(gè)過(guò)程中贷岸,筆者切實(shí)的加深了對(duì) AOP 的理解壹士。代碼抽取過(guò)程中,同事也問(wèn)我這樣做有什么好處偿警,對(duì)性能有什么優(yōu)化躏救?我想了一下,回答:這對(duì)性能沒(méi)有任何優(yōu)化户敬。同事問(wèn)我做 AOP 切面的意義落剪,我開(kāi)了個(gè)腦洞睁本,用這個(gè)例子給出了一個(gè)比較通俗易懂的解釋?zhuān)?/p>
問(wèn):把大象放在冰箱里總共分幾步尿庐?
答:分三步。第一步把冰箱門(mén)打開(kāi)呢堰,第二步把大象給塞進(jìn)去抄瑟,第三步把冰箱門(mén)關(guān)上。
這個(gè)經(jīng)典段子在筆者看來(lái)枉疼,很有用 AOP 思路分析的價(jià)值皮假。首先,我們的目的是把大象放進(jìn)冰箱里骂维,這就是我們的業(yè)務(wù)所在惹资。但是要放大象進(jìn)去,開(kāi)冰箱門(mén)和關(guān)冰箱門(mén)可以省略嗎航闺?不能褪测。那這兩者和塞大象的業(yè)務(wù)有關(guān)嗎猴誊?沒(méi)有。
所以與業(yè)務(wù)無(wú)關(guān)侮措,但又必須做的工作(或者優(yōu)化的工作)懈叹,就是切面的意義所在了。緩存的加入分扎,優(yōu)化了數(shù)據(jù)的讀取澄成,但如果去掉了緩存,業(yè)務(wù)依舊可以正常工作畏吓,只是效率低一點(diǎn)而已墨状。所以把緩存從業(yè)務(wù)代碼中拿出來(lái),就實(shí)現(xiàn)了解耦庵佣。
2. AOP 的代理思想
參考地址:
《Spring AOP 的實(shí)現(xiàn)原理》
[《Spring service 本類(lèi)中方法調(diào)用另一個(gè)方法事務(wù)不生效問(wèn)題》](https:// blog.csdn.net/dapinxiaohuo/article/details/52092447)
另外在該過(guò)程中歉胶,筆者也終于理解了代理的意義。
首先敘述一下問(wèn)題:筆者有一次在 A 類(lèi)的 a 方法上加入了后置切面方法后巴粪,用 A 類(lèi)的 b 方法調(diào)用了自身的 a 方法通今,但多次測(cè)試發(fā)現(xiàn)怎么也不會(huì)進(jìn)后置切面方法。經(jīng)過(guò)好長(zhǎng)時(shí)間的加班折騰肛根,筆者終于發(fā)現(xiàn)了一個(gè)問(wèn)題:自身調(diào)用方法辫塌,是不會(huì)進(jìn)入切面方法的。
AOP 的基本是使用代理實(shí)現(xiàn)的派哲。通常使用的是 AspectJ 或者 Spring AOP 切面臼氨。
AspectJ 使用靜態(tài)編譯的方式實(shí)現(xiàn) AOP 功能。對(duì)于一個(gè)寫(xiě)好的類(lèi)芭届,對(duì)其編寫(xiě) aspectj 腳本储矩,然后對(duì)該 *.java 文件進(jìn)行編譯指令,如 <code>ajc -d . Hello.java TxAspect.aj</code>褂乍,即可編譯生成一個(gè)類(lèi)持隧,該類(lèi)會(huì)比原先的類(lèi)多一些內(nèi)容,通過(guò)這種方式實(shí)現(xiàn)切面逃片。
原始類(lèi):
public class Hello {
public void sayHello() {
System.out.println("hello");
}
public static void main(String[] args) {
Hello h = new Hello();
h.sayHello();
}
}
編寫(xiě)的 aspectj 語(yǔ)句:
public aspect TxAspect {
void around():call(void Hello.sayHello()){
System.out.println("開(kāi)始事務(wù) ...");
proceed();
System.out.println("事務(wù)結(jié)束 ...");
}
}
執(zhí)行 aspectj 語(yǔ)句 <code>ajc -d . Hello.java TxAspect.aj</code> 編譯后生成的類(lèi):
public class Hello {
public Hello() {
}
public void sayHello() {
System.out.println("hello");
}
public static void main(String[] args) {
Hello h = new Hello();
sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)null);
}
}
Spring AOP 是通過(guò)動(dòng)態(tài)代理的形式實(shí)現(xiàn)的屡拨,其中又分為通過(guò) JDK 動(dòng)態(tài)代理,以及 CGLIB 動(dòng)態(tài)代理褥实。
- JDK 動(dòng)態(tài)代理:使用反射原理呀狼,對(duì)實(shí)現(xiàn)了接口的類(lèi)進(jìn)行代理;
- CGLIB 動(dòng)態(tài)代理:字節(jié)碼編輯技術(shù)损离,對(duì)沒(méi)有實(shí)現(xiàn)接口的類(lèi)進(jìn)行代理哥艇;
主要原因筆者后續(xù)也終于分析理解了:由于筆者雖然使用的是 @AspectJ 注解,但實(shí)際上使用的依舊是 Spring AOP僻澎。
如果使用 Spring AOP貌踏,使用過(guò)程中可能會(huì)出現(xiàn)一個(gè)問(wèn)題:自身調(diào)用切面注解方法瓮增,切面失效。這是因?yàn)?AOP 的實(shí)現(xiàn)是通過(guò)代理的形式實(shí)現(xiàn)的哩俭,所以自身調(diào)用方法不滿(mǎn)足代理調(diào)用的條件绷跑,所以不會(huì)執(zhí)行切面。切面的調(diào)用流程如下文鏈接所示凡资,文中以事務(wù)出發(fā)砸捏,講解了 AOP 的實(shí)現(xiàn)原理 (注:事務(wù)的實(shí)現(xiàn)原理也是切面):
[圖片上傳失敗...(image-96f767-1556680853759)]
所以,對(duì)于筆者這種自身調(diào)用切面的情況隙赁,可以改變方法的調(diào)用方式:改變調(diào)用自身方法的方式垦藏,使用調(diào)用代理方法的形式。筆者在 Spring 的 XML 中對(duì) aop 進(jìn)行配置:
<!—- 注解風(fēng)格支持 -->
<aop:aspectj-autoproxy expose-proxy="true"/>
<!—- xml 風(fēng)格支持 -->
<aop:config expose-proxy="true"/>
然后在方法中通過(guò) Spring 的 AopContext.currentProxy 獲取代理對(duì)象伞访,然后通過(guò)代理調(diào)用方法掂骏。例如有自身方法調(diào)用如下:
this.b();
變?yōu)椋?/p>
((AService) AopContext.currentProxy()).b();
筆者又開(kāi)了一次腦洞,用娛樂(lè)圈明星和代理人之間的關(guān)系來(lái)類(lèi)比理解了一下代理模式厚掷。作為一個(gè)代理人弟灼,目的是協(xié)助明星的工作。明星主要工作冒黑,就是唱田绑,跳,RAP 之類(lèi)的抡爹,而代理人掩驱,就是類(lèi)似于在演出開(kāi)始之前找廠(chǎng)商談出場(chǎng)費(fèi),演出之后找廠(chǎng)商結(jié)賬冬竟,買(mǎi)熱搜欧穴,或者發(fā)個(gè)律師函之類(lèi)的”门梗總之不管好事兒壞事兒涮帘,代理干的事兒都賊 TM 操心,又和明星的演出工作沒(méi)有直接的關(guān)系袋狞。
數(shù)據(jù)庫(kù)事務(wù)也是一樣的道理焚辅。增刪改查映屋,是 SQL 語(yǔ)句關(guān)心的核心業(yè)務(wù)苟鸯,SQL 語(yǔ)句只要按照語(yǔ)句執(zhí)行就順利完成了任務(wù)。由于事務(wù)的原子性棚点,一個(gè)事務(wù)內(nèi)的所有執(zhí)行完畢后早处,事務(wù)一起提交結(jié)果。如果執(zhí)行過(guò)程中出現(xiàn)了意外呢瘫析?那么事務(wù)就把狀態(tài)回滾到最開(kāi)始的狀態(tài)砌梆。事務(wù)依舊做著處理后續(xù)工作默责,還有幫人擦屁股的工作,而且還是和業(yè)務(wù)本身沒(méi)有關(guān)系的事兒咸包,這和代理人是一樣的命啊……
這樣桃序,AOP 和代理思想,筆者用一頭大象烂瘫,還有一個(gè)明星經(jīng)紀(jì)人的例子便頓悟了媒熊。
3. 分布式緩存問(wèn)題(緩存雪崩,緩存穿透坟比,緩存擊穿)
參考地址:
《緩存穿透芦鳍,緩存擊穿,緩存雪崩解決方案分析》
《緩存穿透葛账、緩存擊穿柠衅、緩存雪崩區(qū)別和解決方案》
好的,把緩存邏輯從業(yè)務(wù)代碼邏輯揪了出來(lái)籍琳,后一個(gè)問(wèn)題就解決了菲宴,現(xiàn)在解決前一個(gè)問(wèn)題:將集群中所有服務(wù)的緩存從本地緩存轉(zhuǎn)為分布式緩存,降低緩存在服務(wù)中占用的資源趋急。
由于業(yè)務(wù)組只有 Memcache 緩存集群裙顽,并沒(méi)有搭起來(lái) Redis,所以筆者還是選了 Memcache 作為分布式緩存工具宣谈。筆者用了一天時(shí)間封裝了我們服務(wù)自己用的 MemcacheService愈犹,把初始化、常用的 get, set 方法封裝完畢闻丑,測(cè)試也沒(méi)有問(wèn)題漩怎。由于具體過(guò)程只是對(duì) Memcache 的 API 進(jìn)行簡(jiǎn)單封裝,故具體過(guò)程不表嗦嗡。但是進(jìn)行到這里勋锤,筆者也只是簡(jiǎn)單的封裝完畢,仍然有可以?xún)?yōu)化的空間侥祭。
集群服務(wù)的緩存叁执,有三大問(wèn)題:緩存雪崩、緩存穿透矮冬、緩存擊穿谈宛。在并發(fā)量高的時(shí)候,這三個(gè)緩存問(wèn)題很容易引起服務(wù)與數(shù)據(jù)庫(kù)的宕機(jī)胎署。雖然我們的小服務(wù)并不存在高并發(fā)的場(chǎng)景吆录,但既然要做性能優(yōu)化,就要盡量做到最好琼牧,所以筆者還是在我這小小的服務(wù)上事先了這幾個(gè)緩存問(wèn)題并加以解決恢筝。
(1) 緩存雪崩
緩存雪崩和緩存擊穿都和分布式緩存的緩存過(guò)期時(shí)間有關(guān)哀卫。
緩存雪崩,指的是對(duì)于某些熱點(diǎn)緩存撬槽,如果都設(shè)置了相同的過(guò)期時(shí)間此改,在過(guò)期時(shí)間范圍之內(nèi)是正常的。但等到經(jīng)過(guò)了這個(gè)過(guò)期時(shí)間之后侄柔,大量并發(fā)再訪(fǎng)問(wèn)這些緩存內(nèi)容带斑,會(huì)因?yàn)榫彺鎯?nèi)容已經(jīng)過(guò)期而失效,從而大量并發(fā)短時(shí)間內(nèi)涌向數(shù)據(jù)庫(kù)勋拟,很容易造成數(shù)據(jù)庫(kù)的崩潰勋磕。
這樣的情況發(fā)生的主要原因,在于熱點(diǎn)數(shù)據(jù)設(shè)置了相同的過(guò)期時(shí)間敢靡。解決的方案是對(duì)這些熱點(diǎn)數(shù)據(jù)設(shè)置隨機(jī)的過(guò)期時(shí)間即可挂滓。比如筆者在封裝 Memcache 接口的參數(shù)中有過(guò)期時(shí)間 int expireTime,并設(shè)置了默認(rèn)的過(guò)期時(shí)間為 30min啸胧,這樣的緩存策略確實(shí)容易產(chǎn)生緩存雪崩現(xiàn)象赶站。此后筆者在傳入的 expireTime 值的基礎(chǔ)上,由加上了一個(gè) 0~300 秒的隨機(jī)值纺念。這樣所有緩存的過(guò)期時(shí)間都有了一定的隨機(jī)性贝椿,從而避免了緩存雪崩現(xiàn)象。
(2) 緩存擊穿
假設(shè)有某個(gè)熱點(diǎn)數(shù)據(jù)陷谱,該數(shù)據(jù)在數(shù)據(jù)庫(kù)中存在該值烙博,但緩存中不存在,那么如果同一時(shí)間大量并發(fā)查詢(xún)?cè)摼彺嫜萄罚瑒t會(huì)由于緩存中不存在該數(shù)據(jù)渣窜,從而將大量并發(fā)釋放,大量并發(fā)涌向數(shù)據(jù)庫(kù)宪躯,容易引起數(shù)據(jù)庫(kù)的宕機(jī)乔宿。
看到這里也可以體會(huì)到,前面的緩存雪崩與緩存擊穿有很大的相似性访雪。緩存雪崩針對(duì)的是對(duì)一批在數(shù)據(jù)庫(kù)中存在详瑞,但在緩存中不存在的數(shù)據(jù);而緩存擊穿針對(duì)的是一個(gè)數(shù)據(jù)臣缀。
在《緩存穿透坝橡,緩存擊穿,緩存雪崩解決方案分析》一文中提到了四種方式肝陪,筆者采用了類(lèi)似于第一種方式的解決方法:使用互斥鎖驳庭。由于這里的環(huán)境是分布式環(huán)境刑顺,所以這里的互斥鎖指的其實(shí)是分布式鎖氯窍。筆者又按照《緩存穿透饲常、緩存擊穿、緩存雪崩區(qū)別和解決方案》一文中的思路狼讨,以業(yè)務(wù)組的 Zookeeer 集群為基礎(chǔ)實(shí)現(xiàn)了分布式鎖贝淤,解決了緩存擊穿的問(wèn)題。偽代碼如下:
public Object getData(String key) {
// 1. 從緩存中讀取數(shù)據(jù)
Object result = getDataFromMemcache(key);
// 2. 如果緩存中不存在數(shù)據(jù)政供,則從數(shù)據(jù)庫(kù)中 (或者計(jì)算) 獲取
if (result == null) {
InterProcessMutex lock = new InterProcessMutex(client, "/service/lock/test1");
// 2.1 嘗試獲取鎖
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
// ※ 2.1.1 嘗試再次獲取緩存播聪,如果獲取值不為空,則直接返回
result = getDataFromMemcache(key);
if (result != null) {
log.info("獲取鎖后再次嘗試獲取緩存布隔,緩存命中离陶,直接返回");
return result;
}
// 2.1.2 從數(shù)據(jù)庫(kù)中獲取原始數(shù)據(jù) (或者計(jì)算獲取得到數(shù)據(jù))
result = queryData(key);
// 2.1.3 將結(jié)果存入緩存
setDataToMemcache(key, result);
}
// 2.2 獲取鎖失敗,暫停短暫時(shí)間衅檀,嘗試再次重新獲取緩存信息
else {
TimeUnit.MILLISECONDS.sleep(100);
result = getData(key);
}
} catch (Exception e) {
e.printStackTrace();
}
// 2.3 退出方法前釋放分布式鎖
finally {
if (lock != null && lock.isAcquiredInThisProcess()) {
lock.release();
}
}
}
return result;
}
筆者解決緩存擊穿的思路招刨,是集群中服務(wù)如果同時(shí)處理大量并發(fā),且嘗試獲取同一數(shù)據(jù)時(shí)哀军,所有并發(fā)都會(huì)嘗試獲取 InterProcessMutex 的分布式鎖沉眶。這里的 InterProcessMutex,是 Curator 自帶的一個(gè)分布式鎖杉适,它基于 Zookeeper 的 Znode 實(shí)現(xiàn)了分布式鎖的功能谎倔。在 InterProcessMutex 的傳參中,需要傳入一個(gè) ZNode 路徑猿推,當(dāng)大量并發(fā)都嘗試獲取這個(gè)分布式鎖時(shí)片习,只有一個(gè)鎖可以獲得該鎖,其他鎖需要等待一定時(shí)間 (acquire 方法中傳入的時(shí)間)蹬叭。如果經(jīng)過(guò)這段時(shí)間仍然沒(méi)有獲得該鎖毯侦,則 acquire 方法返回 false。
筆者解決緩存擊穿的邏輯偽代碼如上所示具垫。邏輯比較簡(jiǎn)單侈离,但其中值得一提的是,在 2.1.1 中筝蚕,對(duì)于已經(jīng)獲取了分布式鎖的請(qǐng)求卦碾,筆者又重新嘗試獲取一次緩存。這是因?yàn)?Memcache 緩存的存入與讀取可能會(huì)不同步的情況起宽。假想一種情況:對(duì)于嘗試獲取分布式鎖的請(qǐng)求 req1, req2洲胖,如果 req1 首先獲取到了鎖,且將計(jì)算的結(jié)果存入了 Memcache坯沪,然后 req2 在等待時(shí)間內(nèi)又重新獲取到了該鎖绿映,如果直接繼續(xù)執(zhí)行,也就會(huì)重新從數(shù)據(jù)庫(kù)中獲取一次 req1 已經(jīng)獲取且存入緩存的數(shù)據(jù),這樣就造成了重復(fù)數(shù)據(jù)的讀取叉弦。所以需要在獲取了分布式鎖之后重新再獲取一次緩存丐一,判斷在爭(zhēng)搶分布式鎖的過(guò)程中,緩存是否已經(jīng)處理完畢淹冰。
(3) 緩存穿透
緩存穿透库车,指的是當(dāng)數(shù)據(jù)庫(kù)與緩存中都沒(méi)有某數(shù)據(jù)時(shí),該條數(shù)據(jù)就會(huì)成為漏洞樱拴,如果有人蓄意短時(shí)間內(nèi)大量查詢(xún)這條數(shù)據(jù)柠衍,大量連接就很容易穿透緩存涌向數(shù)據(jù)庫(kù),會(huì)造成數(shù)據(jù)庫(kù)的宕機(jī)晶乔。針對(duì)這種情況珍坊,比較普遍的應(yīng)對(duì)方法是使用布隆過(guò)濾器 (Bloom Filter)進(jìn)行防護(hù)。
布隆過(guò)濾器和弗雷爾卓德之心有一些相似的地方,它的防御不是完全抵擋的腺怯,是不準(zhǔn)確的袱饭。換句話(huà)說(shuō),針對(duì)某條數(shù)據(jù)呛占,布隆過(guò)濾器只保證在數(shù)據(jù)庫(kù)中一定沒(méi)有該數(shù)據(jù)虑乖,不能保證一定有這條數(shù)據(jù)。
布隆過(guò)濾器的最大的好處是晾虑,判斷簡(jiǎn)單疹味,消耗空間少。通常如果直接使用 Map 訪(fǎng)問(wèn)結(jié)果來(lái)判斷是否存在數(shù)據(jù)是否存在帜篇,雖然可以實(shí)現(xiàn)糙捺,但 Map 通常的內(nèi)存利用率不會(huì)太高,對(duì)于幾百萬(wàn)甚至幾億的大數(shù)據(jù)集笙隙,太浪費(fèi)空間洪灯。而布隆過(guò)濾器本身是一個(gè) bitmap 的結(jié)構(gòu)(筆者個(gè)人理解基本是一個(gè)很大很大的 0-1 數(shù)組),初始狀態(tài)下全部為 0竟痰。當(dāng)有值存入緩存時(shí)签钩,使用多個(gè) Hash 函數(shù)分別計(jì)算對(duì)應(yīng) Key 值的結(jié)果,結(jié)果轉(zhuǎn)換為 bitmap 指定的位數(shù)坏快,對(duì)應(yīng)位上置 1铅檩。這樣,越來(lái)越多的值存入莽鸿,bitmap 上也填充了越來(lái)越多的 1昧旨。
這樣如果有請(qǐng)求查詢(xún)某個(gè)數(shù)據(jù)是否存在拾给,則依舊利用相同的 Hash 函數(shù)計(jì)算結(jié)果,并在 bitmap 上查找計(jì)算結(jié)果的位置上是否全部為 1兔沃。只要有一個(gè)位置不為 1蒋得,緩存中就必然沒(méi)有該數(shù)據(jù)。但是如果所有位置都為 1粘拾,那么也不能說(shuō)明緩存中一定有這條數(shù)據(jù)窄锅。因?yàn)殡S著越來(lái)越多的數(shù)據(jù)存入緩存创千,布隆過(guò)濾器 bitmap 中的 1 值也越來(lái)越多缰雇,所以即使計(jì)算結(jié)果中所有位數(shù)的值都為 1,也有可能是其他若干計(jì)算結(jié)果將這些位置上的 1 給占據(jù)了追驴。布隆過(guò)濾器雖然有誤判率械哟,但是有文章指出布隆過(guò)濾器的誤判率在合適的參數(shù)設(shè)置之下會(huì)變得很低。具體可以見(jiàn)文章《使用BloomFilter布隆過(guò)濾器解決緩存擊穿殿雪、垃圾郵件識(shí)別暇咆、集合判重》。
除了不能判斷數(shù)據(jù)庫(kù)中一定存在某條數(shù)據(jù)之外丙曙,布隆過(guò)濾器還有一個(gè)問(wèn)題爸业,在于它不能刪除某個(gè)值填充在 bitmap 中的結(jié)果。
筆者本來(lái)想用 guava 包中自帶的 BloomFilter 來(lái)實(shí)現(xiàn) Memcache 的緩存穿透防護(hù)亏镰,本來(lái)都已經(jīng)研究好該怎么加入布隆的大盾牌了扯旷,但是后來(lái)一想,布隆過(guò)濾器應(yīng)該是在 Memcache 端做的事情索抓,而不是在我集群服務(wù)這里該做的钧忽。如果每個(gè)服務(wù)都建一個(gè) BloomFilter,這幾個(gè)過(guò)濾器的值肯定是不同步的逼肯,而且會(huì)造成大量的空間浪費(fèi)耸黑,所以最后并沒(méi)有付諸實(shí)踐。
六篮幢、04.17—04.25 業(yè)務(wù)邏輯修正
與解決技術(shù)層面同步進(jìn)行的大刊,是對(duì)于業(yè)務(wù)邏輯的修正。修正的主要思路是調(diào)整消息訂閱后的處理方式三椿,以及方法奈揍、緩存的粒度調(diào)整(從粗粒度調(diào)整到細(xì)粒度)。涉及具體的業(yè)務(wù)邏輯赋续,此處不表男翰。
結(jié)語(yǔ)
經(jīng)過(guò)一段長(zhǎng)時(shí)間的奮戰(zhàn),我們的并發(fā)效率提升了二到三倍纽乱。
但筆者并不是感覺(jué)我們做的很好蛾绎,筆者更認(rèn)為這是項(xiàng)目整個(gè)過(guò)程中的問(wèn)題爆發(fā)。由于去年項(xiàng)目趕的太緊,三個(gè)月下來(lái)幾乎天天 9107 的節(jié)奏租冠,小伙伴們都累的沒(méi)脾氣鹏倘,自然而然產(chǎn)生了抵觸心理,代碼質(zhì)量與效率也自然下降顽爹。整個(gè)過(guò)程下來(lái)纤泵,堆積的坑越攢越多,最終到了某個(gè)時(shí)間不得不改镜粤。
看著這些被修改的代碼捏题,有一部分確實(shí)都是自己的手筆,確實(shí)算是段悲傷的黑歷史了肉渴。但歷史已不再重要了公荧,而是在這段解決問(wèn)題的過(guò)程中積累學(xué)習(xí)的經(jīng)驗(yàn),是十分寶貴的同规。希望以后在工作中能夠不再出現(xiàn)類(lèi)似的問(wèn)題吧循狰。
本文于 2019.03.06 始,于 2019 五一勞動(dòng)節(jié)終券勺。
系列文章:
《服務(wù)假死問(wèn)題解決過(guò)程實(shí)記(一)——問(wèn)題發(fā)現(xiàn)篇》
《服務(wù)假死問(wèn)題解決過(guò)程實(shí)記(二)——C3P0 數(shù)據(jù)庫(kù)連接池配置引發(fā)的血案》
《服務(wù)假死問(wèn)題解決過(guò)程實(shí)記(三)——緩存問(wèn)題優(yōu)化》