月12日,俄軍的兩種天線被烏克蘭微型民用無人機投擲改裝的小型炸彈命中,引發外界關注,因為這是俄軍ROSC-1反無人機系統測向天線與定向干擾機在投入作戰后第一次被摧毀。
俄羅斯之所以研發這套反無人機系統,還是為了應對越來越普遍的微型/小型無人機威脅。畢竟在俄烏戰場上廣泛使用大疆等小型無人機執行偵查監視、炮兵定位甚至投彈攻擊任務,還出現了3D打印專門適配榴彈的尾翼和投彈裝置等,使本來毫無殺傷力的民用無人機也可以客串一把察打一體的感覺,偏偏這樣的視頻還會在網上大量流傳,大漲敵人士氣,從而使得空防越來越重要,偏偏這么小的無人機一般的雷達很難發現,因此俄軍必須拿出反制手段。于是俄羅斯金剛石研究和生產聯合體就研制了ROSC-1反無人機系統,由利亞諾索夫斯基機電廠(LEMZ)NPO生產。
ROSC-1反無人機系統在Army-2018上被稱為RLK-MC-A系統,自用型為RLK-MC系統。該系統包含多個組件,包括三坐標固態X波段雷達、內置ADS-B廣播式自動相關監視系統、光電系統(OES)、無線電監測子系統(RTM,即無線電偵察測向機)、無線電電子控制子系統(REU,即定向干擾機)、輔助發電系統、內部顯控臺,都集成在KAMAZ卡車底盤上的集裝箱117Zh6聯合體上,可由2-4人操作。
集裝箱頂部的雷達三坐標X波段固態雷達波長范圍3厘米,方位角探測區360度,仰角檢測區域0到+30度,可在至少15千米的范圍和至少1.5千米的高度探測無人機和其他RCS約為0.2平方米的小型物體,8千米探測小型無人機,最小探測范圍300米,可使用RMS測量目標的三個坐標,測量精度不超過10米,方位角不超過0.2度,仰角不超過1度,方位角旋轉速率2.5秒。具有高可靠性、遠程控制、自動控制和診斷功能。大多數信號處理操作和操作模式的選擇都是通過計算機軟件來完成的。復合雙極化天線設計用于將微波能量輻射到空氣中并接收兩個極化的回波信號。無齒輪回轉裝置設計根據控制系統指定的旋轉速度在方位角旋轉天線,雷達安裝在液壓升降機的RPU下方,可以縮入集裝箱或伸出,所有雷達設備都安裝在天線后側的旋轉部件上。在集裝箱內有一個供雷達和REU操作員使用的顯控臺,也可以遠程遙控。
ROSC-1反無人機系統還配備有光電系統,由電視攝像機、熱成像攝像機和操控系統組成,可進行360度旋轉,仰角-3到+45,更新速率每秒10次。ECO單元的安裝誤差不超過0.3度。內置ADS-B系統的工作范圍至少為150千米(取決于ADS-B天線的類型)。ROSC-1能夠與其他外部OES、RTM和REU系統以及無人機控制系統連接。RTM和REU設備既可以安裝在KAMAZ底盤上,也可以安裝在遠程快速安裝的機架上,采用測向方法相關干涉法探測390至5900 MHz信道,均方根測向誤差(RMS)不超過4度,可通過無線電干擾控制信道或導航信道對付無人機,通過控制信道不小于5千米,通過無線電導航信道不小于2千米,可抑制2.5-5GHz頻率范圍內的信號和GPS信號,誘導敵方無人機降落,甚至接管無人機。
有趣的是ROSC-1還配備了小型無人機自動攔截器,其實是一種Volk-18型4旋翼無人機。這種無人機也分早期型和后期型,一種通體白色,最早在Army-2018展示,一種則是黑色,體積更大,在NAIS2021論壇上亮相。這種四軸無人機外形尺寸為60×60×40厘米,四個螺旋槳的直徑為40厘米,重量為4公斤,有效載荷質量為2公斤。無人機的機身由碳纖維制成。螺旋槳由電動馬達驅動,總推力為13.6kgf,由電池供電。為了搜索目標,該機使用了光學定位系統,其視場角為20×25度,最長飛行時間達到30分鐘,可以爬升到2000米的高度。在MAKS-2021上,Volk-18無人機還安裝了一個新的光學定位系統,提供更大范圍的空中目標檢測,可以自動攔截。該機前部安裝有2個無人機捕捉網,網的大小為2×2米,發射距離小于5米,在無法使用電子對抗措施時可使用Volk-18無人機攔截目標,從ROSC-1站接收目標位置,在攔截失敗的情況下還可以選擇撞向敵機。
早期型
ROSC-1反無人機系統可提供對空中狀況的綜合監測,以檢測和識別各種類型的空中物體,包括小型和低速無人機,主雷達對1千米及以上高度的中型和大型無人機具有良好的探測能力,該系統可以干擾RQ-11B“大烏鴉”和RQ-7 “影子”無人機,以及能夠攜帶爆炸物的各種改裝商用無人機,比如大疆四軸無人機、多旋翼無人機、無人直升機等。除此之外,該綜合體可探測機場區域的鳥類。但該系統對RCS值較低(小于0.05平方米)的微型無人機時存在局限性。而本次襲擊兩種天線的烏克蘭無人機顯然就是大疆一類的小/微型無人機,很可能是借助樹林等掩蔽低空隱蔽接近并偷襲成功,好在卡瑪茲雷達車與天線并不在一處,而是進行了分散布置,否則損失更大。奇怪的是按照俄方宣傳的說法,民用無人機信道應該是可以干擾和接管的,都接近了還沒發現,要么就是系統存在缺陷,要么就是人員疏忽沒開機或故障。這種問題也實在有點說不過去啊!
背景說明
- Blink提交采用進程模型(包裝flink info/run命令)進行作業執行計劃的生成和作業的提交,這個基本是大數據計算引擎jstorm/spark/flink的共識,采用該方式的優點在于:
- 用戶只需在自己的jar包中進行邏輯處理
- 引擎client負責以方法調用形式調用用戶main方法,然后編譯、提交
- 進程模型用戶包用完銷毀,引擎版本包通過目錄隔離,不用考慮多版本問題。
- 但這也到來了缺點,每次都得走一遍大量class 加載、校驗等jvm啟動全流程。同時,大多數作業的的執行計劃生成耗時是在20秒以內,也就是說此時瓶頸不在編譯階段,此時jvm啟動開銷就成為了瓶頸。尤其當這些操作極其高頻時,帶來的開銷不容小視。
- 下圖是blink作業模式下的plan生成和提交的耗時情況。
技術演進
JVM 進程冷啟動層面優化
- 下圖展示的是一個典型的Java應用各模塊執行時間的分布情況。從JVM啟動到應用程序開始執行需要經歷:VM加載,字節碼文件加載,以及JIT(just in time)編譯技術對解釋執行的字節碼進行優化,生成本地執行代碼的過程,還需加上JVM內部垃圾回收所耗費的時間。 典型的Java應用加載時間通常是秒級起步,如果遇到比較大的應用初始花費幾分鐘都是正常的。
CDS
- CDS全稱是 Class-Data Sharing,其可以讓類可以被預處理放到一個歸檔文件中,后續 Java 程序啟動時將該歸檔文件映射到內存中,類加載器復用上一次進程啟動時曾經加載、驗證過的類信息,以節約應用啟動的時間。換句話就是避免每次進程冷啟動。
缺點:
通過CDS方案改進提交,雖然有些效果,但是總體上差強人意,核心缺點卻不容忽視:
- 原因:由于不同用戶作業很大可能依賴不同版本的包,做class緩存時就會存在沖突。
- 間接帶來的問題:
- 每個作業需要緩存大量class,對brs服務磁盤帶來巨大壓力(單個作業緩存數據100MB以上)。
- 引入CDS清理機制:由于blink作業操作并不復合近期操作原則,此時后續作業操作cds命中不了。
AOT
- AOT提前編譯也不是一個新鮮的概念,在Java之前、很多其他的語言已經提出了AOT。DK9 引入了AOT(Ahead of Time) 編譯技術,核心思想是在程序運行之前將class編譯成native的code,程序運行階段從原先的解釋執行變成執行native code,從而減少冷啟動問題。
缺點:
- 1、暫不支持自定義ClassLoader
- 2、不支持CMS和ZGC
常駐服務生成
- 可以在blink rest server機器上,部署額外服務,負責生成plan/jobgraph(該服務和blink rest server同屬一個進程)
- 方案1:多進程方案(每個版本額外啟動一個常駐進程)
- 為了更快速地支持業務需求、快速迭代,我們團隊基本保持著半個月內發版的頻率。
- 為了盡快fix用戶問題,也會發布臨時版本
- 基于此,該方案不可行,否則會有大量進場。
- 每個版本一個classloader,通過classloader做jar包隔離。
引擎多版本classloader方案實現思路
- flink/lib依賴包
- launcher包:包涵和引擎交互的包。如plan/jobgraph的生成、資源plan apply到jobgraph中、熱更新等
- user jar用戶jar包: 作業級別
- connnector/backend插件包
- 引擎當前為了支持平臺包優先級高于引擎端而設計
- 可以當做用戶jar包來看
引入version classloader
- 可以簡單使用該classloader層級關系做隔離
- 由于每個作業的user jar包不同,則version classloader沒法復用
- version classloader用完及釋放,此時和進程模型相比也就沒有太大區別,即性能會不好。
進一步引入reuqest level classloader
- 由于version級別的classloader,很少或者不變動,可復用。
- request級別的classloader每次用完立即釋放
- launcher包的功能如何暴露給spring boot server(即blink rest server)使用呢?
- spring boot server通過反射調用launcher包中的方法即可
- 但是遵循一下規則即可:
- 由于該spring boot server和flink打交道通過launcher包,暴露的方法參數務必注意只能是jdk的類
- 假如暴露的參數使用的是開源庫的類,哪怕version classloader和spring boot的app都有該jar包,但是此時類是不同的classloader加載了,會導致LinkageError問題。
- version classloader和spring boot的app classloader沒有繼承關系,做到了干凈隔離,因此該spring boot可以隨便依賴flink、甚至blink或者其他依賴,并不影響該服務
- 將version classloader cache起來,復用率非常高
- 當同版本更新發布或者測試環境希望僅僅更新某變更jar包做驗證時,通過監聽版本包目錄jar包變更,讓classloader緩存失效,重新構建即可。
思考
- 為啥hive/spark/flink計算引擎都是通過自定義classloader方案,不采用類似上面的方案,如下圖1所示呢?
- 自定義classloader本質上是想解決用戶jar和引擎包沖突的問題
- 但是用戶包和引擎包的交互:
- 上面方案:spring boot server僅單向訪問引擎launcher提供的接口
- 而對于計算引擎來說,user code訪問引擎代碼,引擎代碼依賴user code的返回值是不可避免的。
- 2) 不可能像上面方案約束暴露的方法參數必須為jdk的類
- 基于上面兩點,計算引擎自然不可能使用下面圖1方案,而是圖2方案。
圖1:flink classloader方案改造
圖2:flink 自定義classloader 引擎當前方案
- 由于相互交互,同一個類被不同的classloader加載然后相互引用,細節見筆者分析的文檔鏈接
- 雖然flink對此做了很多改進,但是該問題無法根本解決
- 比如引擎已經約束好哪些包是必須交給app classloader加載,防止被user classloader加載,那么相互引用就不會有問題
- 但不可能放進去很多,否則不同版本三方包沖突問題不就隨之而來。所以又暴露了用戶級別配置,用戶作業運行時報LinkageError問題,用戶把對應的包路徑塞入配置即可。但如果兩個classloader比如需要且引用,則沒有辦法解
- ClassNotFoundExceotion報錯詭異,讓人困惑
- 一般地,用戶插件包該錯,很簡單,user jar打上依賴即可
- 但是有些情況,就比較繞。
- 先鋪墊下基礎知識, classloader類加載機制3原則:
- 全盤負責:所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴和引用其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入,如class.forName(, classloader)。
- 雙親委派:所謂的雙親委派,則是先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父加載器,依次遞歸,如果父加載器可以完成類加載任務,就成功返回;只有父加載器無法完成此加載任務時,才自己去加載。
- 緩存機制:緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區中搜尋該Class,只有當緩存區中不存在該Class對象時,系統才會讀取該類對應的二進制數據,并將其轉換成Class對象,存入緩沖區中。這就是為很么修改了Class后,必須重新啟動JVM,程序所做的修改才會生效的原因。
- 舉例:A, B, C三個類依賴關系如下圖,但是類B對應的jar在兩個classloader中都有。
- 此時B在進程啟動時,已經被父classloader加載。然后調用user code時,調用了A -> B -> C。由于B已經被父classloader加載,根據全盤負責原則此時C將交給父classloader加載,而父classloader沒有該C的jar包,則報ClassNotFoundExceotion。
- 但是用戶就很困惑,調用鏈明明是我的代碼,而且我的包中已經有該class,為什么會報這個錯呢
- 將B從父classpath去除。不可行,這樣父classloader在進程啟動前,就報ClassNotFoundExceotion了
- 對user code中B 做shade改包名,一般該解法可行。但是比較trick的是用戶代碼依賴的B不是依賴形式使用,而是以hard code編碼方式。如果讓用戶改動依賴代碼,就很麻煩。
- 最終臨時是將該依賴打入到父classpath。但是對于引擎來說,就會有較大改動。如果是廣泛使用的包,又會很容易和其他用戶作業沖突。
效果
- 通過多版本classloader方案優化后,經測試簡單作業plan耗時從10秒降低到1秒以內,有數量級級別的提升
- 同時,從背景說明章節的圖中可看到絕大多數作業都為簡單作業
作業提交和jobgraph生成解耦
- blink/flink核心在于jobgraph,而session/perjob/application模式核心僅僅在于生成job graph的位置不同、是否支持多作業而已。具體細節見筆者之前寫的文檔鏈接
- blink 采用single job的session模式,提交作業時先拉起JobManager,然后同步方式等pod拉起之后(拉起需要申請pod比較耗時),之后在編譯作業生成jobgraph。如果發現不兼容再退出JM作業,則前面耗時的工作白做了
- 基于此,我們實現flink支持k8s per job模式,解耦作業提交和jobgraph生成。在客戶端提前生成jobgraph,如果不兼容直接報錯了,無需拉起JobManager。
- 解耦后,可以做很多優化。運維態不變更作業。可以直接復用已經生成的jobgraph,無需再重復生成等。
- 同時,為了統一代碼棧,降低開發成本,也擴展datastream作業支持per job模式提交。
結語
- 提交主要分為兩個階段client段和服務端,本文主要從客戶端優化出發,對于服務端提交優化,螞蟻實時計算團隊還做了其他大量工作,如鏡像加速(鏡像拉取的優化)、集群模式(申請TaskManager資源時,不走sigma鏡像方式,直接起進程方式)、熱更新(對用戶作業不修改情況下,不走整個提交流程,復用k8s的flink集群)。
參考文檔鏈接:
[1]https://www.yuque.com/g/jackylau-sc7w6/bve18l/rgy8y7e47abmw17c/collaborator/join?token=dGXoLPcmNkj0ILEP#%20《包沖突常見解法》
[2]https://www.yuque.com/g/jackylau-sc7w6/bve18l/tui3w27uq7rush3o/collaborator/join?token=oVoqrCtZYs3152jY#%20《Flink%20JobGraph核心信息》