query 启动入口

Databend-query server 的启动入口在 databend/src/binaries/query/main.rs 下,在初始化配置之后,它会创建一个 GlobalServices 和 server 关闭时负责处理 shutdown 逻辑的 shutdown_handle

  1. GlobalServices::init(conf.clone()).await?;
  2. let mut shutdown_handle = ShutdownHandle::create()?;

GlobalServices

GlobalServices 负责启动 databend-query 的所有全局服务,这些服务都遵循单一责任原则。

  1. pub struct GlobalServices {
  2.     global_runtime: UnsafeCell<Option<Arc<Runtime>>>,
  3.     // 负责处理 query log
  4.     query_logger: UnsafeCell<Option<Arc<QueryLogger>>>,
  5.     // 负责 databend query 集群发现
  6.     cluster_discovery: UnsafeCell<Option<Arc<ClusterDiscovery>>>,
  7.     // 负责与 storage 层交互来读写数据
  8.     storage_operator: UnsafeCell<Option<Operator>>,
  9.     async_insert_manager: UnsafeCell<Option<Arc<AsyncInsertManager>>>,
  10.     cache_manager: UnsafeCell<Option<Arc<CacheManager>>>,
  11.     catalog_manager: UnsafeCell<Option<Arc<CatalogManager>>>,
  12.     http_query_manager: UnsafeCell<Option<Arc<HttpQueryManager>>>,
  13.     data_exchange_manager: UnsafeCell<Option<Arc<DataExchangeManager>>>,
  14.     session_manager: UnsafeCell<Option<Arc<SessionManager>>>,
  15.     users_manager: UnsafeCell<Option<Arc<UserApiProvider>>>,
  16.     users_role_manager: UnsafeCell<Option<Arc<RoleCacheManager>>>,
  17. }

GlobalServices 中的全局服务都实现了单例 trait,这些全局管理器后续会有对应的源码分析文章介绍,本文介绍与 Session 处理相关的逻辑。

  1. pub trait SingletonImpl<T>: Send + Sync {
  2.     fn get(&self) -> T;
  3.     fn init(&self, value: T) -> Result<()>;
  4. }
  5. pub type Singleton<T> = Arc<dyn SingletonImpl<T>>;

ShutdownHandle

接下来会根据网络协议初始化 handlers,并把它们注册到 shutdown_handler 的 services 中,任何实现 Server trait 的类型都可以被添加到 services 中。

  1. #[async_trait::async_trait]
  2. pub trait Server: Send {
  3.     async fn shutdown(&mut self, graceful: bool);
  4.     async fn start(&mut self, listening: SocketAddr) -> Result<SocketAddr>;
  5. }

目前 Databend 支持三种协议提交查询请求(mysql, clickhouse http, raw http)。

  1. // MySQL handler.
  2. {
  3.     let hostname = conf.query.mysql_handler_host.clone();
  4.     let listening = format!("{}:{}", hostname, conf.query.mysql_handler_port);
  5.     let mut handler = MySQLHandler::create(session_manager.clone());
  6.     let listening = handler.start(listening.parse()?).await?;
  7.     // 注册服务到 shutdown_handle 来处理 server shutdown 时候的关闭逻辑,下同
  8.     shutdown_handle.add_service(handler);
  9. }
  10. // ClickHouse HTTP handler.
  11. {
  12.     let hostname = conf.query.clickhouse_http_handler_host.clone();
  13.     let listening = format!("{}:{}", hostname, conf.query.clickhouse_http_handler_port);
  14.     let mut srv = HttpHandler::create(session_manager.clone(), HttpHandlerKind::Clickhouse);
  15.     let listening = srv.start(listening.parse()?).await?;
  16.     shutdown_handle.add_service(srv);
  17. }
  18. // Databend HTTP handler.
  19. {
  20.     let hostname = conf.query.http_handler_host.clone();
  21.     let listening = format!("{}:{}", hostname, conf.query.http_handler_port);
  22.     let mut srv = HttpHandler::create(session_manager.clone(), HttpHandlerKind::Query);
  23.     let listening = srv.start(listening.parse()?).await?;
  24.     shutdown_handle.add_service(srv);
  25. }

