天啦擼,甩鍋不成,程序員只好含淚修復bug

1. 前言

最近項目有個需求:一個內部派單系統,同事反饋:來一個活兒系統就立馬派給我,我的工作永無結束之日。程序員小哥們能不能在系統控制每日派單上限,每日派單超過這個上限就別分給我了,我想早點下班偶遇我未來的女朋友。嚶嚶嚶…
“年輕人,勇敢的去追求愛情吧,剩下的交給我”
望着同事心滿意足下班的背影,我冷笑,“哼,年輕,絲毫不懂工作的樂趣。”

2. 開心的改造代碼

咱先梳理下邏輯:

  • 1.一天24小時隨時都有可能來新訂單。原本邏輯系統每分鐘掃描未派出去的訂單,並根據某些規則下發給用戶。
  • 2.不同訂單會派給不同角色下的用戶。

戴上我山寨的earPods Pro 閃瞎眼Max,“愛情買賣”調到最大聲,音樂一放,我最閃亮,翹着二郎腿就開幹, 在最終派單的代碼中添加如下邏輯:

//是否強制分配
//是否有配置上限,有則判斷:超上限不分配。沒有配置則能直接通過
if (!force && dispatchOverLimitPerDay(orderType, userId)) {
     log.warn("經辦人:{},本日已超出類型:{}的分配上限,系統不分配orderId:{}", userId, orderType, orderId);
     return;
}

//派單邏輯
...
orderService.save(entity);

方法內查詢用戶當天已經派了多少單,當天還剩餘可派多少:

 //當天開始時間和結束時間
Date date = new Date();
Date beginOfDate = DateUtil.getBeginOfDate(date);
Date endOfDate = DateUtil.getEndOfDate(date);

//當天某一角色下用戶,當天已分配的數量
List<DispatchOrderNumLimitResDTO> dayDispatchedInfo = orderService.queryDispatchedOrderNumGroupByUserId(orderType, beginOfDate, endOfDate);
 
// 查找某一角色下用戶配置的每日上限,無則默認無上限
List<DispatchCaseNumLimitInfo> dayLimitInfo = findLimitConfig();

//待分配的用戶當天是否還有剩餘
int remainNum = getRemainNum(userId,dayDispatchedInfo,dayLimitInfo);
...
...
return remainNum > 0; 

本地測試通過,測試驗收通過,生產發版上線,需求標記爲已解決。一套流程走下來,如德芙般絲滑。合上電腦,揹包,打卡下班一氣呵成。
在這裏插入圖片描述

3. 天吶,系統貌似出現了問題

先介紹下項目:

  • springboot + mybatis-plus + dubbo
  • 分佈式定時任務採用quartz
  • 預發佈1臺服務器,生產3臺服務器
  • MySQL 5.6.42

第二天上班收到測試反饋:系統超出了配置的用戶派單上限,原本配置每日分配上限爲12,實際分配了18。如下:
測試反饋分配了18條
遇到bug不要慌,甩鍋已經是基本操作了。看數據庫的確是凌晨00:00系統自動派單的,看了系統日誌,同事昨晚最遲操作系統是在20:54。排除人爲因素,那就是系統層面的原因了。

4. 一個鍵盤一包煙,一個BUG找一天

接下來就是一天的排查,有以下幾個懷疑的點:

  1. 每日上限數配置配置有誤/被修改過
  2. 系統查詢條件有問題
  3. 系統的定時任務由於併發導致查詢出髒數據,也就導致系統超出當天的分配上限

4.1 每日上限數配置配置有誤/被修改過

這個純粹是想甩鍋,去nacos查看配置確認:pre和prod上的nacos配置都是12。問了幾個同事,他們都表示沒有修改過nacos。
況且,上線當天(6/10 18:30上線,6/11早上發現問題)系統分配是剛好12條上限,且有剩餘其他訂單等待11號分配。此路不通。
疑點:上線當天正常,第二天卻超上限分配,差別是時間。

4.2 系統查詢條件有問題

查詢邏輯爲:查詢一天的開始~結束時間段之內,每個人派了多少單。
SQL語句如下:

select order_id,user_id,count(1) as dispatchedNum
from wait_order
where 
is_deleted = false and orderType = #{orderType} 
and create_time >= #{beginOfDate}
and create_time <= #{endOfDate}
order by userId

