时序数据库InfluxIOx源码学习三(命令行及配置)

database

欢迎关注公众号:

上篇介绍到:InfluxDB-IOx的环境搭建,详情见:https://my.oschina.net/u/3374539/blog/5016798

本章开始,讲解启动的主流程!

打开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()接下来就分别跟踪这两个方法,看明白是怎么工作的。

加载配置文件

README文件中,我们可以看到这样一行:

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:

cp docs/env.example .env

意思就是这个工程使用的配置文件,名字是.env。了解这个特殊的名字之后,我们看代码src/main.rs:276

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也成功的识别了。

后面继续学习程序的启动过程,祝玩儿的开心!

以上是 时序数据库InfluxIOx源码学习三(命令行及配置) 的全部内容, 来源链接: utcz.com/z/535412.html

回到顶部