資料庫重構之路,以 OrientDB 到 NebulaGraph 為例

發表於2023-09-19

“本文由社群使用者 @阿七從第一視角講述其團隊重構圖資料庫的過程,首發於阿七公眾號「淺談架構」”

原文出處:https://mp.weixin.qq.com/s/WIJNq-nuuAGtMjYo5rPLyg

一、寫在前面

讀過我公眾號文章的同學都知道,我做過很多次重構,可以說是“重構釘子戶”,但是這次,重構圖資料庫 OrientDB 為 NebulaGraph(https://www.nebula-graph.com.cn/),可以說是我做過最艱難的一次重構。

那這篇文章就來聊聊,圖資料庫重構之路。

二、難點在哪裡

  1. 歷史包袱重,原來使用 OrientDB 系統是2016年開始開發的,邏輯很複雜,歷史背景完全不清楚。
  2. 業務不瞭解,我們是臨時接的大資料需求,之前沒有參與過這塊業務,完全不瞭解。
  3. 技術棧不瞭解,圖資料庫是第一次接觸(團隊中也沒有人瞭解),OrientDB 和 NebulaGraph 之前都沒有接觸過,原來老系統大部分程式碼是 Scala 語言寫的,系統中使用的 HBase、Spark、Kafka,對於我們也比較陌生。
  4. 時間緊迫

總結來說: 業務不瞭解,技術棧不熟悉

tips: 大家思考一個問題,在業務和技術棧都不熟的情況下,如何做重構呢?

三、技術方案

下面介紹一下本次重構技術方案

1、遷移背景

獵戶座的圖資料庫 OrientDB 存在效能瓶頸和單點問題,需升級為 NebulaGraph。

老系統是用使用技術棧無法支援彈性伸縮,監控報警設施也不夠完善。

具體的使用痛點後續我將會寫一篇文章具體講述下,本篇就不詳細展開了。

2、調研事項

注:既然業務都不熟悉,那我們都調研了哪些東西呢?

  1. 對外介面梳理:梳理系統所有對外介面,包括介面名、介面用途、請求量 QPS、平均耗時,呼叫方(服務和 IP);
  2. 老系統核心流程梳理:輸出老系統整理架構圖,重要的介面(大概 10 個)輸出流程圖;
  3. 環境梳理:涉及到的需要改造的專案有哪些,應用部署、MySQL、Redis、HBase 叢集 IP,及目前線上部署分支整理;
  4. 觸發場景:介面都是如何觸發的,從業務使用場景出發,每個介面至少一個場景覆蓋到,方便後期功能驗證;
  5. 改造方案:可行性分析,針對每一個介面,如何改造(OrientDB 語句改為 NebulaGraph 查詢語句),入圖(寫流程)如何改造;
  6. 新系統設計方案: 輸出整理架構圖,核心流程圖。

3、專案目標

​完成圖資料庫資料來源 OrientDB 改造為 NebulaGraph,重構老系統統一技術棧為 Java,支援服務水平擴充套件。

4、整體方案

我們採用了比較激進的方案:

  1. 從呼叫介面入口出發,直接重寫底層老系統,影響面可控;
  2. 一勞永逸,方便後期維護;
  3. 統一 Java 技術棧、接入公司統一服務框架,更利於監控及維護;
  4. 基礎圖資料庫應用邊界清晰,後續上層應用接入圖資料庫更簡單。

​注:這裡就貼調研階段畫的圖,圖涉及業務,我這裡就不列舉了。

5、灰度方案

灰度方案

  • 寫請求:採用同步雙寫
  • 讀請求:按流量從小到大陸續遷移、平滑過渡

灰度計劃

階段一階段二階段三階段四階段五階段六階段七
0%1‰1%10%20%50%100%
同步雙寫, 流量回放取樣對比,100% 透過、預計灰度 2 天灰度 2 天灰度 2 天灰度 5 天、此階段要壓測灰度 2 天灰度 2 天-

注:

  1. 配置中心開關控制,有問題隨時切換,秒級恢復。
  2. 讀介面遺漏無影響, 只有改到的才會影響。
  3. 使用引數 hash 值作為 key,確保同一引數多次請求結果一致、滿足 abs(key) % 1000 < X ( 0< X < 1000, X 為動態配置 ) 即為命中灰度。

題外話:其實重構,最重要的就是灰度方案,這個我在之前文章《淺談這些年做過的千萬級系統重構專案》也提到過。本次灰度方案設計比較完善,大家重點看階段一、在灰度放量之前,我們用線上真實的流量去非同步做資料對比,對比完全透過之後,再放量,本次對比階段比預期長了很多(實際上用了 2 周時間,發現了很多問題)。

6、資料對比方案

未命中灰度

未命中灰度流程如下:

先呼叫老系統,再根據是否命中取樣(取樣比例配置 0% ~ 100%),命中取樣會傳送 MQ,再在新系統消費 MQ,請求新系統介面,於老系統介面返回資料進行 JSON 對比。對比不一致,傳送企業微信通知,實時感知資料不一致,發現並解決問題。

反之亦然!!

7、資料遷移方案

  1. 全量(歷史資料):寫指令碼全量遷移,上線期間產生不一致從 MQ 消費近 3 天資料
  2. 增量:同步雙寫(寫的介面很少,寫請求 QPS 不高)

8、改造案例 - 以子圖查詢為例

改造前:

@Override
    public MSubGraphReceive getSubGraph(MSubGraphSend subGraphSend) {
        logger.info("-----start getSubGraph------(" + subGraphSend.toString() + ")");
        MSubGraphReceive r = (MSubGraphReceive) akkaClient.sendMessage(subGraphSend, 30);
        logger.info("-----end getSubGraph:");
        return r;
    }

改造後:

定義灰度模組介面

public interface IGrayService {
    /**
     * 是否命中灰度 配置值 0 ~ 1000  true: 命中  false:未命中
     *
     * @param hashCode
     * @return
     */
    public boolean hit(Integer hashCode);

    /**
     * 是否取樣 配置值 0 ~ 100
     *
     * @return
     */
    public boolean hitSample();

    /**
     * 傳送請求-響應資料
     * @param requestDTO
     */
    public void sendReqMsg(MessageRequestDTO requestDTO);

    /**
     * 根據
     * @param methodKeyEnum
     * @return
     */
    public boolean hitSample(MethodKeyEnum methodKeyEnum);
}

介面改造如下, kgpCoreService 請求到 kgp-core 新服務,介面業務邏輯和 orion-x 保持一致、底層圖資料庫改為查詢 NebulaGraph:

@Override
    public MSubGraphReceive getSubGraph(MSubGraphSend subGraphSend) {
        logger.info("-----start getSubGraph------(" + subGraphSend.toString() + ")");
        long start = System.currentTimeMillis();
        //1. 請求灰度
        boolean hit = grayService.hit(HashUtils.getHashCode(subGraphSend));
        MSubGraphReceive r;
        if (hit) {
            //2、命中灰度 走新流程
            r = kgpCoreService.getSubGraph(subGraphSend); // 使用Dubbo呼叫新服務
        } else {
            //這裡是原來的流程 使用的akka通訊
            r = (MSubGraphReceive) akkaClient.sendMessage(subGraphSend, 30);
        }
        long requestTime = System.currentTimeMillis() - start;

        //3.取樣命中了傳送資料對比MQ 
        if (grayService.hitSample(MethodKeyEnum.getSubGraph_subGraphSend)) {
            MessageRequestDTO requestDTO = new MessageRequestDTO.Builder()
                    .req(JSON.toJSONString(subGraphSend))
                    .res(JSON.toJSONString(r))
                    .requestTime(requestTime)
                    .methodKey(MethodKeyEnum.getSubGraph_subGraphSend)
                    .isGray(hit).build();
            grayService.sendReqMsg(requestDTO);
        }
        logger.info("-----end getSubGraph: {} ms", requestTime);
        return r;
    }

9、專案排期計劃

投入人力: 開發 4 人,測試 1 人

主要事項及耗時如下:

10、所需資源

略,這裡不展開講述。

四、重構收益

經過團隊 2 個月奮鬥,目前已完成灰度階段,收益如下

  1. NebulaGraph 本身支援分散式擴充套件,新系統服務支援彈性伸縮,整體支援效能水平擴充套件;
  2. 從壓測結果看,介面效能提升很明顯,可支撐請求遠超預期;
  3. 接入公司統一監控、告警,更利於後期維護。

五、總結

本次重構順利完成,感謝本次一起重構的小夥伴,以及大資料、風控同學支援,同時也感謝 NebulaGraph社群(https://discuss.nebula-graph.com.cn/),我們遇到一些問題提問,也很快幫忙解答。


謝謝你讀完本文 (///▽///)

如果你想嚐鮮圖資料庫 NebulaGraph,記得去 GitHub 下載、使用、(^з^)-☆ star 它 -> GitHub;和其他的 NebulaGraph 使用者一起交流圖資料庫技術和應用技能,留下「你的名片」一起玩耍呀~

2023 年 NebulaGraph 技術社群年度徵文活動正在進行中,來這裡領取華為 Meta 60 Pro、Switch 遊戲機、小米掃地機器人等等禮品喲~ 活動連結:https://discuss.nebula-graph.com.cn/t/topic/13970