是不是獲取開始時間和結束時間有問題?
我在本地跑main方法輸出這兩個時間,發現也是正常的:
獲取時間是正常的00:00:00 ~ 23:59:59,時間獲取是正常的。我手動替換掉#{beginOfDate}等佔位參數去生產環境跑此條SQL,能把當天分配的所有訂單都查詢出來(12條正常的 + 6條額外分配的)。那我判定SQL是沒問題的。

4.3 系統的定時任務由於併發導致查詢出髒數據

這個就能很好的解釋分配超限問題了。由於同事小M寫的分佈式定時任務有問題,導致同時有倆線程A,B同時執行分配。

  • 假設A線程在某一刻查詢出某人當天分配了6條,準備分配剩餘6條。
  • B線程此時也查詢出6條,並且把剩餘6條給分配了。(B:“我就是比你快” A:“老哥,男人不能太快”)
  • A繼續分配6條訂單,這樣就導致了超限分配

科學~ 合理~
跑去同時小M位置上,反手甩給他一口鋥光瓦亮的鍋
轉身背鍋你以爲這就結束了?
小M反手就是程序員靈魂提問:“請先看下文檔,請仔細看文檔,請熟讀並背誦文檔”。
okok我錯了,定時任務沒有問題。(其實項目的定時任務一直沒出過問題)
甩鍋
此外,還懷疑了數據庫主從沒同步的問題,但都否決了。
第一天排查無果,加上了幾行log日誌後下班回家。

5. 柳暗花明

第二天一早看數據庫,超限派單問題依然存在——某賬戶設置一天派單8件,但實際派單12件。日誌打印如下:

2020-06-12 00:00:00.175 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[]
2020-06-12 00:00:00.386 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=1, remainNum=7)]
2020-06-12 00:00:00.437 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=1, remainNum=7)]
2020-06-12 00:00:00.487 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=1, remainNum=7)]
2020-06-12 00:00:00.632 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=1, remainNum=7)]
2020-06-12 00:00:00.681 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=2, remainNum=6)]
2020-06-12 00:00:00.732 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=3, remainNum=5)]
2020-06-12 00:00:00.781 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=4, remainNum=4)]
2020-06-12 00:00:00.831 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=5, remainNum=3)]
2020-06-12 00:00:00.882 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=6, remainNum=2)]
2020-06-12 00:00:00.931 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=7, remainNum=1)]
2020-06-12 00:00:00.978 [pool-124-thread-1] INFO   - 每日分配上限剩餘信息:[XXXDTO(userId='測試1', dispatchedNum=8, remainNum=0)]
2020-06-12 00:00:00.978 [pool-124-thread-1] WARN - 經辦人:測試1,本日已超出類型:3的分配上限,系統不分配orderId:XXX


我從MySQL數據中抓住了元兇,當天插入數據如下:
6-12日數據

疑問:

  • 第3,4,5,6次派單dispatchedNum(已派單)數量仍爲1?
  • 系統實際多派了4單,從數據上看,有4單插入的時間均爲00:00:00。我回頭看昨天的派單,發現11號數據中,有6單插入時間也爲00:00:00,數量恰好是多派的單!我們再看一下前一天的數據:
    前一天的數據
    我回頭看看工具類源碼:
    public static Date getBeginOfDate(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(11, 0);
        calendar.set(12, 0);
        calendar.set(13, 0);
        return calendar.getTime();
    }

    public static Date getEndOfDate(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(11, 23);
        calendar.set(12, 59);
        calendar.set(13, 59);
        return calendar.getTime();
    }

問題定位!
getBeginOfDate方法,只把時,分,秒設置爲0,未把毫秒也設置爲0。同理,getEndOfDate也未把毫秒設置爲999,導致我的SQL查詢語句實際爲帶了毫秒查詢:
帶毫秒查詢至此,看官們可能還有最後一個疑問:爲什麼中途會有條記錄爲00:00:01的數據呢?你聽我給你 狡辯 解釋。
在這裏插入圖片描述定時任務調用service.save(entity)方法,有可能打在不同的svc上,而這三個svc系統時間可能有毫秒時間的差別,所以~~~

6. 解決BUG

//getBeginOfDate方法
calendar.set(Calendar.MILLISECOND,0);

//getEndOfDate方法
calendar.set(Calendar.MILLISECOND,999);

問題出在了工具類上,我的心情:在這裏插入圖片描述完~
(開頭的同事仍在努力尋找愛情ing~)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章