obproxy sql路由模块源码阅读

OBProxy作为OceanBase专用的反向代理服务器,功能非常强大。要学习obproxy,决定从源码开始阅读。首先选择路由模块。

路由选择是OBProxy的核心功能。路由选择的输入用户的SQL,用户配置规则,ObServer的状态,路由选择的输出是一个可用ObServer地址。其路由逻辑可以入下图所示:

    路由选择的相关代码在路径obproxy-master\src\obproxy\proxy\route下。首先可以看到在ob_mysql_route.h中定义了若干状态,从ObMysqlRoute::state_route_start()开始,状态之间的变换如下图所示:

下面分别针对每一个动作去看相关的代码。

首先是ROUTE_ACTION_ROUTINE_ENTRY_LOOKUP_START,在ObMysqlRoute::setup_routine_entry_lookup()可以看到,首先是调用ObRouteUtils::convert_route_param_to_routine_param()来处理参数,然后ObRoutineProcessor::get_routine_entry()中先尝试从本地的thread_cache中获取,如果失败则从remote_cache或者global_cache中获取。如果从cache中获取成功,则触发一个ROUTINE_ENTRY_LOOKUP_CACHE_DONE事件,否则转入ROUTE_ACTION_ROUTINE_ENTRY_LOOKUP_DONE状态。

在ObMysqlRoute::handle_routine_entry_lookup_done()中,如果如果上一步的routine entry获取成功,则转入ROUTE_ACTION_ROUTE_SQL_PARSE_START状态,否则使用默认route,并且转入ROUTE_ACTION_TABLE_ENTRY_LOOKUP_START状态。

在ObMysqlRoute::setup_route_sql_parse()函数中,首先使用函数分配一个ObProxyParser对象,然后调用ObProxyParser类的parse()函数对sql进行解析,得到返回值是一个ObSqlParseResult对象,包括sql type, table_name以及database_name等等。之后转入ROUTE_ACTION_ROUTE_SQL_PARSE_DONE状态,如果没有报错,则继续转入ROUTE_ACTION_TABLE_ENTRY_LOOKUP_START状态。

在ObMysqlRoute::setup_table_entry_lookup()函数中,首先获得一个ObTableProcessor对象,然后调用ObTableProcessor::get_table_entry()函数来查询。与之前类似,也是先查询thread_cache,失败后再查询global cache。 如果查询成功则触发一个TABLE_ENTRY_EVENT_LOOKUP_DONE事件,否则转入ROUTE_ACTION_TABLE_ENTRY_LOOKUP_DONE状态。

在ObMysqlRoute::handle_table_entry_lookup_done()中,判断表是否为分区表,如果是分区表则转入ROUTE_ACTION_PARTITION_ID_CALC_STAR状态,否则转入ROUTE_ACTION_NOTIFY_OUT状态。

在ObMysqlRoute::setup_partition_id_calc()函数中,通过ObProxyExprCalculator类的calculate_partition_id函数,计算出需要发到哪个分区,并且转入ROUTE_ACTION_PARTITION_ID_CALC_DONE状态。计算的过程包括calc_part_id_with_simple_route_info, do_expr_parse,do_expr_resolve以及do_partition_id_calc等几个关键函数。

ObMysqlRoute::handle_partition_id_calc_done()中,如果表达式上套用了不支持的函数,或者表不支持分区路由,或者sql语句中不包含分区键,则转入ROUTE_ACTION_NOTIFY_OUT状态。否则转入ROUTE_ACTION_PARTITION_ENTRY_LOOKUP_START状态。

ObMysqlRoute::setup_partition_entry_lookup()中,通过ObPartitionProcessor::get_partition_entry()函数来获取分区对应的节点。首先也是通过get_partition_entry_from_thread_cache函数来访问thread cache, 没有成功的话就去访问remote或者global cache。 这里是会触发一个PARTITION_ENTRY_LOOKUP_START_EVENT事件。查询成功后转入PARTITION_ENTRY_LOOKUP_CACHE_DONE状态。

ObMysqlRoute::handle_partition_entry_lookup_done()的逻辑很简单,如果上一步partition entry查询成功则转入ROUTE_ACTION_NOTIFY_OUT状态,否则报错。

ObMysqlRoute::notify_caller()用来通知调用者回掉已完成,这里是处理结果,以及处理cancel以及各种错误。(这里通过ObContinuation这个类来实现状态机转换时的各种事件以及回调)。

将整个状态机拆分来看,首先是cache的部分。Obproxy中包含三个cache,routine_cache, table_cache以及partition_cache。对于cache的查询也都是首先尝试本地命中thread_cache,如果失败则通过触发一个event事件来查询global cache,并等待查询结果返回。这里我的理解是因为多线程并发访问global cache,通常来说是要加锁的。但是加锁会导致性能下降。这里obproxy选择通过异步的方式进行,触发一个查询事件,然后通过回调的方式在查询完成后唤醒线程。

以tableCache为例,其本质是一个hashmap,默认初始化为1024条。Key包括表明,版本号以及id等。Value包括比较完整的表元数据信息。

然后是obproxy中的事件处理机制。Obproxy中自定义了一个类ObContinuation来处理状态机中各种事件和回调。为了实现状态机,其他类需要继承ObContinuation类,并包含额外的状态和方法来实现控制流。另外由于ObEvent System通常运行在多线程状态下,ObContinuation类中需要包含一个ObProxyMutex对象来保护状态并实现各种原子操作。ObProxyMutex对象必须由子类或者事件调用者分配并包含在ObContinuation类的构造函数里。

