一個 Blink 小白的成長之路

寫在前面

寫過blink sql的同學應該都有體會,明明寫的時候就很順滑,小手一抖,洋洋灑灑三百行代碼,一氣呵成。結果跑的時候,吞吐量就是上不去。導致數據延遲高,消息嚴重積壓,被業務方瘋狂吐槽。這時候,老鳥就會告訴你,同學,該優化優化你的代碼了,再丟過來一個鏈接,然後留下一臉懵逼的你。筆者就是這麼過來的,希望本文能幫助到跟我有過同樣困惑,現在還一籌莫展的同學。

背景故事

先說一下相關背景吧,筆者作爲一個剛入職阿里的小白,還處在水土不服的階段,就被臨危受命,改造數據大屏。爲什麼說臨危受命呢,首先是此時距雙十一僅剩一個月,再者,去年的雙十一,這個大屏剛過零點就出現問題,數據一動不動,幾個小時後開始恢復,但仍然延遲嚴重。此前,筆者僅有的實時計算開發經驗是storm,用的是stream API,對於blink這種sql式的API完全沒接觸過。接到這個需求的時候,腦子裏是懵的,靈魂三問來了,我是誰?我即將經歷什麼?我會死得有多慘?不是“此時此刻,非我莫屬”的價值觀喚醒了我,是老大的一句話,在阿里,不是先讓老闆給你資源,你再證明你自己,而是你先證明你自己,再用結果贏得資源,一席話如醍醐灌頂。然後就開始了一段有趣的故事~

壓測血案

要找性能問題出在哪兒,最好的方法就是壓測。這裏默認大家都對節點反壓有一定的瞭解,不瞭解的請先移步典型的節點反壓案例及解法

一開始是跟着大部隊進行壓測的,壓測的結果是不通過!!!一起參加壓測的有三十多個項目組,就我被點名。雙十一演練的初夜,就這樣傷心地流走了(╯°□°)╯︵ ┻━┻。西湖的水,全是我的淚啊。不過痛定思痛,我也是通過這次壓測終於定位到了瓶頸在哪裏。

瓶頸初現

數據傾斜
在做單量統計的時候,很多時候都是按商家維度,行業維度在做aggregate,按商家維度,不可避免會出現熱點問題。

hbase寫瓶頸
當時我在調大source分片數,並且也無腦調大了各個算子的資源之後,發現輸出RPS還是上不去,sink節點也出現了消息積壓。當時就判斷,hbase有寫瓶頸,這個我是無能爲力了。後來的事實證明我錯了,hbase的確有寫瓶頸,但原因是我們寫的姿勢不對。至於該換什麼姿勢,請繼續看下去。

神擋殺神

先來分析一下我們的數據結構(核心字段)
biz_date, order_code, seller_id, seller_layer, order_status, industry_id

我們group by的典型場景有

CREATE VIEW order_day_view AS
    SELECT
        industry_id,
        seller_layer,
        biz_date,
        count(distinct order_code) AS salesCount
    FROM
        order_view
    GROUP BY industry_id,seller_id,seller_layer,biz_date
;

總結下來就是,按賣家維度,行業維度什麼的,都非常容易出現數據傾斜。

數據傾斜其實有很多解法,這裏我不展開討論,只講我們這個案例的解法。
傾斜的原因,無非就是group by的字段出現了熱點,大量的消息都集中在了該字段少數幾個取值上。通常的解法是,在消息中選擇具備唯一性,或者預估會分佈比較均勻的字段。如果這個字段是整型的,可以直接取模(模數一般是節點的併發數),如果是字符串,可以先進行哈希計算,再取模,得到一個分片地址(本文取名爲bucket_id)。在接下來的所有aggregate算子中,都要把他作爲group by的key之一。

在我們這個案例中,我們選擇了order_code這個具備唯一性的字段。首先在源頭把分片地址算出來,加到消息裏面,代碼如下:

SELECT
o.biz_date, o.order_code, o.seller_id, o.seller_layer, o.order_status, o.industry_id, o.bucket_id
FROM (select *,MOD(hash_code(order_code), 32) AS bucket_id from order_stream) o

然後把這個bucket_id層層傳遞下去,在每一個需要group by的地方都在後面帶上bucket_id,例如:

CREATE VIEW order_day_view AS
    SELECT
        industry_id,
        seller_layer,
        biz_date,
        count(distinct order_code) AS salesCount,
        bucket_id
    FROM
        order_view
    GROUP BY industry_id,seller_id,seller_layer,biz_date,bucket_id
;

事實上,我一開始想到的是用下面tips裏的方法,結果就杵進垃圾堆裏了,性能問題是解了,但是計算出來的數據都翻倍了,明顯是錯的。至於我是怎麼發現這個問題,並分析其原因,再換了解法,又是另一段故事了。可以提前預告一下,是踩了blink撤回計算的坑,後面會再出一個專題來講述這個故事噠~

這裏還想再延伸一下,講講我的學習方法。如果讀者中有跟我一樣的小白,可能會奇怪,同樣是小白,爲何你這麼秀,一上來就搞壓測,還能準確地分析出性能的瓶頸在哪裏。其實有兩方面的原因,一方面是我有過storm的開發經驗,對實時計算中會遇到的坑還是有一定的認識;另一方面,是我沒說出來的多少個日日夜夜苦逼學習充電的故事。我的學習習慣是喜歡追根溯源,就找了很多介紹flink基本概念,發展歷史,以及跟流式和批處理計算框架橫向對比的各類博客。而且帶着kpi去學習和什麼包袱都沒有去學習,心態和學習效率是不一樣的。前者雖然效率更高,但是是以損害身心健康爲代價的,因爲學習過程中不可避免的會產生急躁情緒,然後就會不可避免的加班,熬夜,咖啡,再然後他們的好朋友,黑眼圈,豆豆,感冒就全來了。後者雖然輕鬆,但是什麼包袱都沒有,反而會產生懈怠,沒有壓力就沒有動力,這是人的天性,拗不過的。這就是矛盾的點,所以在阿里,經常提到“既要也要還要”,其實宣揚的是一種學會平衡的價值觀。至於怎麼平衡,嘻嘻,天知地知我知。對,只能自己去領悟怎麼平衡,別人教不會的。

概念有了一定的認知,下面就開始實踐了。整個實踐的過程,其實就是在不斷的試錯。我是一開始連反壓的概念都不知道的,一直在無腦的調大CU,調大內存,調高併發數,調整每兩個節點之間的併發數比例。寄希望於這樣能解決問題,結果當然是無論我怎麼調,吞吐量都是都風雨不動安如山。現在想想還是太年輕呀,如果這樣簡單的做法能解決問題,那那個前輩就絕對不會搞砸了,還輪的到我今天來解決。後來也是在無盡的絕望中想通了,不能再這麼無腦了,我要找其他法子。想到的就是在代碼層面動刀子,當然試錯的基本路線沒有動搖,前面也提到過,我一開始是想到的“加鹽”,也是在試錯。

學習方式決定了我做什麼事,都不可能一次成功。甚至有很多情況,我明知道這樣做是錯的,但我就是想弄明白爲什麼行不通,而故意去踩這個坑。不過也正是因爲試了很多錯,踩了很多坑,才挖出了更多的有價值的知識點,擴大了知識的邊界。

此時無聲勝有聲,送上幾句名言,與諸君共勉
塞翁失馬,焉知非福。---淮南子·人間訓
一切過往,皆爲序章。---阿里巴巴·行癲
學習就像跑步一樣,每一步都算數。---百阿·南秋

tips: 如果在消息本身中找不到分佈均勻的字段,可以考慮給每一條消息加上一個時間戳,直接使用系統函數獲取當前時間,然後再對時間戳進行哈希取模計算,得到分片地址。相當於強行在時間維度上對消息進行打散,這種做法也被形象的稱爲“加鹽”。