之后会创建一些其它服务

  • Metric service: 指标服务

  • Admin service: 负责处理管理信息

  • RPC service: query 节点的 rpc 服务,负责 query 节点之间的通信,使用 arrow flight 协议

  1. // Metric API service.
  2. {
  3.     let address = conf.query.metric_api_address.clone();
  4.     let mut srv = MetricService::create(session_manager.clone());
  5.     let listening = srv.start(address.parse()?).await?;
  6.     shutdown_handle.add_service(srv);
  7.     info!("Listening for Metric API: {}/metrics", listening);
  8. }
  9. // Admin HTTP API service.
  10. {
  11.     let address = conf.query.admin_api_address.clone();
  12.     let mut srv = HttpService::create(session_manager.clone());
  13.     let listening = srv.start(address.parse()?).await?;
  14.     shutdown_handle.add_service(srv);
  15.     info!("Listening for Admin HTTP API: {}", listening);
  16. }
  17. // RPC API service.
  18. {
  19.     let address = conf.query.flight_api_address.clone();
  20.     let mut srv = RpcService::create(session_manager.clone());
  21.     let listening = srv.start(address.parse()?).await?;
  22.     shutdown_handle.add_service(srv);
  23.     info!("Listening for RPC API (interserver): {}", listening);
  24. }

最后会将这个 query 节点注册到 meta server 中。

  1. // Cluster register.
  2. {
  3.     let cluster_discovery = session_manager.get_cluster_discovery();
  4.     let register_to_metastore = cluster_discovery.register_to_metastore(&conf);
  5.     register_to_metastore.await?;
  6. }

Session 相关

session 主要分为 4 个部分

  1. session_manager: 全局唯一,负责管理 client session

  2. session: 每当有新的 client 连接到 server 之后会创建一个新的 session 并且注册到 session_manager

  3. query_ctx: 每一条查询语句会有一个 query_ctx,用来存储当前查询的一些上下文信息

  4. query_ctx_shared: 查询语句中的子查询共享的上下文信息

下面逐一来分析

SessionManager (query/src/sessions/session_mgr.rs)

  1. pub struct SessionManager {
  2.     pub(in crate::sessions) conf: Config,
  3.     pub(in crate::sessions) max_sessions: usize,
  4.     pub(in crate::sessions) active_sessions: Arc<RwLock<HashMap<String, Arc<Session>>>>,
  5.     pub status: Arc<RwLock<SessionManagerStatus>>,
  6.     // When session type is MySQL, insert into this map, key is id, val is MySQL connection id.
  7.     pub(crate) mysql_conn_map: Arc<RwLock<HashMap<Option<u32>, String>>>,
  8.     pub(in crate::sessions) mysql_basic_conn_id: AtomicU32,
  9. }

SessionManager 主要用来创建和销毁 session,对应方法如下

  1. // 根据 client 协议类型来创建 session
  2. pub async fn create_session(self: &Arc<Self>, typ: SessionType) -> Result<SessionRef>
  3. // 根据 session id 来销毁 session
  4. pub fn destroy_session(self: &Arc<Self>, session_id: &String)

Session (query/src/sessions/session.rs)

session 主要存储 client-server 的上下文信息,代码命名已经很清晰了,这里就不再过多赘述。

  1. pub struct Session {
  2.     pub(in crate::sessions) id: String,
  3.     pub(in crate::sessions) typ: RwLock<SessionType>,
  4.     pub(in crate::sessions) session_ctx: Arc<SessionContext>,
  5.     status: Arc<RwLock<SessionStatus>>,
  6.     pub(in crate::sessions) mysql_connection_id: Option<u32>,
  7. }
  8. pub struct SessionContext {
  9.     conf: Config,
  10.     abort: AtomicBool,
  11.     current_catalog: RwLock<String>,
  12.     current_database: RwLock<String>,
  13.     current_tenant: RwLock<String>,
  14.     current_user: RwLock<Option<UserInfo>>,
  15.     auth_role: RwLock<Option<String>>,
  16.     client_host: RwLock<Option<SocketAddr>>,
  17.     io_shutdown_tx: RwLock<Option<Sender<Sender<()>>>>,
  18.     query_context_shared: RwLock<Option<Arc<QueryContextShared>>>,
  19. }
  20. pub struct SessionStatus {
  21.     pub session_started_at: Instant,
  22.     pub last_query_finished_at: Option<Instant>,
  23. }

Session 的另一个大的功能是负责创建和获取 QueryContext,每次接收到新的 query 请求都会创建一个 QueryContext 并绑定在对应的 query 语句上。

QueryContext (query/src/sessions/query_ctx.rs)

