以大見小 - Rust快速實踐(一)

目標

被內存安全與極致性能的特性所吸引,前段時間簡單了解了一下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?);

以大見小,開發穩定并且高性能的程序

在進一步學習具體語法和開發等細節之前,我覺得更應該思考一下學習和實踐應該從哪些方面入手。也是偶然一次,因為我曾研究過一段時間Seaweedfs(Golang開發的高并發的分布式文件系統),一個朋友問我Seaweedfs是如何實現高并發的。我講了很多,卻并沒有讓朋友聽明白,因為我當時并沒有足夠簡練的總結出重點。也因此在我重新思考了Seaweedfs的高并發關鍵因素之后,我有了現在的想法:技術在某種程度上都是相通的,可以以小見大,同樣也可以以大見小。對于分布式服務系統的重要因素,同樣適用于開發一個獨立的服務程序!

Seaweedfs 分布式文件服務穩定和高并發的關鍵因素

一、可觀測:是服務穩定的保證,測試用例,日志輸出,監控接口;
二、算法實現:數據結構與索引搜索算法,快速定位數據;
三、資源優化:存儲卷,合并小文件,優化IO從而提高整體的吞吐量;
四、分布式:分布式集群,數據副本冗余分發,進一步提高穩定性和并發能力,使服務能夠并發訪問和故障轉移;

穩定高性能的服務程序同樣需要關注以下類似的幾個因素:

一、可觀測:測試用例,日志輸出,監控接口;
二、數據結構與算法實現:功能的實現,數據結構和算法的選擇;
三、資源優化:語言的資源(比如:鎖,內存,引用包提供的數據結構等),調用的資源(比如:依賴包,依賴服務,底層資源,CPU,內存,IO磁盤,網絡等等);
四、并發:多線程,協程等
參考項目

為了更快的接觸掌握最佳實踐,挑選了一個社區活躍的開源項目。參考學習它的項目結構,文件配置以及包的選擇。

Lighthouse

項目結構

$ 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              // 程序入口

先寫到這里,本來想寫一篇就可以了,結果寫了這么多還沒到主題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容