服務(wù)假死問(wèn)題解決過(guò)程實(shí)記(三)——緩存問(wèn)題優(yōu)化

接上篇 《服務(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ù)。

布隆正罢,來(lái)干活了5媲(弗雷爾卓德之心——Bloom Filter)

布隆過(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)化》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绪钥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子关炼,更是在濱河造成了極大的恐慌程腹,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盗扒,死亡現(xiàn)場(chǎng)離奇詭異跪楞,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)侣灶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)甸祭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人褥影,你說(shuō)我怎么就攤上這事池户。” “怎么了凡怎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵校焦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我统倒,道長(zhǎng)寨典,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任房匆,我火速辦了婚禮耸成,結(jié)果婚禮上报亩,老公的妹妹穿的比我還像新娘。我一直安慰自己井氢,他們只是感情好弦追,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著花竞,像睡著了一般劲件。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上约急,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天零远,我揣著相機(jī)與錄音,去河邊找鬼烤宙。 笑死遍烦,一個(gè)胖子當(dāng)著我的面吹牛俭嘁,可吹牛的內(nèi)容都是我干的躺枕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼供填,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拐云!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起近她,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤叉瘩,失蹤者是張志新(化名)和其女友劉穎权埠,沒(méi)想到半個(gè)月后履腋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徒欣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年攒磨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泳桦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娩缰,死狀恐怖灸撰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拼坎,我是刑警寧澤浮毯,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站泰鸡,受9級(jí)特大地震影響债蓝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盛龄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一饰迹、第九天 我趴在偏房一處隱蔽的房頂上張望器虾。 院中可真熱鬧,春花似錦蹦锋、人聲如沸兆沙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)葛圃。三九已至,卻和暖如春憎妙,著一層夾襖步出監(jiān)牢的瞬間库正,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工厘唾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留褥符,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓抚垃,卻偏偏與公主長(zhǎng)得像喷楣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹤树,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容