QueryContext 主要是维护查询的上下文信息,它通过 QueryContext::create_from_shared(query_ctx_shared)创建。

  1. #[derive(Clone)]
  2. pub struct QueryContext {
  3.     version: String,
  4.     statistics: Arc<RwLock<Statistics>>,
  5.     partition_queue: Arc<RwLock<VecDeque<PartInfoPtr>>>,
  6.     shared: Arc<QueryContextShared>,
  7.     precommit_blocks: Arc<RwLock<Vec<DataBlock>>>,
  8.     fragment_id: Arc<AtomicUsize>,
  9. }

其中 partition_queue 主要存储查询对应的 PartInfo,包括 part 的地址、版本信息、涉及数据的行数,part 使用的压缩算法、以及涉及到 column 的 meta 信息。在 pipeline build 时候会去设置 partition。pipeline 后续会有专门的文章介绍。

precommit_blocks 负责暂存插入操作的时已经写入到存储, 但是尚未提交的元数据,DataBlock 主要包含 Column 的元信息引用和 arrow schema 的信息。

QueryContextShared (query/src/sessions/query_ctx_shared.rs)

对于包含子查询的查询,需要共享很多上下文信息,这就是 QueryContextShared存在的理由。

  1. /// 数据需要在查询上下文中被共享,这个很重要,比如:
  2. ///     USE database_1;
  3. ///     SELECT
  4. ///         (SELECT scalar FROM table_name_1) AS scalar_1,
  5. ///         (SELECT scalar FROM table_name_2) AS scalar_2,
  6. ///         (SELECT scalar FROM table_name_3) AS scalar_3
  7. ///     FROM table_name_4;
  8. /// 对于上面子查询, 会共享 runtime, session, progress, init_query_id
  9. pub struct QueryContextShared {
  10.     /// scan_progress for scan metrics of datablocks (uncompressed)
  11.     pub(in crate::sessions) scan_progress: Arc<Progress>,
  12.     /// write_progress for write/commit metrics of datablocks (uncompressed)
  13.     pub(in crate::sessions) write_progress: Arc<Progress>,
  14.     /// result_progress for metrics of result datablocks (uncompressed)
  15.     pub(in crate::sessions) result_progress: Arc<Progress>,
  16.     pub(in crate::sessions) error: Arc<Mutex<Option<ErrorCode>>>,
  17.     pub(in crate::sessions) session: Arc<Session>,
  18.     pub(in crate::sessions) runtime: Arc<RwLock<Option<Arc<Runtime>>>>,
  19.     pub(in crate::sessions) init_query_id: Arc<RwLock<String>>,
  20.     ...
  21. }

它提供了 query 上下文所需要的一切基本信息。

Handler

之前提到了 Databend 支持多种 handler,下面就以 mysql 为例,看一下 handler 的处理流程以及如何与 session 产生交互。

首先 MySQLHandler 会包含一个 SessionManager 的引用

  1. pub struct MySQLHandler {
  2.     abort_handle: AbortHandle,
  3.     abort_registration: Option<AbortRegistration>,
  4.     join_handle: Option<JoinHandle<()>>,
  5. }

MySQLHandler 在启动后,会 spawn 一个 tokio task 来持续监听 tcp stream,并且创建一个 session 再启动一个 task 去执行之后的查询请求。

  1. fn accept_socket(session_mgr: Arc<SessionManager>, executor: Arc<Runtime>, socket: TcpStream) {
  2.     executor.spawn(async move {
  3.         // 创建 session
  4.         match session_mgr.create_session(SessionType::MySQL).await {
  5.             Err(error) => Self::reject_session(socket, error).await,
  6.             Ok(session) => {
  7.                 info!("MySQL connection coming: {:?}", socket.peer_addr());
  8.                 // 执行查询
  9.                 if let Err(error) = MySQLConnection::run_on_stream(session, socket) {
  10.                     error!("Unexpected error occurred during query: {:?}", error);
  11.                 };
  12.             }
  13.         }
  14.     });
  15. }

