只有光頭才能變強。
文本已收錄至個人GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y
記一次在工做中愚蠢的操做,本文關鍵字:線程安全java
(我怎麼每天在寫Bug啊)--本文適合新手觀看git
我這邊有一個系統,提供一個RPC接口去發送各類信息(好比短信、郵件、微信)等等渠道。我這邊的系統架構是這樣的:github
系統架構
歸納:service系統提供一個RPC接口,別人調用我提供的接口,我在service系統中對這個消息進行判斷、拼接等等業務邏輯,最後會將這個消息放到消息隊列裏邊。sender系統會消費消息隊列裏邊的數據,而後發送消息面試
例子:小王調用咱們的RPC接口,想要發送郵件。我對郵件的參數進行判斷和拼裝成一個我這邊定義好的Task,將這個Task丟到消息隊列裏邊。sender系統消費這個Task,調用java.mail的API完成發送郵件的功能。安全
小王調用咱們這個RPC接口,只要service系統把這個task丟到消息隊列裏邊去,咱們就返回response給小王。微信
而小王的這些郵件又十分在乎是否成功發送出去了,若是發送失敗了他那邊須要重發。因而,他監聽咱們DB的binlog,根據binlog的信息來判斷是否須要重發。架構
因爲種種的緣由,小王但願調用咱們RPC接口的時候就能拿到一個惟一的標識好讓他去判斷這封郵件是成功仍是失敗併發
流程圖異步
上面肯定好需求和思路以後,我這邊就去看返回給小王的response對象,一看,發現已經有msgId字段了ide
public class SendResponse { // 錯誤碼 private int errCode; // 錯誤信息 private String errInfo; // messageId private long msgId; }
我搜了一下這個字段的信息ctrl + shift + f,發現這msgId沒有被用到啊。一想,這恰好,我來用了。我看了一下用法,發現這邊不是直接使用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; } }
有了枚舉使用起來就很簡單了,好比我發現小王某個參數傳進來有問題,我反手就是:
Response.PARAM_ERROR
service系統主要作了兩件事
// 首先將sendResponse默認設置爲success,也就是代碼以下: map.put("sendResponse",Response.SUCCESS); // 若是中途某個地方可能有問題了,那咱們將Map中sendResponse進行修改 map.put("sendResponse",Response.ERROR); // 等整條鏈路完成,從Map拿出sendResponse返回 return map.get("sendResponse");
因而我要作的就是:在將SendResponse返回以前,我生成一個惟一的msgId,並插入到SendResponse對象裏邊就行了。
Response.getSendResponse().setMsgid(uuid);
在返回sendResponse以前插入msgId就行了
這個需求完成得很是快,簡單測試了一下也沒毛病,就果斷上線了。小王用了一陣子也沒說有什麼問題,因而這個需求就交付了。
昨天,小王告訴我:「我這邊郵件發送失敗啦,有msgId,看下是什麼緣由形成的「
出問題啦
因而我就去撈線上的日誌,發現根據他給出的msgId,我這邊打出的日誌都不是發送郵件的(而是其餘Task的日誌)。我這就慌了,難道咱們這個系統出問題了?
繼續補充信息
以後發現郵件是發送成功的,可是他拿到部分的msgId是別的Task的,不是郵件的。因而只能先比對剩下的郵件是否有問題,再看看MsgId是什麼緣由。
解決首要的問題
現有的條件是:
因而我就去找緣由啦,在查代碼的時候發現前同事還在Service系統中的某個類留了一個註解@NotThreadSafe。我就以爲確定是中途哪一個地方我沒注意到,致使小王拿到了其餘Task的msgId。
人肉Debug了一個午休的時間仍是沒找出來:每一個線程都獨有一份的操做對象,對象的屬性都沒有逸出(都在方法內部操做),跟着整塊鏈路一直傳遞,直至鏈路結束。看似沒啥毛病啊,懷疑是否是方向錯了。
後來,一想,我應該關注msgId生成以及可能會變更的地方就行了呀。才發現,項目裏邊用的是枚舉啊!
// 首先將sendResponse默認設置爲success,也就是代碼以下: map.put("sendResponse",Response.SUCCESS); // 若是中途某個地方可能有問題了,那咱們將Map中sendResponse進行修改 map.put("sendResponse",Response.ERROR); // 把response的msgId的值設置爲當前Task綁定的值 map.get("sendResponse").setMsgid(uuid); // 等整條鏈路完成,從Map拿出sendResponse返回 return map.get("sendResponse");
醒悟:
總結:
200多篇原創技術文章海量視頻資源精美腦圖面試題長按掃碼可關注獲取