例如访问tableCache,则需要定义一个ObTableCacheCont类,继承ObContinuation类。

最后来看对sql的解析。分别是进行sql语句的解析,然后对表达式进行解析,计算,最后得到分区的id值。

ObProxyParser::parse()函数,传入sql_string为参数,返回ObProxyParseResult类的parse_result对象。词法和语法规则在obproxy-master/src/obproxy/opsql/parser目录下,分别只有一千行左右。

ObProxyParseResult类里定义了语法解析的返回值。在ObSqlParseResult::load_result()函数中将ObProxyParseResult中的结果load到ObSqlParseResult中。之后可以使用ObSqlParseResult中定义的接口来获取信息,如下所示:

bool is_invalid_stmt() const { return OBPROXY_T_INVALID == stmt_type_; }

  bool is_start_trans_stmt() const { return OBPROXY_T_BEGIN == stmt_type_; }

  bool is_select_stmt() const { return OBPROXY_T_SELECT == stmt_type_; }

  bool is_select_database_stmt() const { return OBPROXY_T_SELECT == stmt_type_ && OBPROXY_T_SUB_SELECT_DATABASE == cmd_sub_type_; }

  bool is_update_stmt() const { return OBPROXY_T_UPDATE == stmt_type_; }

  bool is_replace_stmt() const { return OBPROXY_T_REPLACE == stmt_type_; }

  bool is_insert_stmt() const { return OBPROXY_T_INSERT == stmt_type_; }

  bool is_delete_stmt() const { return OBPROXY_T_DELETE == stmt_type_; }

  bool is_merge_stmt() const { return OBPROXY_T_MERGE == stmt_type_; }

  bool is_set_stmt() const { return OBPROXY_T_SET == stmt_type_; }

  bool is_set_names_stmt() const { return OBPROXY_T_SET_NAMES == stmt_type_; }

  bool is_use_db_stmt() const { return OBPROXY_T_USE_DB == stmt_type_; }

int64_t get_hint_query_timeout() const { return hint_query_timeout_; }

  common::ObConsistencyLevel get_hint_consistency_level() const { return hint_consistency_level_; }

  int64_t get_parsed_length() const { return parsed_length_; }

  const common::ObString get_table_name() const { return table_name_; }

  const common::ObString get_package_name() const { return package_name_; }

  const common::ObString get_database_name() const { return database_name_; }

  const common::ObString get_origin_table_name() const { return origin_table_name_; }

  const common::ObString get_origin_database_name() const { return origin_database_name_;}

  const common::ObString get_col_name() const { return col_name_; }

  const common::ObString get_alias_name() const { return alias_name_; }

  const common::ObString get_part_name() const { return part_name_; }

  const common::ObString get_text_ps_name() const { return text_ps_name_; }

  ObProxyParseQuoteType get_table_name_quote() const { return table_name_quote_; }

  ObProxyParseQuoteType get_package_name_quote() const { return package_name_quote_; }

  ObProxyParseQuoteType get_database_name_quote() const { return database_name_quote_; }

  ObProxyParseQuoteType get_alias_name_quote() const { return alias_name_quote_; }

  ObProxyParseQuoteType get_col_name_quote() const { return col_name_quote_; }

  const common::ObString get_trace_id() const { return trace_id_; }

  const common::ObString get_rpc_id() const { return rpc_id_; }

之后可以利用parse result做一些操作,例如判断是否弱读。如果是弱读,不要求数据是最新,可以把这个请求发往备副本,这样主副本的压力就可以降下来,可以做一下负载均衡。针对强读情况,此时要求读到最新的数据,因此需要发往 leader 所在的节点。可以用get_hint_consistency_level()来获取hint中指定的级别。

级别可以在hint中指定,也可以在sys_var环境变量中指定。Hint级别更高,会覆盖sys_var中的设置。如果都没设置,则默认为强读。另外在代码中判断,如果当前observer不支持弱读,或者是for update语句,则改成强读。另外判断是否for update语句不是通过parser结果来判断,而是通过在原始sql语句中寻找for update字符串来实现的。

do_expr_parse()函数中,调用ObExprParser::parse_reqsql()函数来进行表达式的解析。首先剪切出表达式所在的部分,对于select语句,找到join或者where之后的部分。对于update语句,找到set之后的部分。对于merge语句,找到on之后的部分。然后调用parse()函数进行解析。

从ObExprParser::parse()函数进入ob_expr_parse_utf8_sql()函数,再进入lex和parser(相关的语法规则在obproxy-master/src/obproxy/opsql/expr_parser目录下ob_expr_parser.l和ob_expr_parser.y,分别是三百行和六百行),进行词法语法解析,解析出来的结果保存在结构体ObExprParseResult中。

ObExprParseResult结构体的定义如下所示:

解析出来的表达式关系保存在_ObProxyRelationInfo这个结构体中,定义了OBPROXY_MAX_RELATION_NUM是64,所以最大是能支持64个表达式。

具体的一个表达式保存在ObProxyRelationExpr结构体中,包括如下几个属性。

接下来看ObProxyExprCalculator::do_expr_resolve()函数。这里的工作是根据上一步得到的parse_result,根据元数据信息进行解析,类似于trafodion中的bind过程。

最后是ObProxyExprCalculator::do_partition_id_calc函数,利用之前的parse_result以及resolve_result,计算出partition_id。

这里也有几个比较关键的操作get_first_part(), get_sub_part_desc_by_first_part_id(), get_sub_part(), calc_part_id_by_random_choose_from_exist(), generate_phy_part_id()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值