阿里萬億交易量級下的秒級監(jiān)控
我今天分享的內(nèi)容是:怎么在萬億交易量下實(shí)現(xiàn)足夠?qū)崟r(shí)的秒級監(jiān)控?
我先來介紹一下監(jiān)控系統(tǒng) Sunfire,它是阿里集團(tuán)的業(yè)務(wù)監(jiān)控系統(tǒng),前身是螞蟻的 xflush,它支持應(yīng)用標(biāo)準(zhǔn)化監(jiān)控,如操作系統(tǒng),JVM,中間件等。
除此之外還有更強(qiáng)大的日志監(jiān)控能力,大多數(shù)業(yè)務(wù)的監(jiān)控指標(biāo)都從應(yīng)用的日志中抽取。目前覆蓋了集團(tuán)幾乎所有 BU 和絕大多數(shù)業(yè)務(wù),每分鐘處理 TB 級日志。
下面我將從以下四個(gè)方面進(jìn)行講解:
- 架構(gòu)
- 規(guī)模與挑戰(zhàn)
- 技術(shù)選擇
- 方向
架構(gòu)
每分鐘處理這么大的 TB 級日志量,我們是怎么設(shè)計(jì)架構(gòu)去實(shí)現(xiàn)它的呢?
傳統(tǒng)日志監(jiān)控
上圖是傳統(tǒng)的日志監(jiān)控,現(xiàn)在大多數(shù)監(jiān)控平臺采用的一個(gè)方案。
Agent 檢測日志變化增量推送,經(jīng)過消息中間件如 kafka,流式計(jì)算引擎如 Jstorm/flink 去消費(fèi) kafka 產(chǎn)生出來的數(shù)據(jù),中間的流式計(jì)算可能有多步的處理,***流向 DB,這是很傳統(tǒng)的架構(gòu)。
這種架構(gòu)會有一個(gè)問題就是:某一分鐘的數(shù)據(jù),何時(shí)可以發(fā)報(bào)警?
流式計(jì)算的問題
Process Time 超過 Event Time Window,我們最早嘗試了上面?zhèn)鹘y(tǒng)的架構(gòu),但是有一個(gè)問題,我到底什么時(shí)候這個(gè)數(shù)據(jù)才能發(fā)報(bào)警呢?
因?yàn)檫@個(gè)架構(gòu)最麻煩的是我不知道什么時(shí)候數(shù)據(jù)已經(jīng)全部到齊了。如果機(jī)器很多,Agent 返回?cái)?shù)據(jù)的時(shí)間并不確定, 要保證所有機(jī)器日志采齊了數(shù)據(jù)才準(zhǔn)確,這在流式計(jì)算里很難處理。
這是個(gè)經(jīng)典的問題,有兩篇文章很詳細(xì)的講解了流式計(jì)算中如何解決這種問題:
https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-101
https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-102
但是數(shù)據(jù)丟了就是丟了, 無論怎么樣就是不準(zhǔn)了,也很難拍出一個(gè) delay 的時(shí)間確保數(shù)據(jù)可以用來發(fā)報(bào)警, 那么當(dāng)數(shù)據(jù)不準(zhǔn)時(shí), 我們能不能知道不準(zhǔn)了呢?
為了解決這個(gè)問題我們走了另一條路線:讓主動(dòng)權(quán)留在服務(wù)端。
Sunfire 功能結(jié)構(gòu)
這是 Sunfire 的功能結(jié)構(gòu),圖中比較重要的是 Sunfire-lika 模塊,它是用來支撐整個(gè)計(jì)算框架的,就是線程模型、消息調(diào)度處理、故障自愈恢復(fù)都是通過這個(gè)模塊實(shí)現(xiàn)的。
Sunfire 架構(gòu)
上圖是 Sunfire 的架構(gòu)圖,這個(gè)架構(gòu)圖是怎么工作的呢?
首先有三個(gè)角色 Brain、Reduce 和 Map,這三個(gè)角色我們統(tǒng)稱為計(jì)算模塊。
ConfigDB 里面配置了監(jiān)控項(xiàng)。監(jiān)控項(xiàng)會定義配置需要從哪個(gè)應(yīng)用、哪個(gè)路徑采集日志、采集回來的日志應(yīng)該做哪些的處理、根據(jù)什么樣的規(guī)則進(jìn)行計(jì)算。
Brain 會按照周期從 ConflgDB 里讀取配置,生成拓?fù)?。然后安裝到 Reduce 上面,Reduce 把拓?fù)湓俜纸獬伤淖尤蝿?wù),再安裝到 Map 上面,*** Map 去拉日志。
這里畫了兩個(gè)租戶,租戶 A 和共享租戶,其實(shí)就是資源是獨(dú)享的還是共用的。
因?yàn)槲覀冇幸恍┖诵牡慕灰妆O(jiān)控,也有一些不太重要的,還有很多邊緣業(yè)務(wù)。
如果是很重要的用戶,比如說交易,我們就單獨(dú)給它一個(gè)租戶,它的所有計(jì)算資源都是它自己獨(dú)享的。
對于一些邊緣的業(yè)務(wù)是可以共用服務(wù)器的。我們現(xiàn)在有 80 多個(gè)租戶,基本上一個(gè)租戶對應(yīng)一個(gè)大的業(yè)務(wù)。
時(shí)序圖
用時(shí)序圖的視角看一下上面的任務(wù)。這個(gè)拓?fù)浒伺渲?,也包括這個(gè)拓?fù)淙蝿?wù)從多少個(gè)服務(wù)器,到底從哪些服務(wù)器上去采集日志,都是在這個(gè)拓?fù)淅锩嫱瓿傻摹?/p>
有了這個(gè)拓?fù)?,才有了?jié)點(diǎn)故障時(shí)候,恢復(fù)它的前提條件。因?yàn)橥負(fù)淅锩姘怂行畔?,無論是哪個(gè)節(jié)點(diǎn)掛掉了,上游都能用它來恢復(fù)下游節(jié)點(diǎn)。
把這個(gè)拓?fù)浒惭b到一臺 Reduce 上面去,然后 Reduce 會把它分解掉。假如我有 1 臺 Reduce,有 100 個(gè) Map,Reduce 會把這個(gè)任務(wù)分解成 100 個(gè) Map。
如果這時(shí)候有 1000 個(gè) Agent,有可能每個(gè) Map 會采集 10 個(gè) Agent 的日志,最終 Map 去 Agent 拉取日志。
然后再一步步往回走,Map 做初步的計(jì)算,Reduce 再做進(jìn)一步的聚合存入到 HBase,然后最終返回給 Brain,告訴它這個(gè)任務(wù)完成了。
這里面存在很多可能會出問題的點(diǎn),因?yàn)榧悍浅}嫶?,跑著跑著機(jī)器可能就掛掉了,這對我們來說是很正常的,一天掛掉十幾臺機(jī)器也是常有的事。
下面說一下怎么解決可靠性的問題。
關(guān)鍵點(diǎn)
上面架構(gòu)有兩個(gè)關(guān)鍵點(diǎn):
Preload
就是任務(wù)是提前注冊的,它不是在需要的時(shí)候才生成任務(wù)。我們把任務(wù)提前下發(fā)下去了,有什么好處呢?
假如集群有一些壞掉的機(jī)器可能網(wǎng)絡(luò)很慢也可能連不上,在這個(gè)階段就可以提前發(fā)現(xiàn)這些機(jī)器屏蔽掉,在后面真正去做任務(wù)的時(shí)候,延遲就會相應(yīng)的降低很多,因?yàn)椴恍枰偃サ热ブ卦嚵恕?/p>
同時(shí),Preload 是輸入共享的前提,因?yàn)椴煌娜藭渫瑯拥娜罩?,并且?guī)則可能也是類似的,我們在這里會做輸入共享,去共享日志的采集來減少帶寬和 CPU 的消耗,也會共享中間一部分計(jì)算的結(jié)果。
Pull
主動(dòng)權(quán)控制在服務(wù)端,就是服務(wù)端發(fā)現(xiàn)數(shù)據(jù)拉不上來,想要放棄還是重試,可以由自己做出決定了。
最終服務(wù)端會決定多長的時(shí)間內(nèi),一定把這些全部都處理完,而不會過了很長一段時(shí)間還有數(shù)據(jù)突然推上來的問題了。
還有就是 push 時(shí),有可能遇到網(wǎng)絡(luò)抖動(dòng),導(dǎo)致失敗,重試也不成功,但在 pull 模式下,相當(dāng)于把 Agent 作為 hadoop 中的 hdfs 節(jié)點(diǎn),只要日志還在,我們就有補(bǔ)數(shù)據(jù)的機(jī)會。
另外,降低用戶開銷對我們來說也是比較重要的,像雙十一場景,交易的應(yīng)用開銷非常大,我們一定要盡量降低它們的開銷。比如占了 10% 的 CPU,交易的用戶就受不了這個(gè)開銷。
因此,所有的計(jì)算都是在服務(wù)端完成,也使得我們的集群規(guī)模非常大。
規(guī)模與挑戰(zhàn)
挑戰(zhàn)
挑戰(zhàn)主要來自于如圖中的這四個(gè)方面,都是因?yàn)橐?guī)模而引起的挑戰(zhàn)。
規(guī)模
現(xiàn)在有 80 多個(gè)租戶,基本上一個(gè)租戶對應(yīng)一個(gè)大的業(yè)務(wù),比如交易是一個(gè)租戶,阿里媽媽是一個(gè)租戶,高德是一個(gè)租戶。
部署機(jī)器最多的時(shí)候有 6000 多臺,上面的應(yīng)用有 8000 多個(gè),每分鐘處理的日志量在 3000GB 以上,這只是常態(tài)化的日志量并不是***峰的日志量。
這么大的日志量用一個(gè)消息中間件去承載也是很困難的,這也是我們沒用流式計(jì)算的原因之一。
場景挑戰(zhàn)
場景挑戰(zhàn)主要有如下幾個(gè)方面:
某應(yīng)用有上萬臺服務(wù)器,每分鐘產(chǎn)生的日志量近 1T,如何在秒級完成采集并輸出準(zhǔn)確的結(jié)果?
假如有很多人配置了基于該日志的監(jiān)控項(xiàng),如何降低開銷?
假如過程中有服務(wù)器宕機(jī)了,怎么辦?
快速
我們怎么實(shí)現(xiàn)快速拉取呢?
在 server 端,其中核心的鏈路是異步的,所有的通信也是異步的,沒有一個(gè)地方允許有鎖。
這兩個(gè)是通過上面提到的 lika 框架來實(shí)現(xiàn)的,lika 框架沒有什么特別神奇的地方,把 Akka 的一些核心理念拿出來做了一個(gè)簡化的框架,更簡單更容易維護(hù)。
在 Agent 端,最重要的是用了 Zero-copy,使得讀日志不經(jīng)過任何 CPU 的處理,直接通過 socket 發(fā)送出去。這樣***的好處是對用戶開銷極小,壞處就是不能壓縮了。
RandomAccessfile 是配合動(dòng)態(tài)二分法來使用的,配日志的時(shí)候沒有讓用戶指定時(shí)間字段應(yīng)該在哪個(gè)位置,時(shí)間是什么格式的,這些都是我們自己判斷的。
怎么知道用戶的某個(gè)周期應(yīng)該推上去的日志是哪些呢?是通過動(dòng)態(tài)二分法來實(shí)現(xiàn)的。
Brain 生成拓?fù)涞臅r(shí)候,是有時(shí)間戳的。Agent 拿到以后,簡單來說先看頭和尾有沒有,因?yàn)槿罩臼遣粩啻虺鰜淼?,采集也是不斷進(jìn)行的,尾部拿到的概率特別大。
如果不在就根據(jù)這個(gè)時(shí)間去找,把它做二分查找,***找到時(shí)間。上面提到的唯一開銷就來自這里,要去猜時(shí)間在哪,在極端情況下對用戶的 CPU 也能控制在 8% 以下。
準(zhǔn)確
準(zhǔn)確性從這個(gè)系統(tǒng)一開始設(shè)計(jì)時(shí)就貫穿始終的,也是我們?yōu)槭裁丛谝婚_始沒有用流式計(jì)算的原因。
除了 pull 的機(jī)制來把控制權(quán)保持在服務(wù)端之外,我們還設(shè)計(jì)了齊全度,這對我們來說是非常重要的。傳統(tǒng)的監(jiān)控一個(gè)指標(biāo)產(chǎn)生一個(gè)值就行了,我們每一個(gè)值還有一個(gè)相對應(yīng)的齊全度。
這個(gè)齊全度代表什么意思呢?比如 1000 臺機(jī)器里面有幾臺機(jī)器的網(wǎng)絡(luò)不通或者機(jī)器掛掉了,因?yàn)闄C(jī)器多了什么問題都會有,這很正常。
我們會在***采集完成的時(shí)候,多打出來一個(gè)指標(biāo)說 1000 臺機(jī)器采集成功 900 臺,失敗 100 臺,成功率是 90%。
這時(shí)候用戶就有參考了,如果此時(shí)發(fā)現(xiàn)交易量下跌了,一看齊全度也下跌了,基本上可以認(rèn)為是采集的問題導(dǎo)致的下跌,有可能并不是真正的業(yè)務(wù)下跌,可以來找我們看為什么采集缺失。
因此齊全度是我們特意設(shè)計(jì)出來,為了讓用戶直觀感受到采集的完整度的一個(gè)概念。
有了上面的措施還是不能保證準(zhǔn)確,還需要有各種各樣的測試來驗(yàn)證這些設(shè)計(jì)是不是可靠的。
所以在線上搭很多環(huán)境,測試同學(xué)造了各種各樣的配置,如虛擬的應(yīng)用大部分機(jī)器都是壞掉的,或者大部分機(jī)器沒有產(chǎn)生日志。再配合上各種各樣的日志計(jì)算規(guī)則,去實(shí)時(shí)校驗(yàn)。
準(zhǔn)確性回歸是我們每次發(fā)布之前都必須做的,也是自動(dòng)觸發(fā)的過程。只要我們每次打包都會觸發(fā)一次準(zhǔn)確性校驗(yàn)。自灰度就是找一些小白鼠,先發(fā)布他們,再發(fā)布重要的客戶。
穩(wěn)定
上面是我列舉的一些影響系統(tǒng)穩(wěn)定性的部分問題。最常見的像下發(fā)失敗,這種好處理,直接重試就可以了。
如果已經(jīng)下發(fā)成功了,但是在做的過程中失敗的,這就很麻煩了。所以我們 lika 框架很重要的一點(diǎn),就是為這個(gè)服務(wù)的。
比如 Brain 生成任務(wù)以后,它安裝成功了一個(gè) Reduce,Brain 就會去守護(hù)這個(gè) Reduce。
我們有一套機(jī)制來保證 Reduce 執(zhí)行成功,直到返回成功給 Brain,這個(gè)任務(wù)才結(jié)束。
如果沒返回,Brain 就會不斷探測它,一旦探測到它失敗了,比如這臺機(jī)器連不上了,或者機(jī)器是好的但是任務(wù)中間出異常掛掉了,那么 Brain 會重試它,換一臺機(jī)器繼續(xù)做這個(gè)任務(wù)。像 Reduce 安裝完 Map 后失敗了,也是類似的邏輯。
拉日志也會帶來一些不可控的事情,就是我不知道要拉的日志到底有多大。有可能我這邊分配的計(jì)算機(jī)器數(shù)很少,但是用戶日志量非常大,就有可能把我們打爆了。
因此我們有一系列自我保護(hù)的邏輯,會計(jì)算每個(gè)監(jiān)控項(xiàng)的開銷,不能高于某一個(gè)值。如果高于這個(gè)值,說明監(jiān)控項(xiàng)消耗資源太高了,可能配了一些極其復(fù)雜的策略,這時(shí)為了自保必須把它 kill 掉。
我們也有實(shí)現(xiàn)內(nèi)存的分配策略,就是每次拉日志的大小是計(jì)算出來的。經(jīng)過一系列的因素計(jì)算出來這次能拉多少日志。如果內(nèi)存不夠,等一會兒再去拉。
同時(shí),我們也做了一系列的自我監(jiān)控。我們是拿自己搭的另外一套環(huán)境來監(jiān)控自己。報(bào)警也是在這上面配的,來觀察各個(gè)租戶的狀態(tài)是不是正常的。
以上這些措施構(gòu)成了穩(wěn)定性的保證。
穩(wěn)定性驗(yàn)收
穩(wěn)定性最終是需要驗(yàn)收的,不能說我們說穩(wěn)定就穩(wěn)定。上圖是我們設(shè)計(jì)的一些場景。
比如有多少機(jī)器宕機(jī),看宕機(jī)的過程有沒有數(shù)據(jù)丟失或者數(shù)據(jù)不準(zhǔn)。還有網(wǎng)絡(luò)丟包,Hbase 服務(wù)中斷等等,再恢復(fù)看能不能恢復(fù)。再有像整個(gè)機(jī)房斷網(wǎng),讓某個(gè)機(jī)房成為孤島,來驗(yàn)證它的穩(wěn)定性。
成本
在成本方面,集群機(jī)器的數(shù)量比較龐大,我們一直想努力降低成本,主要通過下面三個(gè)方面來做的。
租戶間調(diào)度/輸入共享
降低成本最重要的技術(shù)手段就是做了輸入共享,輸入共享在很多情況下能減少起碼三倍或者五倍的日志拉取。因?yàn)樵诙鄶?shù)情況下一個(gè)日志會產(chǎn)出多個(gè)指標(biāo),不同的指標(biāo)也可能會打到同一份日志里面。
怎么做呢?Brain 提前注冊了 Reduce,Reduce 提前注冊了 Map,Map 上有一個(gè)關(guān)系,就是這個(gè) Map 要采集哪些機(jī)器上的哪些日志。
最終可以構(gòu)建出來一個(gè)關(guān)系,就是監(jiān)控項(xiàng)跟機(jī)器上的日志的對應(yīng)關(guān)系。比如說***個(gè)監(jiān)控項(xiàng)要采集 100 臺機(jī)器上的某個(gè)日志,第二個(gè)監(jiān)控項(xiàng)還是要采集這批機(jī)器上的同樣日志。
這兩個(gè)任務(wù)就合并掉了,最終所有的采集同一份日志的任務(wù)都會被合并掉,這是提前注冊里面可以做的事情。
關(guān)系構(gòu)建好了以后,就觸發(fā)一個(gè)定時(shí)器來觸發(fā)拉取。
清理僵尸配置
我們根據(jù)某個(gè)配置它最近一段時(shí)間被多少人訪問,有沒有報(bào)警,報(bào)警后有沒有人處理,等等一系列指標(biāo)計(jì)算出監(jiān)控項(xiàng)的健康度。如果健康度太低,就會通知用戶去清理它,減少我們配置的量。
統(tǒng)計(jì)值優(yōu)先
統(tǒng)計(jì)值優(yōu)先也是現(xiàn)在不得不做的一個(gè)優(yōu)化。因?yàn)橐郧昂芏鄳?yīng)用打的都是流水的日志。
以交易舉例,交易有很多環(huán)節(jié),每個(gè)環(huán)節(jié)至少有一行日志,最終有可能1億筆交易對應(yīng)100億條日志。
所以會要求大的業(yè)務(wù)方,把這些值改成統(tǒng)計(jì)值,至少是每秒或者每分鐘聚合后的值打出來。
輸入共享
對于多個(gè)配置,一份日志只采集一次。
技術(shù)選擇
上圖是我們做監(jiān)控的過程中做的一些技術(shù)選擇。拉和推模式各有優(yōu)缺點(diǎn),為了準(zhǔn)確性選擇了拉的模式,不排除推的模式也能搞定準(zhǔn)確性,還是會走到推的路線上來,因?yàn)榧軜?gòu)總是不斷迭代的。
計(jì)算應(yīng)該在 Server 端還是 Agent 端執(zhí)行呢?因?yàn)橛脩艚邮懿涣?CPU 使用率過高,會影響正常業(yè)務(wù),因此我們最終選擇所有的計(jì)算都在 Server 端完成。
對于使用開源框架還是自研框架,我們也希望用開源框架,但如果有的地方滿足不了或者開源社區(qū)的方向跟我們期望的方向不太一致,我們可能就會基于這個(gè)框架的思想定制一個(gè)簡易的框架。
只有核心的設(shè)計(jì),代碼體量小、維護(hù)也簡單,其實(shí)我們計(jì)算框架做出來以后,幾乎沒有產(chǎn)生過什么 Bug,因?yàn)樗蛔隽讼⒎职l(fā)線程池管理和故障守護(hù)這幾件事情。
在數(shù)據(jù)庫選擇上,當(dāng)前我們是直接寫 Hbase,正在和 HiTSDB 團(tuán)隊(duì)對接, 這是一個(gè)類 OpenTSDB 的存儲, 在阿里云上也有提供。
對于監(jiān)控來說,我們最終選擇的自運(yùn)維,我們幾乎沒有強(qiáng)依賴任何系統(tǒng)。為什么呢?
因?yàn)槲覀冇袀€(gè)理念,監(jiān)控應(yīng)該是最基礎(chǔ)的設(shè)施,如果我們強(qiáng)依賴別人,我們將監(jiān)控不了它,所以我們做了一個(gè)自運(yùn)維體系。
以上就是做的一些技術(shù)選擇,經(jīng)過了很多次迭代,最終走到了現(xiàn)在的路線。
方向
現(xiàn)在我們的方向是這四個(gè):
- 標(biāo)準(zhǔn)化
- 一體化
- 服務(wù)化
- 智能化
標(biāo)準(zhǔn)化:MQL
select avg(cpu.util),max(load.load1) from system where app='AppTest' since 30mselect * from sunfire.1005_SM_13 since 30mselect * from spring filter class='classA' and method='methodB' where ip='192.168.1.1' since 1h
我們的 MQL 希望讓用戶能夠用一個(gè)通用的語法來查詢所有的監(jiān)控?cái)?shù)據(jù)。甚至是其他監(jiān)控系統(tǒng)的數(shù)據(jù),這樣用戶不用管數(shù)據(jù)是哪個(gè)平臺產(chǎn)生的。
MQL 在使用上也比原來的 API 更直觀一些,會是我們后面主推的提供給用戶 API 的方式。
一體化
我們還做了很多一體化的事情,比如說發(fā)現(xiàn)交易下跌了,這時(shí)候交易的應(yīng)用有沒有做變更,有沒有擴(kuò)容、縮容重啟的操作,這是用戶關(guān)心的。
我們統(tǒng)計(jì)出來有相當(dāng)比例的故障是因?yàn)樽兏鼘?dǎo)致的,當(dāng)業(yè)務(wù)異常的時(shí)候直觀的看到有沒有變更,可以為他省很多時(shí)間。雖然這個(gè)事情做起來很簡單,但是作用是很大的。
我們還把宿主機(jī)和網(wǎng)絡(luò)監(jiān)控也關(guān)聯(lián)起來了,現(xiàn)在用的都是容器,但有的問題可能是因?yàn)樗拗鳈C(jī)出問題了,或者上面負(fù)載太高了,用戶可以做出直觀的判斷。
同時(shí),還把報(bào)警集成在釘釘里面完成。釘釘有什么好處呢?它跟傳統(tǒng)的短信、郵件報(bào)警不一樣,它可以有很豐富的交互。
用戶可以點(diǎn)擊進(jìn)來看報(bào)警的詳情,甚至可以有曲線、報(bào)警的歷史,點(diǎn)進(jìn)去還可以做一些重啟機(jī)器的操作,或者覺得這是個(gè)誤報(bào)我要關(guān)閉半個(gè)小時(shí),都可以在這里一站式完成。這比以前用短信收報(bào)警的方式前進(jìn)了一大步。
釘釘
釘釘一站式報(bào)警處理
智能化
在智能化上面我們也在做很多探索,比如智能基線。圖上有一段虛線,是通過算法預(yù)測出來這個(gè)曲線后面這段時(shí)間的走勢可能是什么樣的。
我們可以很直觀的判斷出來到底有沒有異常。進(jìn)一步希望做到用戶不用配報(bào)警,自動(dòng)幫它生成報(bào)警的閾值。
智能基線讓用戶只要配一個(gè)規(guī)則就可以了。原先是一天內(nèi)不同時(shí)間業(yè)務(wù)指標(biāo)的范圍可能都不一樣,用戶只能根據(jù)時(shí)間段配了一堆規(guī)則。
上圖是簡化后的規(guī)則,有了智能基線以后只要配當(dāng)前值和基線比超過百分之多少就報(bào)警,就這么簡單。
作者:孔羅星
孔羅星(癲行),阿里研發(fā)效能事業(yè)部監(jiān)控平臺技術(shù)專家。2014 年加入阿里巴巴,曾在福建富士通開發(fā) CMDB,監(jiān)控等運(yùn)維相關(guān)系統(tǒng),有 6 年工作經(jīng)驗(yàn),從事過 DBA、SA、Python 開發(fā)、Java 開發(fā)。