InfluxDB是一個由InfluxData開發的開源時序數據庫,專一於海量時序數據的高性能讀、寫、高效存儲與實時分析等,在DB-Engines Ranking時序型數據庫排行榜上常年排名第一。數據庫
InfluxDB能夠說是當之無愧的佼佼者,但 InfluxDB CTO Paul 在 2020/12/10 號在博客中發表一篇名爲:Announcing InfluxDB IOx – The Future Core of InfluxDB Built with Rust and Arrow的文章,介紹了一個新項目 InfluxDB IOx,InfluxDB 的下一代時序引擎。微信
接下來,我將連載對於InfluxDB IOx的源碼解析過程,歡迎各位批評指正,聯繫方式見文章末尾。ide
上篇介紹到:InfluxDB-IOx的環境搭建,詳情見:https://my.oschina.net/u/3374539/blog/5016798oop
本章開始,講解啓動的主流程!性能
打開src/main.rs
文件能夠找到下面的代碼學習
fn main() -> Result<(), std::io::Error> { // load all environment variables from .env before doing anything load_dotenv(); let config = Config::from_args(); println!("{:?}", config); //省略 ..... Ok(()) }
在main
方法中映入眼簾的第一行就是load_dotenv()
方法,而後是Config::from_args()
接下來就分別跟蹤這兩個方法,看明白是怎麼工做的。ui
加載配置文件
在README
文件中,咱們能夠看到這樣一行:google
Should you desire specifying config via a file, you can do so using a .env formatted file in the working directory. You can use the provided example as a template if you want:url
cp docs/env.example .env
意思就是這個工程使用的配置文件,名字是.env
。瞭解這個特殊的名字以後,咱們看代碼src/main.rs:276
:.net
fn load_dotenv() { //調用dotenv方法,並對其返回值進行判斷 match dotenv() { //若是返回成功,程序什麼都不作,繼續執行。 Ok(_) => {} //返回的是錯誤,那麼判斷一下是否爲'未找到'錯誤, //若是是未找到,那麼就什麼都不作(也就是有默認值填充) Err(dotenv::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => { } //這裏就是真真正正必需要處理的錯誤了,直接退出程序 Err(e) => { eprintln!("FATAL Error loading config from: {}", e); eprintln!("Aborting"); std::process::exit(1); } }; }
而後跟蹤dotenv()
方法看看如何執行(這裏就進入了dotenv這個crate了): 爲了方便寫,我就直接把全部調用,從上到下的順序全都寫出來了
//返回一個PathBuf的Result,以後再看這個Result pub fn dotenv() -> Result<PathBuf> { //new一個Finder結構並調用find方法 //?表明錯誤的時候直接拋出錯誤 let (path, iter) = Finder::new().find()?; //返回一個自定義的Iter結構,並調用load方法 iter.load()?; //成功返回 Ok(path) } //建立一個Finder結構體,filename使用`.env`填充 pub fn new() -> Self { Finder { filename: Path::new(".env"), } } //返回一個元組,多個返回值,(路徑,文件讀取相關記錄) pub fn find(self) -> Result<(PathBuf, Iter<File>)> { //使用標準庫中的current_dir()方法獲得當前的路徑 //出錯就返回Error::Io錯誤,正常就調用find方法 let path = find(&env::current_dir().map_err(Error::Io)?, self.filename)?; //若是找到了.env文件就打開,打開錯誤就返回Error::Io錯誤 let file = File::open(&path).map_err(Error::Io)?; //使用打開的文件建立一個Iter的結構 let iter = Iter::new(file); //返回 Ok((path, iter)) } //遞歸查找.env文件 pub fn find(directory: &Path, filename: &Path) -> Result<PathBuf> { //拼裝一個全路徑 let candidate = directory.join(filename); //嘗試打開這個文件 match fs::metadata(&candidate) { //成功打開了,說明找到了.env文件,就返回成功 //但我有個疑問文件內容爲啥不校驗一下呢? Ok(metadata) => if metadata.is_file() { return Ok(candidate); }, //除了沒找到文件的錯誤以外,其它錯誤都直接返回異常 Err(error) => { if error.kind() != io::ErrorKind::NotFound { return Err(Error::Io(error)); } } } //沒找到的時候,就返回到父級文件夾裏,繼續找,一直到根文件夾 if let Some(parent) = directory.parent() { find(parent, filename) } else { //一直到根文件夾,還沒找到就返回一個NotFound的IO錯誤, //這個在上面的代碼中提到,這個錯誤會被忽略 Err(Error::Io(io::Error::new(io::ErrorKind::NotFound, "path not found"))) } } //對應的iter.load()?;方法實現 pub fn load(self) -> Result<()> { //可使用for是由於實現了Iterator 這個trait for item in self { //獲取讀取出來的一行一行的配置項 let (key, value) = item?; //驗證key沒有什麼問題,就放到env中 if env::var(&key).is_err() { env::set_var(&key, value); } } Ok(()) } // 爲了可以for循環,實現的Iterator impl<R: Read> Iterator for Iter<R> { type Item = Result<(String, String)>; fn next(&mut self) -> Option<Self::Item> { loop { //一行一行的讀取文件內容 let line = match self.lines.next() { Some(Ok(line)) => line, Some(Err(err)) => return Some(Err(Error::Io(err))), None => return None, }; //解析配置項目,這裏就不在深刻跟了 match parse::parse_line(&line, &mut self.substitution_data) { Ok(Some(result)) => return Some(Ok(result)), Ok(None) => {} Err(err) => return Some(Err(err)), } } } }
研究這裏的時候,我發現了一個比較好玩兒的東西就是返回值的Result<PathBuf>
。標準庫的定義中,Result是有兩個值,分別是<T,E>。
自定義的類型,節省了Error這個模板代碼 pub type Result<T> = std::result::Result<T, Error>; //Error也本身定義 pub enum Error { LineParse(String, usize), Io(io::Error), EnvVar(std::env::VarError), #[doc(hidden)] __Nonexhaustive } //實現一個not_found()的方法來判斷是否爲not_found的一個錯誤類型 impl Error { pub fn not_found(&self) -> bool { if let Error::Io(ref io_error) = *self { return io_error.kind() == io::ErrorKind::NotFound; } false } } //實現標準庫中的error::Error這個trait impl error::Error for Error { //追蹤錯誤的上一級,應該是打印堆棧這種功能 //若是內部有錯誤類型Err返回:Some(e),若是沒有返回:None //關於'static這個生命週期的標註,我也不是很理解 //是指存儲的錯誤生命週期足夠長仍是什麼? fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Error::Io(err) => Some(err), Error::EnvVar(err) => Some(err), _ => None, } } } //實現錯誤的打印 impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self { Error::Io(err) => write!(fmt, "{}", err), Error::EnvVar(err) => write!(fmt, "{}", err), Error::LineParse(line, error_index) => write!(fmt, "Error parsing line: '{}', error at line index: {}", line, error_index), _ => unreachable!(), } } }
更詳細的rust錯誤處理,能夠參見:https://zhuanlan.zhihu.com/p/109242831
命令行參數
在main方法中咱們能夠看到第二行,
let config = Config::from_args();
這是influx
使用了structopt
這個crate
,調用該方法後,程序會根據結構體上的#[structopt()]
中的參數進行執行命令行解析。
#[derive(Debug, StructOpt)] #[structopt( //cargo的crate名字 name = "influxdb_iox", //打印出來介紹 about = "InfluxDB IOx server and command line tools", long_about = // 省略 ... )] struct Config { // from_occurrences表明出現了幾回,就是-vvv的時候v出現的次數 #[structopt(short, long, parse(from_occurrences))] verbose: u64, #[structopt( short, long, global = true, env = "IOX_ADDR", default_value = "http://127.0.0.1:8082" )] host: String, #[structopt(long)] num_threads: Option<usize>, //subcommand表明是一個子類型的, //具體還有什麼命令行要去子類型裏繼續解析, //這個字段不展現在命令行中 #[structopt(subcommand)] command: Command, } //在influx的命令行中提供了8個主要的命令, //在上一章中使用到的run參數就是屬於Run(Box<commands::run::Config>)裏的調用。 //這裏都是subcommand,須要繼續解析,這個在之後學習每一個具體功能的時候再分析 #[derive(Debug, StructOpt)] enum Command { Convert { // 省略 ...}, Meta {// 省略 ...}, Database(commands::database::Config), Run(Box<commands::run::Config>), Stats(commands::stats::Config), Server(commands::server::Config), Writer(commands::writer::Config), Operation(commands::operations::Config), }
下面經過打印出來的例子來對應structopt
中的內容。
$ ./influxdb_iox -vvvv run Config { verbose: 4, host: "http://127.0.0.1:8082", num_threads: None, command: Run(Config { rust_log: None, log_format: None, verbose_count: 0, writer_id: None, http_bind_address: 127.0.0.1:8080, grpc_bind_address: 127.0.0.1:8082, database_directory: None, object_store: None, bucket: None, aws_access_key_id: None, aws_secret_access_key: None, aws_default_region: "us-east-1", google_service_account: None, azure_storage_account: None, azure_storage_access_key: None, jaeger_host: None }) }
能夠看到,咱們執行了Run
這個變體的Subcommand
,而且指定了Config
結構體中的verbose
4 次,IOx
也成功的識別了。
後面繼續學習程序的啓動過程,祝玩兒的開心!
歡迎關注微信公衆號:
或添加微信好友: liutaohua001