大家好,我是華為消費者雲的吳太銀。
我今天分享的主要是華為消費者雲服務使用Cassandra的應用場景和最佳實踐。我這個可能跟其他嘉賓分享的不太一樣,因為前幾個嘉賓講的實際上對Cassandra原生的代碼有一定的修改,而我們當前使用的是純粹的、原生的Cassandra,我們沒有做任何的修改。所以這個分享可以給一些想大量使用原生的Cassandra的朋友有比較好的借鑒意義。
我今天會大概從這三個方面來給大家介紹一下:第一個就是我們用Cassandra的一些使用歷程,經驗和教訓,以及我們當前的規模;第二個就是我們現網遇到的典型問題,我之前跟組織者交流,因為我們當前的規模比較大,他主要是想看我們在用到典型的ToC場景下,在用到大規模,大數據量的情況下,在現網有哪些典型的問題,這個對大家應該有一定的啟示作用(哪些是Cassandra的雷區,是不能碰的,如果你把這些避免掉,就不會出大問題);第三個就是我們使用Cassandra總結出來的最佳實踐,因為我們現在所有的終端業務,基本上都會用到Cassandra,我們的業務場景非常的復雜,必須有些設計上的,包括表結構的設計上的約束,不能隨便用,因為隨便用它一定會有問題。這個在前面的各位演講嘉賓也經常提到:我們要順着它來用。
我們現在先看一下為什么選Cassandra。大家可能都比較清楚了,我就簡單的說一下:一個是去中心化的部署,不但簡單,而且擴展性很好,可以輕松應對業務發展帶來的數據容量和性能上的要求;第三個是它天然支持多DC的部署,我們當前是一主一備,再加容災,三個數據中心,它天然的支持(這種部署),內部自動同步;第四個是它的監控指標和監控接口非常完善,通過nodetool和JMX可以非常容易監控到Cassandra原生的各個指標,這個在我們后面的幻燈片里會看到,這一塊是非常重要的,在現網,特別是你在集群規模變大了之后,你需要快速恢復一些故障的時候,沒有這些東西,你是做不到的;第五個是它的這個開源社區確實很活躍,包括穩定的版本演進,可以讓我們不停的選擇它。
這個(幻燈片)是我們的使用歷程,給大家看一下。我們實際上是從2010年開始就使用了。Cassandra是差不多2008年開始在Apache孵化,我們差不多是跟孵化同時的時間開始接觸,一開始是0.7版本。Cassandra在我們這邊用大概分兩個階段:前一個階段,可能是一個相對來說比較失敗的一個經歷,因為這個階段我們還是主要用於ToB的場景。當時我們的華為手機還沒完全(流行)起來。這個場景在這個階段我們面對都是電信級的應用。
在這個時間段,其實NoSQL還沒有完全流行起來。我們找的應用都是電信級的應用。但是電信級的應用大家都習慣用SQL的方式去做,第一個當時KV的方式大家也不太習慣,第二個當時Cassandra的接口不像現在這么好。當時是純的Thrift接口,現在支持CQL,還有很多CQL的驅動。所以說當時是我們找業務,所以我們要按照它的使用方式,提供了一堆的定制化的東西,比如說我們在Thrfit的基礎上,定制了一個類JDBC的接口,讓它像SQL一樣用Cassandra。這一塊當時我們也是深入的修改,我們寫了一整套SQL解析的模塊(DDL,DML全部都重寫了,然后轉換成原生的mutation對象)。序列化和反序列化我們全部都改了。包括我們做得比較前沿的東西(因為當時0.7的版本還沒有堆外內存),因為它的GC比較嚴重,我們把memtable, index summary, bloom filter, row cache, key cache這些常駐內存的一大部分全部都放到了堆外。
另外,我們還做了存儲過程、二級索引、觸發器等。其實當時我們就是對標的關系型數據庫去做。但是實際上我們大家也知道本來Cassandra原生的是列數據庫,我們強制按照行的方式來改造,實際上有很大的問題。再加上電信級的業務場景,這個對可靠性和數據的准確性的要求是非常高的。所以說我們當時雖然做到了SQL的形狀,但是實際上沒有SQL的實質。這個只是在小范圍使用,也沒有完全用起來。這個基本上算是一個失敗的嘗試。
然后這個過程對我們有什么好處呢?這個讓我們深入的看到了一些Cassandra架構,以及它的處理方式,還有它的源碼。因為在后續的發展過程中,Cassandra的代碼雖然重寫了好多版(不停的重構),但是它的整個框架,整個處理流程是沒有變化的。這些知識對我們后面這個階段是有很好的指導意義的。雖然我們把Cassandra應用在電信場景沒有很成功,但是后來華為的手機慢慢流行起來了。2014年開始,終端開始起來了。
之后,我們面臨互聯網ToC的場景,其實是非常適合Cassandra的,我們就慢慢的找到了Cassandra存在的一些價值,並且不停的在往下走。這一階段我們就沒有修改任何源碼了,完全用原生的。因為根據我們第一階段的教訓,改了源碼之后,基本上就成為了孤版,很難向前演進。然后在終端情況下,我們不停的找,它最佳的使用場景。第二個因為ToC終端用戶對實時性和可靠性要求都非常高,所以我們基於Cassandra的天生的多DC方式,實現故障切換。
這里仔細講一下:我們當前的業務一般是1+1+1,一主一備一容災,每個DC都是3副本。正常的情況下我們只會向一個DC寫,如果出現故障,我們通過這個我們重寫的驅動,把它切到另一個DC去,保證任何DC里的兩個節點出現故障,對終端業務的請求來說是無損的,客戶端會自動切換數據中心。另外,Cassandra原來是有OpsCenter來進行管理的,但是因為我們公司的安全規范,沒有用它。我們現在是構建了一套華為自己的集群部署管理,包括監控系統。第三,我們不斷跟進社區的新版本。第四,Cassandra在華為的使用場景非常多。可以這樣說,凡是華為終端,包括手機,包括穿戴式,包括IoT的所有華為終端應用的背后,你看得見看不見的背后,都有我們Cassandra的身影。比如舉個簡單的例子,運動健康,大家跑步的時候,就是這些數據都基本上存在我們Cassandra里去。華為手機上的應用,只要你看得到的,基本上后面都有Cassandra的身影。所以Cassandra伴隨了我們消費者雲,伴隨了我們華為終端,六年的快速發展。
然后我們可以看一下我們當前的規模。我們當前的規模還是比較龐大的,基本上我們這里存的全是用戶數據。Cassandra我們全球的節點大概有三萬多台,我們的數據規模大概有20PB。我們的集群數量可能有500多。我們最大的集群的節點數有600多節點。我們現在全網每秒有一千萬每秒的訪問吞吐量。我們的平均延遲是4毫秒。我們當前最大的一張表,單表達到三千億條記錄。像我們這個量,在原生的沒有改動Cassandra源碼的情況,能夠達到這個規模,也是比較值得讓人驕傲的一件事情。這些數據從另一個角度證明,Cassandra原生的穩定性,使得它足以在ToC的這種線上場景,可以有很好的一個應用。
我們下面再看一下,雖然我們規模有這么大,但是不代表Cassandra是萬能的,也不代表Cassandra它什么問題都能解決。我們要避開這些問題。
我們當前面臨的挑戰,首先是華為終端,包括中國區和海外不停的業務發展帶來的龐大數據量造成的穩定性的問題。現在華為終端賣得非常好,而且用的人是越來越多,這個對我們數據庫的壓力很大,也帶來數據一致性的問題。當前我們有些數據是沒有上雲的。我們自建了機房,自建機房一塊塊的盤,是不穩定的,會遇到一些壞盤的問題,壞盤會帶來一致性,包括僵屍的問題。第三個是基礎設施的問題,比如JDK的問題,網絡的問題,磁盤的問題,我們都全部遇到過。第四個是故障的快速定位、定界,以及恢復。因為我們現在面臨的都是OLTP的場景,全是ToC的。ToC的場景,基本上就是華為終端用戶的場景。我舉個例子:假如你用到的華為手表,故障的時間一長,你的業務終端用戶就不能用,人家是很着急的。所以說我們現在對於業務的體驗,包括故障的恢復的要求也非常高。我們必須在半個小時之內把所有的故障必須恢復,你可以定位不出來問題,但是你必須把它恢復掉。
我這里有一個分類,把我們現網里遇到的典型問題列了一下。我們現網遇到的問題比這多得多,可能是這個的好幾倍,但是我總結了一下這些典型的問題,希望對大家,或者是即將使用原生Cassandra來構建自己的核心業務的朋友做一個提醒,你一定要注意這些方面的問題。這里都是我們在業務發展過程中遇到的典型問題。我后面會針對每一個問題,包括它的現象,包括從監控里面的反應,包括堆棧,都會介紹一下,結合我們的業務場景,給大家講講。
大家可以看一下,這里是我們的監控系統,我把一些IP抹掉了,這是我們的業務成功率,這是我們現網節點的CPU、IO等系統指標。大家可以看到案例的描述:有一次,現網擴容,但是擴着擴着就發現,到一定程度的時候,所有節點的CPU和IO都全部非常高,這個對我們的業務影響大家可以在右邊的圖里看到,本來成功率百分之百,忽然一下降了這么多。對應的時間點內CPU、IO全部都飆升。為什么,這個就是集群規模過大造成的影響。我們可以先看一下為什么會這樣。
根本原因是:第一是我們的集群非常大,幾百個節點,第二個是我們的Token數有256個,這樣算起來, 我們最多可以有十幾萬個Token范圍。新節點加入集群過程中,Token信息需要更新。同時,Cassandra讀寫流程里面,也需要獲取Token信息用於路由。兩個流程使用讀寫鎖獲取一個對象。當集群規模達到一定的程度時,Token數量過大,會導致Token信息更新緩慢,如果此時剛好業務高峰,請求會因為拿不到鎖而阻塞,從而導致業務請求大量超時失敗。這里我們給出的解決方案是,控制單集群規模,主要是虛擬Token數量,盡量不要超過十萬。集群過大的時候,需要考慮拆分,不要讓一個集群無限膨脹。我們現在ToC的集群為了穩定性,我們的集群節點數不超過兩百。超過兩百個節點我們建議業務去拆分。
第二個是,單節點數據量過大的時候,會有什么問題。我們當時每個節點數據量達到了5TB,集群變得非常不穩定。表現在單節點數據量大時,bloomfilter、index summary等需要的常駐內存量會很大,導致頻繁full GC甚至OOM;另外,我們默認使用的壓實算法是Leveled Compaction Strategy,如果使用LCS而且數據量過大,磁盤空間可能不夠,因為L0經常需要使用STCS來做壓實操作。解決的方法是避免單節點數據量超過1.5TB,另外在擴容過程中臨時增大磁盤空間或者設置disable_tscs_in_l0=true。注意,這個參數只能在緊急時候使用,擴容完成后,務必記得恢復成默認值。
第三個問題是節點壓實操作(Compaction)堆積嚴重。大量的壓實堆積說明壓實跟不上,會產生大量小文件,影響讀性能。后面這兩張圖里可以明顯看到在LCS的小文件太多的時候,讀延遲大大增高。我們找出的解決辦法,一個是調整compaction的速度,一個是調整兩個系統參數:sstable_preemptive_open_interval_in_mb,以及-Dcassandra.never_purge_tombstones。通過jstack查看線程的調用棧可以判斷需要調整那個參數。另外注意,never_purge_tombstones也僅限緊急情況下使用,壓實的堆積消除以后必須恢復原有的默認配置。
第四個問題是大Key的問題。前面的幾位嘉賓也提到單個partition太大的時候對性能和穩定性的影響。這個在Cassandra日志里會出現告警信息。解決的辦法是在業務里改變表結構和使用方法。比如一個文件刪除記錄表,對於個人文件來說,某個文件下面的刪除記錄不會很大,但是對於公共文件,比如華為手機上的鎖屏圖片,就會出現大Key問題,解決的辦法就是在業務里增加判斷,如果是熱門文件,在刪除次數達到某個閾值后就不再新增刪除記錄。再比如,如果記錄一個熱門電影的預約用戶,使用電影的resourceID作為分區鍵,預約用戶的UserID作為聚類鍵,當預約的用戶數達到千萬甚至上億級別,就一定會出現大Key問題。解決辦法就是使用額外的hash串將resourceID繼續離散,避免單個resourceID下的分區太大。
第五個問題是熱點Key問題。表現在短時間內對同一個Key頻繁操作,會導致該節點的CPU和Load過高,影響其他的請求,導致業務成功率下降。這個從右邊監控系統的截圖可以看到,部分節點的CPU和負載都非常高。應急處理的方法,一般是通過toppartitions找到訪問量最大的partition key,在業務側加黑名單屏蔽這種熱Key。最終的解決方案是利用緩存來減小熱Key對數據庫的沖擊。
第六個是墓碑問題,這個我就不花太多時間說了。這方面一定要避免的就是短期內如果有頻繁的刪除並且還有頻繁的讀操作的話,可能Cassandra並不適合這種場景。另外,作為應急方案,可以臨時減少gc_grace_seconds,以加速墓碑的物理清理回收時間。
第七個是壞盤導致的僵屍數據,這個大家可以直接看一下圖示和源碼,因為我的時間有限。我們的解決方案是如果你用的是自建的IDC機房,出現壞盤了,必須在gc_grace_seconds的周期內完成數據修復,或者直接replace掉出了壞盤的節點;當然,如果你的業務有條件上雲的話,這種壞盤發生的可能性要低很多。
第八個是基礎設施方面的網絡丟包問題。我們現網當時出現的症狀是突然時延大幅度增加,Cassandra驅動側出現大量的慢日志信息。排查了集群的資源利用和線程池都沒發現問題,但是我們用getendpoints把慢查詢日志涉及的分區鍵對應的副本節點打出來,發現都涉及到一個*.*.23.20的節點。后來果然發現這個節點的網卡出現了丟包的故障。修復了丟包故障后,業務時延恢復正常。
第九個是集群節點規格不一致導致的節點負載不均。這個其實涉及Cassandra的一個優化,但是優化用得不好,也會帶來問題。因為Cassandra的Gossip交換的信息里,會包含每個副本節點的負載,負載越小,收到的請求就越多。如果你的節點的物理配置不均,會導致請求集中在高配的幾個節點上。這個對自建IDC的影響比較大。在雲上,大家的節點配置比較一致,會較少遇到這個問題。
第十個是操作系統的網絡調參。我們這里只列舉了一個。網絡參數不合理的症狀是數據遷移不會出錯,但是會卡死。在集群擴容時,200GB的數據不是卡死,就是長達一兩天。在公有雲上我們發現把net.ipv4.tcp_sack (Selective ACK) 開啟,之后我們200GB的數據遷移20分鍾就完成了。這個參數能夠減小報文重傳的概率,在網絡擁塞或者亂序的情況下會有很好的效果。
最后一個是JDK的STW (Stop The World)的問題。這個問題我們到現在都還沒有復現,我們是通過把業務切到備用的DC,然后重啟故障DC的所有節點解決的。我們是怎么發現這個問題呢,當時業務的平均時延增高到3秒,但是系統CPU、IO、負載都正常,我們也排查了集群節點系統的各個核心參數,均沒有發現問題,但是注意到Hint大量出現,這說明數據在寫入過程中,出現了大量的業務節點被短暫識別為宕機狀態,引起Hint被記錄下來。通過查看Jstack和GC日志,發現線程卡頓和STW經常長達10秒。
最后,我們再來看看我們總結出來的Cassandra最佳實踐。我這里總結了幾點。
首先,需要管控業務使用場景,加強業務表結構的評審。不管你是用雲上的,還是用自建IDC的,這個對大家都是有一定的借鑒意義的。我們現在整個業務大概有幾百個利用Cassandra數據庫。我們這個組現在負責在業務上線之前,對業務場景和表結構進行評審。我們基於這樣一些方面的規范(我們也叫“軍規”):主鍵設計合不合理,Schema約束,數據老化機制,單條數據頻繁更新/刪除,單條記錄過大,大面積數據刪除引發墓碑問題,集群規模。我們現在為什么會做到這么大的量,是因為我們管控了使用的場景,讓它必須按照我們要求的來做。
然后,是構建完善的Cassandra集群監控系統。我們有很多方面的監控:第一個是主機級別的監控,包括CPU/IO/磁盤/內存;第二個是讀寫請求的監控,這個是從業務的角度來看請求量是多少;第三個是Cassandra內部核心線程監控,這個是Cassandra內部的一些顯微鏡級別的監控手段,必須可視化出來,否則的話現網幾萬台機器,出了問題的話,你是沒有任何辦法可以快速恢復的;第四個是集群規模的監控,包括節點數和集群統計量,告警,自動化部署相關的指標。
另一個最佳實踐,就是使用Cassandra,一定要多看源碼,多熟練掌握nodetool各種命令和使用場景。Nodetool命令其實非常好用,這個只是列了一些我們用得最多的,包括cleanup, compactionstats, getendpoints, netstats, rebuild, repair, toppartitions, tpstats, cfstats,我不在這里一一說明,大家可以看這個表。如果你在大規模應用中,需要快速的恢復,這些命令對於故障排查和恢復會非常有幫助。