在 MySQLConnection::run_on_stream中,session 会先 attach 到对应的 client host 并且注册一个 shutdown 闭包来处理关闭连接关闭时需要执行的清理,关键代码如下:

  1. // mysql_session.rs
  2. pub fn run_on_stream(session: SessionRef, stream: TcpStream) -> Result<()> {
  3.     let blocking_stream = Self::convert_stream(stream)?;
  4.     MySQLConnection::attach_session(&session, &blocking_stream)?;
  5.     ...
  6. }
  7. fn attach_session(session: &SessionRef, blocking_stream: &std::net::TcpStream) -> Result<()> {
  8.     let host = blocking_stream.peer_addr().ok();
  9.     let blocking_stream_ref = blocking_stream.try_clone()?;
  10.     session.attach(host, move || {
  11.         // 注册 shutdown 逻辑
  12.         if let Err(error) = blocking_stream_ref.shutdown(Shutdown::Both) {
  13.             error!("Cannot shutdown MySQL session io {}", error);
  14.         }
  15.     });
  16.     Ok(())
  17. }
  18. // session.rs
  19. pub fn attach<F>(self: &Arc<Self>, host: Option<SocketAddr>, io_shutdown: F)
  20. where F: FnOnce() + Send + 'static {
  21.     let (tx, rx) = oneshot::channel();
  22.     self.session_ctx.set_client_host(host);
  23.     self.session_ctx.set_io_shutdown_tx(Some(tx));
  24.     common_base::base::tokio::spawn(async move {
  25.         // 在 session quit 时候触发清理
  26.         if let Ok(tx) = rx.await {
  27.             (io_shutdown)();
  28.             tx.send(()).ok();
  29.         }
  30.     });
  31. }

之后会启动一个 MySQL InteractiveWorker 来处理后续的查询。

  1. let join_handle = query_executor.spawn(async move {
  2.     let client_addr = non_blocking_stream.peer_addr().unwrap().to_string();
  3.     let interactive_worker = InteractiveWorker::create(session, client_addr);
  4.     let opts = IntermediaryOptions {
  5.         process_use_statement_on_query: true,
  6.     };
  7.     let (r, w) = non_blocking_stream.into_split();
  8.     let w = BufWriter::with_capacity(DEFAULT_RESULT_SET_WRITE_BUFFER_SIZE, w);
  9.     AsyncMysqlIntermediary::run_with_options(interactive_worker, r, w, &opts).await
  10. });
  11. let _ = futures::executor::block_on(join_handle);

该 InteractiveWorker会实现 AsyncMysqlShim trait 的方法,比如:on_executeon_query 等。查询到来时会回调这些方法来执行查询。这里以 on_query 为例,关键代码如下:

  1. async fn on_query<'a>(
  2.     &'a mut self,
  3.     query: &'a str,
  4.     writer: QueryResultWriter<'a, W>,
  5. ) -> Result<()> {
  6.     ...
  7.     // response writer
  8.     let mut writer = DFQueryResultWriter::create(writer);
  9.     let instant = Instant::now();
  10.     // 执行查询
  11.     let blocks = self.base.do_query(query).await;
  12.     // 回写结果
  13.     let format = self.base.session.get_format_settings()?;
  14.     let mut write_result = writer.write(blocks, &format);
  15.     ...
  16.     // metrics 信息
  17.     histogram!(
  18.         super::mysql_metrics::METRIC_MYSQL_PROCESSOR_REQUEST_DURATION,
  19.         instant.elapsed()
  20.     );
  21.     write_result
  22. }

在 do_query 中会创建 QueryContext 并开始解析 sql 流程来完成后续的整个 sql 查询。关键代码如下:

  1. // 创建 QueryContext
  2. let context = self.session.create_query_context().await?;
  3. // 关联到查询语句
  4. context.attach_query_str(query);
  5. let settings = context.get_settings();
  6. // parse sql
  7. let stmts_hints = DfParser::parse_sql(query, context.get_current_session().get_type());
  8. ...
  9. // 创建并生成查询计划
  10. let mut planner = Planner::new(context.clone());
  11. let interpreter = planner.plan_sql(query).await.and_then(|v| {
  12.     has_result_set = has_result_set_by_plan(&v.0);
  13.     InterpreterFactoryV2::get(context.clone(), &v.0)
  14. })
  15. // 执行查询,返回结果
  16. Self::exec_query(interpreter.clone(), &context).await?;
  17. let schema = interpreter.schema();
  18. Ok(QueryResult::create(
  19.     blocks,
  20.     extra_info,
  21.     has_result_set,
  22.     schema,
  23. ))

尾声

以上就是从 Databend 启动服务到接受 sql 请求并开始处理的流程。最近我们因为一些原因(Clickhouse tcp 协议偏向 clickhouse 的底层,协议没有公开的文档说明,同时里面历史包袱比较重,排查问题浪费大量精力)去掉了 ClickHouse native tcp client,具体请参见: https://github.com/datafuselabs/databend/pull/7012