佛擋殺佛

上一段看下來,似乎只解決了數據傾斜的問題。之前還提到有一個hbase寫瓶頸問題,這個該如何解呢?

還是接着上面的思路繼續走下去,當我們把bucket_id一路傳遞下去,到了sink任務的時候,假設我們要按商家維度來統計單量,但是別忘了,我們統計的結果還按訂單號來分片了的,所以爲了得到最終的統計值,還需要把所有分片下的值再sum一下才行,這大概也是大多數人能想到的常規做法。而且我們現有的hbase rowKey設計,也是每個維度的統計數據對應一個rowKey的,爲了兼容現有的設計,必須在寫hbase之前sum一下。

但是筆者當時突發奇想,偏偏要反其道而行之,我就不sum,對於rowKey,我也給它分個片,就是在原來rowKey的基礎上,後面再追加一個bucket_id。就相當於原來寫到一個rowKey上的數據,現在把他們分散寫到64個分片上了。
具體實現代碼如下:

INSERT INTO hbase_result_sink
    SELECT
        CONCAT(businessRowkey, '|', bucket_id) AS businessRowkey,
        cast(uopAcceptCount as DECIMAL)
    from hashBucket_view

這樣一來,API也必須改造了,讀的時候採用scan模式,把所有分片都讀出來,然後求和,相當於把sum的工作轉移到API端了。
這樣做的好處在於,一方面可以轉移一部分計算壓力,另一方面,因爲rowKey只有一個,而我們寫rowKey的任務(即sink節點)併發數可能有多個,Java開發者應該都深有體會,多線程併發對一個變量進行累加的時候,是需要加鎖和釋放鎖的,會有性能損耗,可以猜測,hbase的寫瓶頸就在於此。後來的事實也證明,這種做法將輸出RPS提升了不止一個兩個檔次。

趕考當天

人事已盡,接下來就是關二爺的事了( ̄∇ ̄)。雙十一零點倒計時結束,大屏數字開始飆升起來,隨之一起的,還有我的腎上腺素。再看看數據曲線,延遲正常,流量峯值達日常的10倍。其實結果完全是在預期之內的,因爲從最後一次的壓測表現來看,100W的輸入峯值(日常的333倍),5W的輸出峯值(日常的400倍),都能穩穩的扛下來。出於數(懶)據(癌)安(晚)全(期)的角度考慮,很多大屏和數據曲線的截圖就不放出來了。

其實現在回過頭再看,此時的內心是平靜如水的。不是大獲全勝後的傲嬌,也不是退隱山林的怯懦。只是看待問題的心態變了。沒有翻不過的山,沒有邁不過的坎。遇事不急躁,走好當下的每一步就好,也不必思考是對是錯,因爲每一步都算數,最後總能到達終點。

浮生後記

筆者寫文章習慣帶一些有故事趣味性的章節在裏面,因爲我覺得純講技術,即使是技術人看起來也會相當乏味,再者純講技術的前提是作者具備真正透進骨髓去講述的功底,筆者自認爲還相差甚遠,只能加點魚目來混珠了。換個角度來看,純技術性的文章,觀賞性和權威性更強,每一句都是精華,這種咀嚼後的知識雖有營養飽滿,但是不是那麼容易消化,消化後能吸收多少,還有待確認。所以我力求展示我的咀嚼過程,更多是面向跟我一樣的小白用戶,如果覺得冗長,請各位讀者姥爺見諒~

本文作者:王科,花名伏難,先後經歷國內三大電商平臺,蘇寧,京東,阿里,電商大戰深度參與者。現任職阿里巴巴供應鏈事業部,從事業務平臺化改造、實時計算相關工作。信奉技術自由平等,希望通過簡單形象的語言,打破上層建築構建的知識壁壘,讓天下沒有難做的技術。

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