成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

記一次愚蠢的操作--線程安全問題

Forelax / 2110人閱讀

摘要:文本已收錄至我的倉庫,歡迎記一次在工作中愚蠢的操作,本文關(guān)鍵字線程安全我怎么天天在寫啊一交代背景我這邊有一個系統(tǒng),提供一個接口去發(fā)送各種信息比如短信郵件微信等等渠道。小王用了一陣子也沒說有什么問題,于是這個需求就交付了。

前言
只有光頭才能變強(qiáng)。

文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y

記一次在工作中愚蠢的操作,本文關(guān)鍵字:線程安全

(我怎么天天在寫B(tài)ug?。?/p> 一、交代背景

我這邊有一個系統(tǒng),提供一個RPC接口去發(fā)送各種信息(比如短信、郵件、微信)等等渠道。我這邊的系統(tǒng)架構(gòu)是這樣的:

概括:service系統(tǒng)提供一個RPC接口,別人調(diào)用我提供的接口,我在service系統(tǒng)中對這個消息進(jìn)行判斷、拼接等等業(yè)務(wù)邏輯,最后會將這個消息放到消息隊列里邊。sender系統(tǒng)會消費消息隊列里邊的數(shù)據(jù),然后發(fā)送消息

例子:小王調(diào)用我們的RPC接口,想要發(fā)送郵件。我對郵件的參數(shù)進(jìn)行判斷和拼裝成一個我這邊定義好的Task,將這個Task丟到消息隊列里邊。sender系統(tǒng)消費這個Task,調(diào)用java.mail的API完成發(fā)送郵件的功能。

小王調(diào)用我們這個RPC接口,只要service系統(tǒng)把這個task丟到消息隊列里邊去,我們就返回response給小王。

只要這個task放到了消息隊列里邊,我們就返回success。所以有的時候,小王會問:“我這明明返回是success啊,怎么我的郵件沒發(fā)出去呢” ------(異步)

每發(fā)送一封郵件,我們都會將這封郵件的信息入庫(保存在MySQL中),在MySQL中我們可以得知這封郵件的發(fā)送時間,發(fā)送狀態(tài)等等。

而小王的這些郵件又十分在意是否成功發(fā)送出去了,如果發(fā)送失敗了他那邊需要重發(fā)。于是,他監(jiān)聽我們DB的binlog,根據(jù)binlog的信息來判斷是否需要重發(fā)。

由于種種的原因,小王希望調(diào)用我們RPC接口的時候就能拿到一個唯一的標(biāo)識好讓他去判斷這封郵件是成功還是失敗

顯然,入庫的Email ID是不可能的(因為他調(diào)我們RPC接口,我們將Task放到消息隊列就返回了。此時sender系統(tǒng)還沒消費呢)

于是,我們這邊打算在service系統(tǒng)生成一個messageId,然后返回給他,將這個messageId綁定到Task里邊,一直到入庫。

二、上鉤

上面確定好需求和思路之后,我這邊就去看返回給小王的response對象,一看,發(fā)現(xiàn)已經(jīng)有msgId字段了

public class SendResponse {
    
    // 錯誤碼
    private int errCode;

    // 錯誤信息
    private String errInfo;

    // messageId
    private long msgId;

}

我搜了一下這個字段的信息ctrl + shift + f,發(fā)現(xiàn)這msgId沒有被用到啊。一想,這剛好,我來用了。我看了一下用法,發(fā)現(xiàn)這邊不是直接使用SendResponse的,而是在外面包了一個枚舉類,代碼大概如下:

public enum Response {
    
    SUCCESS(1, "success"),
    PARAM_MISSING(2, "param is missing"),
    INVALID_xxxx(3, "xxxx is invalid"),
    INVALID_xxxx(4, "xxxx is invalid"),
    
    private SendResponse sendResponse;
    
    private Response(int errCode, String errInfo) {
        sendResponse = new SendResponse();
        sendResponse.setMsgId(0);
        sendResponse.setErrCode(errCode);
        sendResponse.setErrInfo(errInfo);
    }

    public SendResponse getSendResponse() {
        return sendResponse;
    }

}

有了枚舉使用起來就很簡單了,比如我發(fā)現(xiàn)小王某個參數(shù)傳進(jìn)來有問題,我反手就是:

Response.PARAM_ERROR

service系統(tǒng)主要做了兩件事

判斷參數(shù)/類型,各種業(yè)務(wù)邏輯有沒有問題,將小王帶過來的參數(shù)封裝成Task對象

將Task對象放到消息隊列里邊

要明確的是:等到整一個調(diào)用鏈結(jié)束(將Task對象放到消息隊列中),才會將sendResponse對象返回出去。而又因為可能要判斷的地方有點多,所以我們這邊是這樣設(shè)計了一個Map來存儲數(shù)據(jù),這個Map貫穿整條鏈路

// 首先將sendResponse默認(rèn)設(shè)置為success,也就是代碼如下:
map.put("sendResponse",Response.SUCCESS);

// 如果中途某個地方可能有問題了,那我們將Map中sendResponse進(jìn)行修改
map.put("sendResponse",Response.ERROR);

// 等整條鏈路完成,從Map拿出sendResponse返回
return map.get("sendResponse");

于是我要做的就是:在將SendResponse返回之前,我生成一個唯一的msgId,并插入到SendResponse對象里邊就好了。

Response.getSendResponse().setMsgid(uuid);

這個需求完成得非???,簡單測試了一下也沒毛病,就果斷上線了。小王用了一陣子也沒說有什么問題,于是這個需求就交付了。

三、出現(xiàn)問題

昨天,小王告訴我:“我這邊郵件發(fā)送失敗啦,有msgId,看下是什么原因造成的“

于是我就去撈線上的日志,發(fā)現(xiàn)根據(jù)他給出的msgId,我這邊打出的日志都不是發(fā)送郵件的(而是其他Task的日志)。我這就慌了,難道我們這個系統(tǒng)出問題了?

心理活動:msgId能夠唯一標(biāo)識這條Task,而小王發(fā)給我的msgId,卻是別的Task的內(nèi)容。是不是出大問題啦(錯亂消費?數(shù)據(jù)全亂了?),驚慌失措

然后,他那邊繼續(xù)補(bǔ)充:

之后發(fā)現(xiàn)郵件是發(fā)送成功的,但是他拿到部分的msgId是別的Task的,不是郵件的。于是只能先比對剩下的郵件是否有問題,再看看MsgId是什么原因。

四、尋找問題

現(xiàn)有的條件是:

那批郵箱發(fā)送是成功的

小王拿到了別的Task的msgId

所以,判斷系統(tǒng)是沒問題的,只是msgId在并發(fā)的過程中出了問題(拿到其他Task的msgId了)

于是我就去找原因啦,在查代碼的時候發(fā)現(xiàn)前同事還在Service系統(tǒng)中的某個類留了一個注解@NotThreadSafe。我就覺得肯定是中途哪個地方我沒注意到,導(dǎo)致小王拿到了其他Task的msgId。

人肉Debug了一個午休的時間還是沒找出來:每個線程都獨有一份的操作對象,對象的屬性都沒有逸出(都在方法內(nèi)部操作),跟著整塊鏈路一直傳遞,直至鏈路結(jié)束。

后來,一想,我應(yīng)該只看msgId生成的地方就好了呀。才發(fā)現(xiàn),項目里邊用的是枚舉??!

// 首先將sendResponse默認(rèn)設(shè)置為success,也就是代碼如下:
map.put("sendResponse",Response.SUCCESS);

// 如果中途某個地方可能有問題了,那我們將Map中sendResponse進(jìn)行修改
map.put("sendResponse",Response.ERROR);

// 把response的msgId的值設(shè)置為當(dāng)前Task綁定的值
map.get("sendResponse").setMsgid(uuid);

// 等整條鏈路完成,從Map拿出sendResponse返回
return map.get("sendResponse");

醒悟

現(xiàn)在我有50個線程,每個線程在處理數(shù)據(jù)的時候都會有一個默認(rèn)的sendResponse對象,這個對象是用枚舉來標(biāo)識Response.SUCCESS。所以,這50個線程都共享著這個sendResponse對象

50個線程共享著這個sendResponse對象,每個線程都可以修改sendResponse里邊的msgId屬性,這就自然是線程不安全的。

所以小王能拿到其他Task的msgId(小王的線程設(shè)置完msgId之后,還沒返回,三歪的線程又更改了一次msgId,導(dǎo)致小王拿到三歪的msgId了)

總結(jié):

終于知道為啥當(dāng)初前同事在代碼上保留了msgId屬性,但是沒有使用這個屬性。

使用枚舉就不應(yīng)該帶 有狀態(tài)的屬性(能修改、可變的屬性)

最后
樂于輸出干貨的Java技術(shù)公眾號:Java3y。公眾號內(nèi)有200多篇原創(chuàng)技術(shù)文章、海量視頻資源、精美腦圖,關(guān)注即可獲??!

覺得我的文章寫得不錯,點!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/75826.html

相關(guān)文章

  • 一次愚蠢經(jīng)歷--String不可變性

    摘要:文本已收錄至我的倉庫,歡迎記錄一次在寫代碼時愚蠢的操作,本文涉及到的知識點不可變性一交代背景我這邊有一個系統(tǒng),提供一個接口去發(fā)送短信。 前言 只有光頭才能變強(qiáng)。文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y 記錄一次在寫代碼時愚蠢的操作,本文涉及到的知識點:String不可變性 一、交代背景 我這邊有一個系統(tǒng),...

    woshicixide 評論0 收藏0
  • 一次 Booking 線上面試中遇到問題

    從事 Android 開發(fā)工作要滿 5 年了,雖然明白自己技術(shù)很一般,但是也總是期望能夠有機(jī)會進(jìn)入更好的平臺發(fā)展。這不,因為機(jī)緣巧合有了一次 Booking 的面試邀請(是在 hackerrank 上),然后開始臨時抱佛腳 (leetcode 走起),最終選擇了一個周末去完成線上測試,結(jié)果我完全沒預(yù)料到。本以為會被某道題的邏輯繞昏,結(jié)果哪知道被標(biāo)準(zhǔn)輸入這個東西卡得死死的,現(xiàn)在就記錄一下這次非常糟...

    lykops 評論0 收藏0
  • 一次狂懟多線程面經(jīng)

    摘要:最近面試一家有直播業(yè)務(wù)的公司,明顯感覺到對多線程的理解有一些要求。第一輪面試大概就面了分鐘左右,一輪下來口干舌燥。下面對面試題做了下簡單分類,分享給大家。 最近面試一家有直播業(yè)務(wù)的公司,明顯感覺到對多線程的理解有一些要求。第一輪面試大概就面了 70 分鐘左右,一輪下來口干舌燥。 下面對面試題做了下簡單分類,分享給大家。 多線程 有什么方法可以監(jiān)控線程的狀態(tài) synchronized ...

    Scott 評論0 收藏0
  • 一次慘痛面試經(jīng)歷

    摘要:把內(nèi)存分成兩種,一種叫做棧內(nèi)存,一種叫做堆內(nèi)存在函數(shù)中定義的一些基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配。堆內(nèi)存用于存放由創(chuàng)建的對象和數(shù)組。 一次慘痛的阿里技術(shù)面 就在昨天,有幸接到了阿里的面試通知,本來我以為自己的簡歷應(yīng)該不會的到面試的機(jī)會了,然而機(jī)會卻這么來了,我卻沒有做好準(zhǔn)備,被面試官大大一通血虐。因此,我想寫點東西紀(jì)念一下這次的經(jīng)歷,也當(dāng)一次教訓(xùn)了。其實面試官大大...

    CoorChice 評論0 收藏0

發(fā)表評論

0條評論

Forelax

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<