如果你阅读完代码有好的提议,欢迎来这里讨论,另外如果发现相关的问题,可以提交到 issue 来帮助我们提高 Databend 的稳定性。Databend 社区欢迎一切善意的意见和建议

关于 Databend

Databend 是一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式数仓。期待您的关注,一起探索云原生数仓解决方案,打造新一代开源 Data Cloud。



文章首发于公众号:Databend

Databend 源码阅读系列(二):Query server 启动,Session 管理及请求处理的更多相关文章

  1. swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?

    date: 2018-8-01 14:22:17title: swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?description: 阅读 sowft 框架源码, 了解 sowf ...

  2. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  3. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  4. Spring源码阅读系列总结

    最近一段时间,粗略的查看了一下Spring源码,对Spring的两大核心和Spring的组件有了更深入的了解.同时在学习Spring源码时,得了解一些设计模式,不然阅读源码还是有一定难度的,所以一些重 ...

  5. JDK1.8源码阅读系列之三:Vector

    本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...

  6. 【详解】ThreadPoolExecutor源码阅读(二)

    系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) AQS在W ...

  7. Redis源码阅读(二)高可用设计——复制

    Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...

  8. 【合集】TiDB 源码阅读系列文章

    [合集]TiDB 源码阅读系列文章 (一)序 (二)初识 TiDB 源码 (三)SQL 的一生 (四)INSERT 语句概览 (五)TiDB SQL Parser 的实现 (六)Select 语句概览 ...

  9. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

随机推荐

  1. 8.shell编程之免交互

    shell编程之免交互 目录 shell编程之免交互 Here Document免交互 免交互定义 Here Document变量设定 多行的注释 expect expect 定义 expect基本命 ...

  2. easyui combobox重复渲染问题

    当一个页面有两个easyui combobox存在时,并且同时给两个combobox赋相同值,某些easyui的版本会导致其中一个无法切换选项. 解决办法,分两步赋值,可解决问题

  3. 细说GaussDB(DWS)复杂多样的资源负载管理手段

    摘要:对于如此多的管控功能,管控起来实际的效果到底如何,本篇文章就基于当前最新版本,进行效果实测,并进行一定的分析说明. 本文分享自华为云社区<GaussDB(DWS) 资源负载管理:并发管控以 ...

  4. 【小程序自动化Minium】二、元素定位-Page接口中的 get_element() 与 get_elements()

    UI自动化中的重要工作就是元素定位了,高效精准的定位方法可以让工作事半功倍. 在过去的一段web自动化经历中,使用的selenium库支持了多种定位方法,我们可以利用这些定位方法来做进一步封装,写出符 ...

  5. 聊聊 Netty 那些事儿之 Reactor 在 Netty 中的实现(创建篇)

    本系列Netty源码解析文章基于 4.1.56.Final版本 在上篇文章<聊聊Netty那些事儿之从内核角度看IO模型>中我们花了大量的篇幅来从内核角度详细讲述了五种IO模型的演进过程以 ...

  6. 数据结构-二叉树(Binary Tree)

    1.二叉树(Binary Tree) 是n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根节点和两棵互不相交的,分别称为根节点的左子树和右子树的二叉树组成.  2.特数二 ...

  7. 攻防世界MISC—进阶区1-10

    1.something_in_image zip中的文件用010 Editor打开后直接搜索flag,即可找到flag 2.wireshark-1 zip内是pcap文件,打开后根据题目知道要寻找登录 ...

  8. cx_Oracle.DatabaseError: ORA-28759: failure to open file

    找了好久这个问题,有人说是tcps的问题,需要自己生成证书什么的,后来才发现原来是 钱包文件路径 的问题,钱包文件解压后必须放在instantclien/network/admin下,在Windows ...

  9. linux新建分区和磁盘

    1.查看已有分区 ]# df –hl fdisk -l 查看磁盘情况 ]# fdisk –l 2.对未分区的进行分区 # fdisk /dev/vdb 硬盘分区 创建了一个55G的分区磁盘 1.新建第 ...

  10. MySQL--SELECT检索语句

    1.检索单个列 SELECT prod_name FROM products; --上述语句利用 SELECT语句从 products表中检索一个名为prod_name的列. 结束SQL:多条SQL语 ...