時序數據庫Influx-IOx源碼學習三(命令行及配置)

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

相關文章
相關標籤/搜索