細說在一次中間件開發中如何實現每次業務調用的唯一標識之流水號生成以及應用

在web系統中,誰也不能保證每一次的業務調用都能正常的執行,在這樣的情況下,我們該如何去處理?

如果能保證每一次的業務調用的結果都持久化到數據庫,而出現異常後,能根據數據庫記錄去檢索日誌文件,定位到異常信息,進而分析異常快速找到異常產生原因,我覺得這是一個很好的處理方案,核心是確保每一次義務調用都可追溯。

由於前段時間工作需要,寫了一個消息推送的中間件,其中業務涉及到上述問題和解決方案,所以特意寫寫如何去解決。

0x01 爲何

話說,在公司實習半年多,一直寫業務,曾經多次遇到項目中需要調用短信api、app通知推送api,而在每個項目中都加入這些邏輯代碼,會讓人特別煩惱。並且,運營朋友經常性的過來詢問:“我能不能看看某某號碼的短信驗證碼”等等,而需要看到這樣的信息有兩種方法

一:找老闆登錄短信服務平臺查看記錄

二:通過在項目中集成數據庫對記錄持久化

第一種方法顯得特別麻煩,而第二種卻要增加特別多的代碼並且使用到數據庫。兩種方法都不太令人滿意,故而一直想找到一個好的方案。

0x02 剛剛好

半個月前突然接到pm的需要,要求實現一個集中管理的消息推送中間件。消息推送中間件實現了短信發送和app通知推送,調用記錄持久化到數據庫,中間件通過遠程調用的方式提供給分佈式環境中的每一個系統使用。

image

0x03 設計

在設計這個消息推送中間件時,任何設計都基於一個前提:
1. 每一次服務調用都可追溯

初步業務邏輯的設計如下:

image

在短信發送邏輯中捕抓異常(try),然後(catch)輸出異常日誌,在finally中把調用記錄和結果持久化到數據庫中。但如果服務器日誌中信息比較多的情況下,查找異常日誌將會成爲非常耗時的工作。

因此,想到了銀行業務系統中的方法,使用流水號機制,只要確保每一次業務調用中,流水號唯一即可,而流水號會隨着異常日誌輸出作爲日誌的唯一標識,以及數據庫記錄的唯一標識,這樣數據庫記錄和異常日誌就有了一個關聯的信息。

而經過改進的流程是這樣的:

image

0x04 核心實現

如何實現每一次業務調用都唯一的流水號生成?這裏巧妙的利用了數據庫的主鍵自增生成原理,每一次啓動中間件後的首次調用業務中往數據庫插入一條記錄,得到一個新的id:

private volatile Long startServerSequence;
if (null == startServerSequence) {
    synchronized (SerialNumberServiceImpl.class) {
        if (null == startServerSequence) {
            ServerStartTimeBean serverStartTimeBean = new ServerStartTimeBean();
            serverStartTimeBean.setDateTime(new Timestamp(System.currentTimeMillis()));
            Long sequence = serverStertTimeDao.insertAndReturnId(serverStartTimeBean);
            if (null == sequence) {
                throw new RuntimeException("服務器啓動後首次請求生成流水號異常,請求數據庫獲取服務啓動序列失敗!");
            } else {
                startServerSequence = sequence;
            }
        }
    }
}

image

得到的id作爲流水號的前綴,從而實現每一次中間件啓動後的流水號唯一性。

然每次啓動後需要確保每個業務調用都具有一個唯一性的流水號的話,則利用到了java中的原子類。

private AtomicLong currentProcessSequence = new AtomicLong(1000);

這個AtomicLong作爲流水號的後綴與數據庫自增生成的id組成一個唯一性的流水號,每一次業務的調用都執行AtomicLong的getAndIncrement方法,實現行AtomicLong的自增。

private static final String SEPARATOR = "_";
private volatile Long startServerSequence;
private AtomicLong currentProcessSequence = new AtomicLong(1000);

public String generateSerialNumber() throws Exception {
    if (null == startServerSequence) {
        synchronized (SerialNumberServiceImpl.class) {
            if (null == startServerSequence) {
                ServerStartTimeBean serverStartTimeBean = new ServerStartTimeBean();
                serverStartTimeBean.setDateTime(new Timestamp(System.currentTimeMillis()));
                Long sequence = serverStertTimeDao.insertAndReturnId(serverStartTimeBean);
                if (null == sequence) {
                    throw new RuntimeException("服務器啓動後首次請求生成流水號異常,請求數據庫獲取服務啓動序列失敗!");
                } else {
                    startServerSequence = sequence;
                }
            }
        }
    }
    return startServerSequence+SEPARATOR+currentProcessSequence.incrementAndGet();
}

如此,我們就實現了每一次中間件的啓動的每一次業務調用都有唯一的流水號,用於異常日誌與數據庫調用記錄的關聯,達到每一次業務都可追溯,每一個異常都能快速定位。

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