目標
被內存安全與極致性能的特性所吸引,前段時間簡單了解了一下Rust 語言,然后就更加好奇起來。元旦也出不去就決定進一步學習一下。不想糾結于過多的語言細節,給自己定了一個明確的目標:開發出一個小工具(開源:Ratchet http/https 服務監控)。
這次完成了一些基本功能,對Rust語言有了初步理解,也收集了一些資料,本文做一個整理。然后繼續深入學習和迭代完善。
文章難免有錯誤或疏漏,歡迎指正。如果對內容有意見或建議,或者新的參考資料或信息也希望能留下評論或發給我,我會進一步更新和完善,在這里先謝謝了。
最后,希望能夠幫到所有想要初步認識Rust的人。
以大見小 - Rust快速實踐(一)- 主觀感受
以大見小 - Rust快速實踐(二)- 語言部分細節
主觀感受
好像同時在接觸三種語言;
細節概念較復雜,學習曲線較陡且長;
編譯很容易報錯,但報錯很清晰,便于查詢解決問題;
編譯較慢,發布程序較小;
三種語言
...
let metrics = vec![
Gauge::new(grabber.name(), grabber.help()).unwrap(),
];
let descs = metrics
.iter()
.map(|c| c.desc().into_iter().cloned())
.fold(
Vec::new(),
|mut acc, ds| {
acc.extend(ds);
acc
},
);
...
這是項目中的一段代碼,寫(Copy)這段代碼的時候,有一種三種語言混在一起的感覺。感覺信息量也比較多,不用深究,體會一下即可。雖然很容易區分,但是總有幾種語言寫在一起的感覺。
// 聲明與賦值代碼!
let metrics = ...
...
let descs = metrics
.iter()
...
... Vec::new()
// vec! 是宏,以嘆號'!'結尾,
// 編譯時會被展開為代碼再進行最終編譯!
... = vec![
// |c| ... 和 |mut acc, ds| {} 都是閉包,
// 當閉包中只有一條語句時{} 可以省略;
// 對了,因為沒有分號,所以概念上好像應該叫表達式,
// 函數中最后一行沒有分號表示這是函數返回值的表達式;
... |c| c.desc().into_iter().cloned()
... |mut acc, ds| {
acc.extend(ds);
acc
}
...
學習曲線
這幾天學習下來,感覺上手還算容易,但學習曲線還是比較陡且長的。很多重要的概念要學習要理解,可以慢慢來,也只能慢慢來。比如(按照我的開發使用順序以及自認為的優先級進行的排序):
- 包和crate;
- 表達式和語句;
- 宏和閉包;
- 所有權,借用與引用;
- 泛型、trait 和生命周期;
- 智能指針;
- 面向對象,并發,unsafe Rust;
- 等等......
本文不會也無法涉及如此多的概念,僅做一些簡要的介紹。
編譯
Rust會在編譯時進行大量的靜態檢查,以盡可能確保運行時安全。也可能是因此編譯速度不是很快,并且很容易報錯。不過報錯基本都很清晰,根據報錯的信息線索,基本都能夠較快的解決問題。當然,有一些涉及到對語言概念理解的問題則需要多一些時間,不過也更能幫助深入理解Rust這門語言。
error[E0277]: the size for values of type `(dyn Grabber + 'static)` cannot be known at compilation time
--> common/exporter/src/collector.rs:10:17
|
10 | pub fn register(grabber: Grabber)
| ^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn Grabber + 'static)`
= help: unsized locals are gated as an unstable feature
help: function arguments must have a statically known size, borrowed types always have a known size
最終令人比較欣慰的是,程序編譯比Java和Golang可運行程序小很多。當然這會由于實現功能的不同而有所不同,但應該只是小多少的問題。
$ ll -lh ./target/release/ratchet
... 13M ... ./target/release/ratchet*
$ ll -lh build/net_watcher
... 16M ... build/net_watcher*
至于性能,CPU和內存占用問題,以后進一步學習之后再整理一篇文章單獨介紹。
初識Rust
內存安全與極致性能
對Rust預研引起興趣,不僅僅是因為其生態中已經有了豐富的開源項目(GitHub 上有哪些值得關注的 Rust 項目?),甚至已經有使用Rust實現的操作系統!更吸引我的是,很多文章所說的Rust的內存安全與機制性能是否屬實?是如何實現的?
我初步了解到有以下幾點:
第一:編譯時的靜態檢查:Rust編譯時有嚴格的檢查,以增強程序內存使用的安全性;
第二:泛型的單太化:泛型代碼在編譯時會被展開為靜態的特定類型代碼;因此如果調用了三個類型的泛型,編譯時會被展開為靜態的三套類型的特定代碼,應該屬于空間換時間的一種方法;
第三:Rust運行時沒有GC:因此也就完全沒有運行時GC進行回收時的任何開銷;
運行時比較
Java 分代GC
Golang 三色GC
Rust 無GC
看網上很多的討論就是關于Rust運行時沒有GC的。Java的分代GC與Golang的三色GC雖然一直在盡可能優化,但是仍然需要在一些情況下完全暫停。這可能就是Rust性能高的很重要一點,Rust沒有通常理解的獨立的GC機制,所以也就不存在暫停的問題。
沒有運行時GC并不是完全沒有垃圾回收。而是通過語言級概念和語法的嚴格定義實現了可預定義的回收(Rust有GC,并且速度很快)。Rust要求開發人員對所有權以及生命周期等概念有清晰的理解,并在代碼中也要有明確的定義和使用,也就不需要通過運行時GC而直接可以更安全的處理內存(語言級GC?);
- 通過生命周期管理(釋放)棧內存
- 通過所有權管理(釋放)堆內存(釋放堆內存,Rust是怎么做的?所有權!)
以大見小,開發穩定并且高性能的程序
在進一步學習具體語法和開發等細節之前,我覺得更應該思考一下學習和實踐應該從哪些方面入手。也是偶然一次,因為我曾研究過一段時間Seaweedfs(Golang開發的高并發的分布式文件系統),一個朋友問我Seaweedfs是如何實現高并發的。我講了很多,卻并沒有讓朋友聽明白,因為我當時并沒有足夠簡練的總結出重點。也因此在我重新思考了Seaweedfs的高并發關鍵因素之后,我有了現在的想法:技術在某種程度上都是相通的,可以以小見大,同樣也可以以大見小。對于分布式服務系統的重要因素,同樣適用于開發一個獨立的服務程序!
Seaweedfs 分布式文件服務穩定和高并發的關鍵因素
一、可觀測:是服務穩定的保證,測試用例,日志輸出,監控接口;
二、算法實現:數據結構與索引搜索算法,快速定位數據;
三、資源優化:存儲卷,合并小文件,優化IO從而提高整體的吞吐量;
四、分布式:分布式集群,數據副本冗余分發,進一步提高穩定性和并發能力,使服務能夠并發訪問和故障轉移;
穩定高性能的服務程序同樣需要關注以下類似的幾個因素:
一、可觀測:測試用例,日志輸出,監控接口;
二、數據結構與算法實現:功能的實現,數據結構和算法的選擇;
三、資源優化:語言的資源(比如:鎖,內存,引用包提供的數據結構等),調用的資源(比如:依賴包,依賴服務,底層資源,CPU,內存,IO磁盤,網絡等等);
四、并發:多線程,協程等
參考項目
為了更快的接觸掌握最佳實踐,挑選了一個社區活躍的開源項目。參考學習它的項目結構,文件配置以及包的選擇。
項目結構
$ tree
├──lighthouse/ // Package
│ ├── Cargo.toml
│ ├── environment // Package
│ │ ├── Cargo.toml
│ │ ├── src
│ │ │ └── lib.rs
│ │ └── ...
│ │ ...
│ ├── src
│ │ └── main.rs
│ └──...
├──account_manager/ // Package
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── common.rs
│ ├── lib.rs
│ ├── validator
│ │ └── *.rs
│ ...
├──beacon_node/ // Package
├──book/ // Package
├──boot_node/ // Package
├──Cargo.toml
...
├──Makefile
├──README.md
...
src/main.rs 是可執行程序crate的根,即可執行程序的構建入口。
src/lib.rs 是庫crate的根,即庫的構建入口。
一個包中至多只能包含一個庫 crate(library crate);
包中可以包含任意多個可執行程序(二進制)crate(binary crate);
包中至少包含一個 crate,無論是庫的還是可執行程序的。
實踐項目Ratchet 參考了Lighthouse 的項目結構,規劃為多個包、箱和模塊組成項目,以便功能劃分和以后的維護迭代。
./Ratchet
├── Cargo.toml
├── log4rs.yaml // 日志配置文件
├── ratchet.yaml // 服務程序配置文件
├── README.md // 項目說明文件
├── Makefile // 構建腳本
├── common // 通用工具
│ ├── exporter // 監控接口
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── collector.rs
│ │ ├── grabber.rs
│ │ └── lib.rs
│ ├── logger // 日志
│ │ ├── Cargo.toml
│ │ └── src
│ │ └── lib.rs
│ └── ratchet_version // 版本信息
│ ├── Cargo.lock
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── watcher // 檢測服務功能實現
│ ├── Cargo.toml
│ └── src
│ ├── config.rs
│ └── lib.rs
└── ratchet // 二進制運行程序
├── Cargo.lock
├── Cargo.toml
└── src
└── main.rs // 程序入口
先寫到這里,本來想寫一篇就可以了,結果寫了這么多還沒到主題。