当前位置: 首页 > news >正文

OceanBase 配置项系统变量实现及应用详解(4):新增系统变量

本专题的前几篇文章已经详细阐述了OceanBase的配置项和系统变量的基础用法,并对配置项的源码进行了剖析。但有一些同学可能还对系统变量的实现方式有兴趣,并希望能够像自定义配置项那样,新增一个系统变量。

本文将围绕“如何新增系统变量”这一话题展开,结合系统变量的源码实现,为大家详细讲解系统变量的定义、初始化过程、内部访问方式以及同步机制。帮助大家更好地理解和运用系统变量。

系统变量概述

一个系统变量可以同时具有 global 级别和 session 级别的数据,在代码中存在多个副本。这与配置项(参数)不同,一个配置项要么是集群级别,要么是租户级别。

系统变量的定义

要增加一个新的变量,首先在 src/share/system_variable/ob_system_variable_init.json 文件中按照下面的格式编写一段代码。

  "ob_query_timeout": {"id": 10005,"name": "ob_query_timeout","default_value": "10000000","base_value": "10000000","data_type": "int","info": "Query timeout in microsecond(us)","flags": "GLOBAL | SESSION | NEED_SERIALIZE","on_check_and_convert_func": "ObSysVarOnCheckFuncs::check_and_convert_timeout_too_large","publish_version": "","info_cn": "","background_cn": "","ref_url": ""},

其中每一项的含义如下:

2参数说明举例
对象名(括号外)数据结构的名字,与括号内的变量名一致ob_query_timeout
id变量的序号,按顺序手动分配0, 1, 95, 10001, 10037
name变量名,与括号外的对象名一致ob_query_timeout
default_value默认值0, 1000, disabled, binary
base_value基准值,初始与默认值相同,用于不同session间的变量同步0, 1000, disabled, binary
data_type数据类型int, varchar, enum, bool
info描述信息,描述变量的主要功能"Query timeout in microsecond(us)"
flags标签,包括级别、是否序列化、是否可见等GLOBAL | SESSION | NEED_SERIALIZE | ORACLE_ONLY | INVISIBLE
on_check_and_convert_func合法性检查和格式转换函数(可选)ObSysVarOnCheckFuncs::check_and_convert_timeout_too_large
min_val最小值(可选)1
max_val最大值(可选)10000
enum_names枚举类型的取值列表(可选)"enum_names": ["OFF", "AUTO", "FORCE"],
publish_version发布版本号"227", "420"(即2.2.7,4.2.0)
info_cn中文描述"查询超时时间(微秒)"
background_cn添加原因/背景"避免死锁、一直重试等问题导致的sql阻塞的情况"
ref_url变量申请链接"xxx"

然后执行 ./gen_ob_sys_variables.py 脚本,新增的变量会出现在 ob_system_variable_init.cpp 文件中。

[&] (){ObSysVars[100].default_value_ = "10000000" ;ObSysVars[100].info_ = "Query timeout in microsecond(us)" ;ObSysVars[100].name_ = "ob_query_timeout" ;ObSysVars[100].data_type_ = ObIntType ;ObSysVars[100].flags_ = ObSysVarFlag::GLOBAL_SCOPE | ObSysVarFlag::SESSION_SCOPE | ObSysVarFlag::NEED_SERIALIZE ;ObSysVars[100].on_check_and_convert_func_ = "ObSysVarOnCheckFuncs::check_and_convert_timeout_too_large" ;ObSysVars[100].id_ = SYS_VAR_OB_QUERY_TIMEOUT ;cur_max_var_id = MAX(cur_max_var_id, static_cast<int64_t>(SYS_VAR_OB_QUERY_TIMEOUT)) ;ObSysVarsIdToArrayIdx[SYS_VAR_OB_QUERY_TIMEOUT] = 100 ;ObSysVars[100].base_value_ = "10000000" ;ObSysVars[100].alias_ = "OB_SV_QUERY_TIMEOUT" ;}();

这些变量的初始值都会存储在以下数组中。其中 ObSysVars 数组保存了从 json 文件生成的最原始的变量信息;ObSysVarDefaultValues 数组存储了系统变量的默认值;ObSysVarBaseValues 数组存储了系统变量的基准值。每新增一个系统变量,这些数组中就会多一个元素。

namespace oceanbase
{
namespace share
{
// 变量的初始信息
static ObSysVarFromJson ObSysVars[ObSysVarFactory::ALL_SYS_VARS_COUNT];
// 变量的默认值
static ObObj ObSysVarDefaultValues[ObSysVarFactory::ALL_SYS_VARS_COUNT];
// 变量的基准值
static ObObj ObSysVarBaseValues[ObSysVarFactory::ALL_SYS_VARS_COUNT];

系统变量缓存的定义

在某些场景为满足功能或性能的需求,需要额外为系统变量增加一份缓存(按需定义,不是必须的)。系统变量的缓存通过 DEF_SYS_VAR_CACHE_FUNCS 和 DEF_SYS_VAR_CACHE_FUNCS_STR 宏定义。

class ObBasicSessionInfo
{
private:
#define DEF_SYS_VAR_CACHE_FUNCS(SYS_VAR_TYPE, SYS_VAR_NAME)                           \void set_##SYS_VAR_NAME(SYS_VAR_TYPE value)                                         \{                                                                                   \inc_data_.SYS_VAR_NAME##_ = (value);                                              \inc_##SYS_VAR_NAME##_ = true;                                                     \}                                                                                   \void set_base_##SYS_VAR_NAME(SYS_VAR_TYPE value)                                    \{                                                                                   \base_data_.SYS_VAR_NAME##_ = (value);                                             \}                                                                                   \const SYS_VAR_TYPE &get_##SYS_VAR_NAME() const                                      \{                                                                                   \return get_##SYS_VAR_NAME(inc_##SYS_VAR_NAME##_);                                 \}                                                                                   \const SYS_VAR_TYPE &get_##SYS_VAR_NAME(bool is_inc) const                           \{                                                                                   \return is_inc ? inc_data_.SYS_VAR_NAME##_ : base_data_.SYS_VAR_NAME##_;           \}class SysVarsCache{public:DEF_SYS_VAR_CACHE_FUNCS(int64_t, ob_max_read_stale_time);DEF_SYS_VAR_CACHE_FUNCS(bool, tx_read_only);DEF_SYS_VAR_CACHE_FUNCS_STR(nls_date_format);}private:static SysVarsCacheData base_data_;SysVarsCacheData inc_data_;union {uint64_t inc_flags_;struct {bool inc_auto_increment_increment_:1;bool inc_sql_throttle_current_priority_:1;......

其中 base_data_ 表示基准数据,它是全局唯一的,与“global变量”对应;inc_data_ 表示最新数据,每个session各有一份,与“session变量”对应。

以 ob_max_read_stale_time 变量为例,上面的宏展开后生成以下代码。

  void set_ob_max_read_stale_time(int64_t value){inc_data_.ob_max_read_stale_time_ = (value);inc_ob_max_read_stale_time_ = true;}void set_base_ob_max_read_stale_time(int64_t value){base_data_.ob_max_read_stale_time_ = (value);}const int64_t &get_ob_max_read_stale_time() const{return get_ob_max_read_stale_time(inc_ob_max_read_stale_time_);}const int64_t &get_ob_max_read_stale_time(bool is_inc) const{return is_inc ? inc_data_.ob_max_read_stale_time_ : base_data_.ob_max_read_stale_time_;}

其中 set_ob_max_read_stale_time() 函数会修改 inc_ob_max_read_stale_time_ 为 true 并更新 inc_data_;然后调用 get_ob_max_read_stale_time() 函数会返回修改过的数据 inc_data_.ob_max_read_stale_time_,未修改则会返回基准数据 base_data_.ob_max_read_stale_time_。

变量初始化

定义上面的一系列数据结构后,接下来在 OBServer 启动时系统变量会进行初始化。

变量基础数据

ObSysVars 数组保存了最原始的变量信息,在静态对象的构造函数中进行初始化。

namespace oceanbase
{
namespace share
{
// 变量的初始信息
static ObSysVarFromJson ObSysVars[ObSysVarFactory::ALL_SYS_VARS_COUNT];
// 变量的默认值
static ObObj ObSysVarDefaultValues[ObSysVarFactory::ALL_SYS_VARS_COUNT];
// 变量的基准值
static ObObj ObSysVarBaseValues[ObSysVarFactory::ALL_SYS_VARS_COUNT];static struct VarsInit{VarsInit(){// 保存当前系统变量的最大的idint64_t cur_max_var_id = 0;// ObSysVarsIdToArrayIdx数组默认初始值为-1,-1表示无效索引memset(ObSysVarsIdToArrayIdx, -1, sizeof(ObSysVarsIdToArrayIdx));[&] (){ObSysVars[0].default_value_ = "1" ;ObSysVars[0].info_ = "" ;ObSysVars[0].name_ = "auto_increment_increment" ;ObSysVars[0].data_type_ = ObUInt64Type ;ObSysVars[0].min_val_ = "1" ;ObSysVars[0].max_val_ = "65535" ;ObSysVars[0].flags_ = ObSysVarFlag::GLOBAL_SCOPE | ObSysVarFlag::SESSION_SCOPE | ObSysVarFlag::NEED_SERIALIZE ;ObSysVars[0].id_ = SYS_VAR_AUTO_INCREMENT_INCREMENT ;cur_max_var_id = MAX(cur_max_var_id, static_cast<int64_t>(SYS_VAR_AUTO_INCREMENT_INCREMENT)) ;ObSysVarsIdToArrayIdx[SYS_VAR_AUTO_INCREMENT_INCREMENT] = 0 ;ObSysVars[0].base_value_ = "1" ;ObSysVars[0].alias_ = "OB_SV_AUTO_INCREMENT_INCREMENT" ;}();

ObSysVarDefaultValues 数组保存了系统变量的默认值,ObSysVarBaseValues 数组保存了系统变量的基准值。在 OBServer 启动时,会初始化这两个数组:

  • ObServer::init()
  • ObPreProcessSysVars::init_sys_var()
  • ObSysVariables::init_default_values()

该函数遍历 ObSysVars 数组,将每个变量的 default_value_ 存储到 ObSysVarDefaultValues 数组中,将每个变量的 base_value_ 存储到 ObSysVarBaseValues 数组中。

int ObSysVariables::init_default_values()
{int ret = OB_SUCCESS;int64_t sys_var_count = get_amount();for (int64_t i = 0; OB_SUCC(ret) && i < sys_var_count; ++i) {const ObString &sys_var_val_str = ObSysVariables::get_value(i);const ObString &base_sys_var_val_str = ObSysVariables::get_base_str_value(i);......ObSysVarDefaultValues[i] = out_obj;ObSysVarBaseValues[i] = base_out_obj;

global 变量:sysvar_array_

ObSysVariableSchema 是管理系统变量的 schema 对象,其中的 sysvar_array_ 数组存储了 global 变量的值,因此系统变量也具有 schema 多版本的能力。

class ObSysVariableSchema : public ObSchema
{
private:uint64_t tenant_id_;int64_t schema_version_;ObSysVarSchema *sysvar_array_[ObSysVarFactory::ALL_SYS_VARS_COUNT];bool read_only_;class ObSysVarSchema : public ObSchema
{
private:uint64_t tenant_id_;common::ObString name_;common::ObObjType data_type_;common::ObString value_;common::ObString min_val_;common::ObString max_val_;common::ObString info_;common::ObZone zone_;int64_t schema_version_;int64_t flags_;

sysvar_array_ 数组在 OBServer 启动时进行初始化,首先调用的是 init_schema() 函数。

  • ObServer::init_schema()
  • ObMultiVersionSchemaService::init()
  • ObMultiVersionSchemaService::init_sys_tenant_user_schema()

先调用 load_default_system_variable() 将所有变量的默认值都加载到 sysvar_array_ 数组中,再调用 put_schema() 把这个 schema 添加到 schema_cache_ 中。

int ObMultiVersionSchemaService::init_sys_tenant_user_schema()
{ObSysVariableSchema sys_variable;if (OB_FAIL(sys_variable.load_default_system_variable(true))) {LOG_WARN("load sys tenant default system variable failed", K(ret));} else if (OB_FAIL(schema_cache_.put_schema(SYS_VARIABLE_SCHEMA,OB_SYS_TENANT_ID,sys_variable.get_tenant_id(),sys_variable.get_schema_version(),sys_variable))) {
  • ObSysVariableSchema::load_default_system_variable()

该函数遍历 ObSysVars 数组获取变量的默认值,然后调用 add_sysvar_schema() 函数将变量值添加到 sysvar_array_ 数组中。

int ObSysVariableSchema::load_default_system_variable(bool is_sys_tenant)
{int ret = OB_SUCCESS;ObString value;ObSysVarSchema sysvar;for (int64_t i = 0; OB_SUCC(ret) && i < ObSysVariables::get_amount(); ++i) {sysvar.reset();// 从 ObSysVariables 中获取默认数据if (is_sys_tenant && ObCharset::case_insensitive_equal(ObSysVariables::get_name(i), OB_SV_LOWER_CASE_TABLE_NAMES)) {value = ObString::make_string("2");} else {value = ObSysVariables::get_value(i);}} else if (OB_FAIL(sysvar.set_value(value))) {LOG_WARN("set sysvar value failed", K(ret), K(value));} else if (OB_FAIL(add_sysvar_schema(sysvar))) {LOG_WARN("add sysvar schema failed", K(ret));}}

session 变量:sys_vars_

ObBasicSessionInfo 是管理 session 相关信息的对象,其中的 sys_vars_ 数组存储了 session 变量的值,在建立 session 连接时进行初始化。

class ObBasicSessionInfo
{
private:share::ObBasicSysVar *sys_vars_[share::ObSysVarFactory::ALL_SYS_VARS_COUNT];
外部 session

外部 session 连接是指从客户端发起的连接,一般用于处理用户的 sql 请求。外部 session 的变量缓存值在初始化时,会从 schema 中取值,同时也依赖 schema 的同步机制。

  • int ObMPConnect::process()

在建立连接时,先调用 create_session() 函数创建 session,然后调用 verify_identify() 初始化变量。

  • ObMPConnect::verify_identify()
  • ObMPConnect::load_privilege_info()

通过租户ID从 schema_cache_ 中获取对应的 sys_variable_schema,然后将 sys_variable_schema 作为参数调用 load_all_sys_vars() 函数。

int ObMPConnect::load_privilege_info(ObSQLSessionInfo &session)
{const ObSysVariableSchema *sys_variable_schema = NULL;} else if (OB_FAIL(schema_guard.get_sys_variable_schema(conn->tenant_id_, sys_variable_schema))) {LOG_WARN("get sys variable schema failed", K(ret));} else if (OB_ISNULL(sys_variable_schema)) {ret = OB_ERR_UNEXPECTED;LOG_WARN("sys variable schema is null", K(ret));} else if (OB_FAIL(session.init_tenant(tenant_name_, conn->tenant_id_))) {LOG_WARN("failed to init_tenant", K(ret));} else if (OB_FAIL(session.load_all_sys_vars(*sys_variable_schema, false))) {LOG_WARN("load system variables failed", K(ret));
  • ObBasicSessionInfo::load_all_sys_vars()

该函数每次循环,先从 ObSysVars 数组中获取 sys_var_id,然后从传入的 sys_var_schema 中获取 sys_var 对象,然后把变量的 name 和 value 作为参数,调用 load_sys_variable() 函数。

int ObBasicSessionInfo::load_all_sys_vars(const ObSysVariableSchema &sys_var_schema, bool sys_var_created)
{int ret = OB_SUCCESS;OZ (clean_all_sys_vars());if (!sys_var_created) {OZ (sys_var_fac_.create_all_sys_vars());}OX (influence_plan_var_indexs_.reset());ObArenaAllocator calc_buf(ObModIds::OB_SQL_SESSION);for (int64_t i = 0; OB_SUCC(ret) && i < get_sys_var_count(); i++) {ObSysVarClassType sys_var_id = ObSysVariables::get_sys_var_id(i);const ObSysVarSchema *sys_var = NULL;OZ (sys_var_schema.get_sysvar_schema(sys_var_id, sys_var), sys_var_id, i);OV (OB_NOT_NULL(sys_var));OZ (load_sys_variable(calc_buf, sys_var->get_name(), sys_var->get_data_type(),sys_var->get_value(), sys_var->get_min_val(),sys_var->get_max_val(), sys_var->get_flags(), true));
  • ObBasicSessionInfo::load_sys_variable() 

先调用 find_sys_var_id_by_name() 函数通过 name 获取 var_id,然后调用 create_sys_var() 函数获取(创建)变量指针 sys_var,再调用 sys_var->init() 给变量赋值,最后调用 process_session_variable() 函数给变量缓存赋值。

int ObBasicSessionInfo::load_sys_variable(ObIAllocator &calc_buf,const ObString &name,const ObObj &type,const ObObj &value,const ObObj &min_val,const ObObj &max_val,const int64_t flags,bool is_from_sys_table)
{if (SYS_VAR_INVALID == (var_id = ObSysVarFactory::find_sys_var_id_by_name(name, is_from_sys_table))) {......} else if (OB_FAIL(create_sys_var(var_id, store_idx, sys_var))) {LOG_WARN("fail to create sys var", K(name), K(value), K(ret));......} else if (OB_FAIL(sys_var->init(real_val, min_ptr, max_ptr, val_type.get_type(), flags))) {LOG_WARN("fail to init sys var", K(ret), K(sys_var->get_type()),K(real_val), K(name), K(value));} else if (OB_FAIL(process_session_variable(var_id, real_val,false /*check_timezone_valid*/,false /*is_update_sys_var*/))) {
  • ObBasicSessionInfo::create_sys_var()

初次调用 create_sys_var() 函数时,sys_vars_[store_idx] 为空,然后调用 sys_var_fac_.create_sys_var() 函数生成 sys_var,再把 sys_var 赋给 sys_vars_[store_idx]。session 刚初始化时,sys_var 指向的是变量的默认值。

再次调用 create_sys_var() 函数时,sys_vars_[store_idx] 不为空,将其值赋给 sys_var,相当于获取了当前session 的变量。

int ObBasicSessionInfo::create_sys_var(ObSysVarClassType sys_var_id,int64_t store_idx, ObBasicSysVar *&sys_var)
{int ret = OB_SUCCESS;OV (0 <= store_idx && store_idx < ObSysVarFactory::ALL_SYS_VARS_COUNT,OB_ERR_UNEXPECTED, sys_var_id, store_idx);if (OB_NOT_NULL(sys_vars_[store_idx])) {OV (sys_vars_[store_idx]->get_type() == sys_var_id,OB_ERR_UNEXPECTED, sys_var_id, store_idx, sys_vars_[store_idx]->get_type());OX (sys_var = sys_vars_[store_idx]);} else {OZ (sys_var_fac_.create_sys_var(sys_var_id, sys_var), sys_var_id);OV (OB_NOT_NULL(sys_var), OB_ERR_UNEXPECTED, sys_var_id, store_idx);OX (sys_vars_[store_idx] = sys_var);}return ret;
}

内部 session

内部 session 连接是指 OBServer 内部或 OBProxy 向 OBServer 发起的内部请求建立的连接,不是用户主动建立的连接。内部 session 的变量总是获取全局默认值作为初值,而不是从最新的 schema 中取值。

  • ObCommonSqlProxy::acquire()
  • ObInnerSQLConnectionPool::acquire()
  • ObInnerSQLConnection::init()
  • ObInnerSQLConnection::init_session()
  • ObInnerSQLConnection::init_session_info()
  • ObBasicSessionInfo::load_default_sys_variable()
int ObBasicSessionInfo::load_default_sys_variable(const bool print_info_log, const bool is_sys_tenant, bool is_deserialized)
{int ret = OB_SUCCESS;if (OB_FAIL(sys_var_fac_.create_all_sys_vars())) {LOG_WARN("fail create all sys variables", K(ret));} else if (OB_FAIL(init_system_variables(print_info_log, is_sys_tenant, is_deserialized))) {LOG_WARN("Init system variables failed !", K(ret));}return ret;
}
  • ObBasicSessionInfo::init_system_variables()

调用 ObSysVariables::get_value(i) 获取 value,也就是从全局的 ObSysVars 数组中获取默认值,然后调用 load_sys_variable() 函数将 value 的值加载到 session 中。

int ObBasicSessionInfo::init_system_variables(const bool print_info_log, const bool is_sys_tenant,bool is_deserialized)
{for (int64_t i = 0; OB_SUCC(ret) && i < var_amount; ++i) {name.assign_ptr(const_cast<char*>(ObSysVariables::get_name(i).ptr()),static_cast<ObString::obstr_size_t>(strlen(ObSysVariables::get_name(i).ptr())));bool is_exist = false;if (OB_FAIL(sys_variable_exists(name, is_exist))) {LOG_WARN("failed to check if sys variable exists", K(name), K(ret));} else if (!is_exist) {// Note: 如果已经初始化过 base value,则下面的流程不会执行var_type = ObSysVariables::get_type(i);var_flag = ObSysVariables::get_flags(i);value.set_varchar(is_deserialized ? ObSysVariables::get_base_str_value(i) :ObSysVariables::get_value(i));value.set_collation_type(ObCharset::get_system_collation());min_val.set_varchar(ObSysVariables::get_min(i));min_val.set_collation_type(ObCharset::get_system_collation());max_val.set_varchar(ObSysVariables::get_max(i));max_val.set_collation_type(ObCharset::get_system_collation());type.set_type(var_type);if(is_sys_tenant) {if (OB_FAIL(process_variable_for_tenant(name, value))) {LOG_WARN("process system variable for tenant error",  K(name), K(value), K(ret));}}if (OB_SUCC(ret)) {if (OB_FAIL(load_sys_variable(calc_buf, name, type, value, min_val, max_val, var_flag, false))) {
  • ObBasicSessionInfo::load_sys_variable()

初次调用 create_sys_var() 函数时,创建 session 变量 sys_var,然后用传入的默认值 value 对其进行初始化。

int ObBasicSessionInfo::load_sys_variable(ObIAllocator &calc_buf,const ObString &name,const ObObj &type,const ObObj &value,const ObObj &min_val,const ObObj &max_val,const int64_t flags,bool is_from_sys_table)
{} else if (OB_FAIL(create_sys_var(var_id, store_idx, sys_var))) {LOG_WARN("fail to create sys var", K(name), K(value), K(ret));} else if (OB_FAIL(ObBasicSessionInfo::change_value_for_special_sys_var(var_id, val_ptr, real_val))) {LOG_WARN("fail to change value for special sys var", K(ret), K(var_id), K(val_ptr));} else if (OB_FAIL(sys_var->init(real_val, min_ptr, max_ptr, val_type.get_type(), flags))) {LOG_WARN("fail to init sys var", K(ret), K(sys_var->get_type()),K(real_val), K(name), K(value));} else if (OB_FAIL(process_session_variable(var_id, real_val,false /*check_timezone_valid*/,false /*is_update_sys_var*/))) {LOG_WARN("process system variable error",  K(name), K(type), K(real_val), K(value), K(ret));

变量缓存基准值:base_data_

base_data_ 表示系统变量缓存的基准数据,它是 OBServer 全局唯一的。

  • ObServer::init()
  • ObBasicSessionInfo::init_sys_vars_cache_base_values()

遍历 ObSysVarDefaultValues 数组,将变量的默认值写到 ObBasicSessionInfo::base_data_ 中,这是一个 static SysVarsCacheData[] 类型的数组。

int ObBasicSessionInfo::init_sys_vars_cache_base_values()
{int ret = OB_SUCCESS;int64_t store_idx = OB_INVALID_INDEX_INT64;ObBasicSessionInfo::SysVarsCache sys_vars_cache;int64_t var_amount = ObSysVariables::get_amount();for (int64_t i = 0; OB_SUCC(ret) && i < var_amount; ++i) {store_idx = ObSysVarsToIdxMap::get_store_idx((int64_t)ObSysVariables::get_sys_var_id(i));OX (fill_sys_vars_cache_base_value(ObSysVariables::get_sys_var_id(i),sys_vars_cache,ObSysVariables::get_default_value(store_idx) ));}return ret;
}

变量缓存增量值:inc_data_

inc_data_ 表示 session 变量的缓存值,每个 session 各有一份缓存数据。

外部session 的 inc_data_ 变量缓存值初始化为 schema 中的最新值,内部 session 的 inc_data_ 初始化为变量的默认值,也就是说变量缓存与 session 变量的初值是一致的,它们初始化的调用路径也基本相同。

变量查询

外部查询

查询 global 变量

global 变量的值是从 schema 中获取的,在执行如“show global variables like xxx”的命令时就会触发以下流程:

  • ObBasicSessionInfo::get_global_sys_variable()

先调用 get_sys_variable_schema() 函数获取 sys_variable_schema 对象,然后从该对象中获取 sysvar_schema。

int ObBasicSessionInfo::get_global_sys_variable(const uint64_t actual_tenant_id, // 为了处理租户已经切掉的情况ObIAllocator &calc_buf,const ObDataTypeCastParams &dtc_params,const ObString &var_name,ObObj &val)
{} else if (OB_FAIL(schema_guard.get_sys_variable_schema(actual_tenant_id, sys_variable_schema))) {LOG_WARN("get sys variable schema failed", K(ret));} else if (OB_ISNULL(sys_variable_schema)) {ret = OB_ERR_UNEXPECTED;LOG_WARN("sys variable schema is null", K(ret));} else if (OB_FAIL(sys_variable_schema->get_sysvar_schema(var_name, sysvar_schema))) {
  • ObSysVariableSchema::get_sysvar_schema(const ObString &name, const ObSysVarSchema *&sysvar_schema)
  • ObSysVariableSchema::get_sysvar_schema(ObSysVarClassType var_id, const ObSysVarSchema *&sysvar_schema)
  • ObSysVariableSchema::get_sysvar_schema(int64_t idx)

该函数根据传入的 idx 获取对应的变量,最终返回 sysvar_array_[idx]。

const ObSysVarSchema *ObSysVariableSchema::get_sysvar_schema(int64_t idx) const
{const ObSysVarSchema *ret = NULL;if (idx >= 0 && idx < get_sysvar_count()) {ret = sysvar_array_[idx];}return ret;
}

查询 session 变量

session 变量的值是从 session 本地数组 sys_vars_ 中获取的,执行如“show variables like xxx”的命令时会触发以下调用流程:

  • ObResultSet::inner_get_next_row()
  • ......
  • ObBasicSessionInfo::get_sys_variable_by_name()
  • ObBasicSessionInfo::inner_get_sys_var(oceanbase::common::ObString const&, oceanbase::share::ObBasicSysVar*&)
  • ObBasicSessionInfo::inner_get_sys_var(oceanbase::common::ObString const&, long&, oceanbase::share::ObBasicSysVar*&)

该函数先调用 find_sys_var_id_by_name() 函数通过 sys_var_name 获取 sys_var_id,然后调用 calc_sys_var_store_idx() 函数利用 sys_var_id 计算出 store_idx,也就是变量在数组中的下标,最后返回 sys_vars_[store_idx] 即可。

int ObBasicSessionInfo::inner_get_sys_var(const ObString &sys_var_name,int64_t &store_idx,ObBasicSysVar *&sys_var) const
{int ret = OB_SUCCESS;ObSysVarClassType sys_var_id = SYS_VAR_INVALID;if (OB_UNLIKELY(SYS_VAR_INVALID == (sys_var_id = ObSysVarFactory::find_sys_var_id_by_name(sys_var_name)))) {ret = OB_ERR_SYS_VARIABLE_UNKNOWN;LOG_WARN("fail to find sys var id by name", K(ret), K(sys_var_name), K(lbt()));} else if (OB_FAIL(ObSysVarFactory::calc_sys_var_store_idx(sys_var_id, store_idx))) {LOG_WARN("fail to calc sys var store idx", K(ret), K(sys_var_id), K(sys_var_name), K(lbt()));} else if (OB_UNLIKELY(store_idx < 0) ||OB_UNLIKELY(store_idx >= ObSysVarFactory::ALL_SYS_VARS_COUNT)) {ret = OB_ERR_UNEXPECTED;LOG_ERROR("got store_idx is invalid", K(ret), K(store_idx));} else if (OB_ISNULL(sys_vars_[store_idx])) {ret = OB_ENTRY_NOT_EXIST;LOG_WARN("sys var is NULL", K(ret), K(store_idx), K(sys_var_name));} else {sys_var = sys_vars_[store_idx];}return ret;
}

内部获取

获取 global 变量

内部访问 global 变量同样是通过 schema 获取,先从全局的 GCTX.schema_service_中获取 schema_guard,然后通过变量名获取 var_schema,再逐步解析得到最终的基础数据类型。

例如获取 weak_read_version_refresh_interval:

int ObRootService::check_weak_read_version_refresh_interval(int64_t refresh_interval, bool &valid)
{......} else if (OB_FAIL(GCTX.schema_service_->get_tenant_schema_guard(tenant_id, schema_guard))) {LOG_WARN("get schema guard failed", KR(ret), K(tenant_id));} else if (OB_FAIL(schema_guard.get_tenant_system_variable(tenant_id,OB_SV_MAX_READ_STALE_TIME, var_schema))) {LOG_WARN("get tenant system variable failed", KR(ret), K(tenant_id));} else if (OB_ISNULL(var_schema)) {ret = OB_ERR_UNEXPECTED;LOG_WARN("var schema is null", KR(ret), K(tenant_id));} else if (OB_FAIL(var_schema->get_value(NULL, NULL, obj))) {LOG_WARN("get value failed", KR(ret), K(tenant_id), K(obj));} else if (OB_FAIL(obj.get_int(session_max_stale_time))) {LOG_WARN("get int failed", KR(ret), K(tenant_id), K(obj));} else if (session_max_stale_time != share::ObSysVarFactory::INVALID_MAX_READ_STALE_TIME&& refresh_interval > session_max_stale_time) {

通过这种方式获取变量,通常会定义一个变量别名,方便代码中进行访问。

namespace oceanbase
{
namespace share
{static const char* const OB_SV_ENABLE_RICH_ERROR_MSG = "ob_enable_rich_error_msg";static const char* const OB_SV_LOG_ROW_VALUE_OPTIONS = "log_row_value_options";static const char* const OB_SV_MAX_READ_STALE_TIME = "ob_max_read_stale_time";......

获取 session 变量最新值

内部查询调用 get_sys_var() 函数通过下标 idx 获取 session 变量的值。

ObBasicSysVar *ObBasicSessionInfo::get_sys_var(const int64_t idx)
{ObBasicSysVar *var = NULL;if (idx >= 0 && idx < ObSysVarFactory::ALL_SYS_VARS_COUNT) {var = sys_vars_[idx];}return var;
}

获取 session 变量缓存值

获取变量缓存值,就是从 ObBasicSessionInfo::sys_vars_cache_数组中取值,通常是封装了一些函数,然后再调用 sys_vars_cache_ 的 get_xxx() 接口,这些接口就是用 DEF_SYS_VAR_CACHE_FUNCS 宏定义的。

举几个例子:

class ObBasicSessionInfo
{int64_t get_trx_lock_timeout() const{return sys_vars_cache_.get_ob_trx_lock_timeout();}int64_t get_ob_max_read_stale_time() {return sys_vars_cache_.get_ob_max_read_stale_time();}int get_sql_throttle_current_priority(int64_t &sql_throttle_current_priority){sql_throttle_current_priority = sys_vars_cache_.get_sql_throttle_current_priority();return common::OB_SUCCESS;}

只要能够访问 session,就可以访问 session 中的变量,比如获取 ob_max_read_stale_time:

session->get_ob_max_read_stale_time();

变量更新

外部修改

global 变量修改流程

更新 global 变量就是更新其对应的 schema,执行如“set global xxx = xx”的命令时触发以下调用流程:

  • ObMPQuery::do_process()
  • ObVariableSetExecutor::execute(ObExecContext &ctx, ObVariableSetStmt &stmt)
  • ObVariableSetExecutor::update_global_variables()

该函数先对更新数据做合法性检查,然后构造一个新的 sysvar_schema 加入到 rpc 参数中,最后发送更新系统变量的请求到其他节点。

int ObVariableSetExecutor::update_global_variables(ObExecContext &ctx,ObDDLStmt &stmt,const ObSetVar &set_var,const ObObj &val)
{// 合法性检查} else if (set_var.var_name_ == OB_SV_MAX_READ_STALE_TIME) {int64_t max_read_stale_time = 0;if (OB_FAIL(val.get_int(max_read_stale_time))) {LOG_WARN("fail to get int value", K(ret), K(val));} else if (max_read_stale_time != ObSysVarFactory::INVALID_MAX_READ_STALE_TIME &&max_read_stale_time < GCONF.weak_read_version_refresh_interval) {ret = OB_INVALID_ARGUMENT;LOG_USER_ERROR(OB_INVALID_ARGUMENT,"max_read_stale_time is smaller than weak_read_version_refresh_interval");}// 构造新的 sysvar_schema,添加到参数 arg 的 sys_var_list_ 列表中if (OB_SUCC(ret)) {......} else if (OB_FAIL(sysvar_schema.set_name(set_var.var_name_))) {LOG_WARN("set sysvar schema name failed", K(ret));} else if (OB_FAIL(sysvar_schema.set_value(val_str))) {LOG_WARN("set sysvar schema value failed", K(ret));} else {sysvar_schema.set_tenant_id(arg.tenant_id_);if (OB_FAIL(arg.sys_var_list_.push_back(sysvar_schema))) {LOG_WARN("store sys var to array failed", K(ret));}}}// 想其他节点发送更新变量的rpc请求if (OB_SUCC(ret)) {if (OB_ISNULL(task_exec_ctx = GET_TASK_EXECUTOR_CTX(ctx)) ||OB_ISNULL(common_rpc_proxy = task_exec_ctx->get_common_rpc())) {ret = OB_NOT_INIT;LOG_WARN("task exec ctx or common rpc proxy is NULL", K(ret), K(task_exec_ctx), K(common_rpc_proxy));} else if (OB_FAIL(common_rpc_proxy->modify_system_variable(arg))) {LOG_WARN("rpc proxy alter system variable failed", K(ret));} else {}}return ret;
}

session 变量修改流程

更新session变量值的同时,还会更新对应缓存数据的值,执行如“set xxx = xx”的命令时会触发以下调用流程:

  • ObBasicSysVar::session_update()
  • ObBasicSessionInfo::update_sys_variable_by_name()

通过变量名 var 获取 var_id,然后调用 update_sys_variable() 传入 val。

int ObBasicSessionInfo::update_sys_variable_by_name(const ObString &var, const ObObj &val)
{int ret = OB_SUCCESS;ObSysVarClassType var_id = SYS_VAR_INVALID;if (var.empty()) {ret = OB_INVALID_ARGUMENT;LOG_WARN("invalid variable name", K(var), K(val), K(ret));} else if (SYS_VAR_INVALID == (var_id = ObSysVarFactory::find_sys_var_id_by_name(var))) {ret = OB_ERR_SYS_VARIABLE_UNKNOWN;LOG_WARN("unknown variable", K(var), K(val), K(ret));} else if (OB_FAIL(update_sys_variable(var_id, val))) {LOG_WARN("failed to update sys variable", K(var), K(val), K(ret));} else {}return ret;
}
  • ObBasicSessionInfo::update_sys_variable()

先记录变量旧值,然后更新 session 变量缓存值,再更新 sys_var 的值,也就是 sys_vars_[store_idx] 的值。

int ObBasicSessionInfo::update_sys_variable(const ObSysVarClassType sys_var_id, const ObObj &val)
{} else if (is_track_session_info()) {// 记录修改变量的旧值if (OB_FAIL(track_sys_var(sys_var_id, sys_var->get_value()))) {LOG_WARN("failed to track sys var", K(ret), K(sys_var_id), K(val));if (OB_SUCC(ret)) {if (OB_FAIL(process_session_variable(sys_var_id, val, false /*check_timezone_valid*/,true /*is_update_sys_var*/))) {LOG_WARN("process system variable error",  K(sys_var_id), K(val), K(ret));} else {sys_var->set_value(val);
  • ObBasicSessionInfo::process_session_variable()

基于传入的变量类型 var,更新对应 session 变量的缓存值,即 inc_data_.xxx 。

OB_INLINE int ObBasicSessionInfo::process_session_variable(ObSysVarClassType var, const ObObj &val,const bool check_timezone_valid/*true*/, const bool is_update_sys_var/*false*/)
{int ret = OB_SUCCESS;switch (var) {case SYS_VAR_OB_MAX_READ_STALE_TIME: {int64_t max_read_stale_time = 0;if (OB_FAIL(val.get_int(max_read_stale_time))) {LOG_WARN("fail to get int value", K(ret), K(val));} else if (max_read_stale_time != ObSysVarFactory::INVALID_MAX_READ_STALE_TIME &&max_read_stale_time < GCONF.weak_read_version_refresh_interval) {ret = OB_INVALID_ARGUMENT;LOG_USER_ERROR(OB_INVALID_ARGUMENT,"max_read_stale_time is smaller than weak_read_version_refresh_interval");} else {sys_vars_cache_.set_ob_max_read_stale_time(max_read_stale_time);}break;}

内部更新

内部更新主要是指系统变量在各节点之间的同步机制。当一个节点执行 global 变量更新操作之后,其他节点刷新 schema 的流程:

  • ObServerSchemaUpdater::process_refresh_task(const ObServerSchemaTask &task)
  • ObMultiVersionSchemaService::refresh_and_add_schema()
  • ObMultiVersionSchemaService::refresh_tenant_schema(const uint64_t tenant_id)
  • ObServerSchemaService::refresh_schema(const ObRefreshSchemaStatus &schema_status)
int ObServerSchemaService::refresh_schema(const ObRefreshSchemaStatus &schema_status)
{} else if (is_full_schema) {if (OB_FAIL(refresh_full_schema(schema_status))) {LOG_WARN("tenant refresh full schema failed", K(ret), K(schema_status));}} else {if (OB_FAIL(refresh_increment_schema(schema_status))) {LOG_WARN("tenant refresh increment schema failed", K(ret), K(schema_status));}
  • ObServerSchemaService::refresh_increment_schema()

调用 get_increment_schema_operations() 获取增量 schema 操作 schema_operations,然后调用 replay_log() 回放日志,再调用 update_schema_mgr() 更新 schema。

int ObServerSchemaService::refresh_increment_schema(const ObRefreshSchemaStatus &schema_status)
{if (OB_SUCC(ret) && !core_schema_change && !sys_schema_change) {const int64_t fetch_version = std::max(core_schema_version, schema_version);if (OB_FAIL(schema_mgr_for_cache_map_.get_refactored(tenant_id, schema_mgr_for_cache))) {LOG_WARN("fail to get schema_mgr_for_cache", KR(ret), K(schema_status));} else if (OB_ISNULL(schema_mgr_for_cache)) {ret = OB_ERR_UNEXPECTED;LOG_WARN("schema mgr for cache is null", KR(ret));} else if (OB_FAIL(schema_service_->get_increment_schema_operations(schema_status,local_schema_version, fetch_version, sql_client, schema_operations))) {LOG_WARN("get_increment_schema_operations failed", KR(ret), K(schema_status),K(local_schema_version), K(fetch_version));} else if (schema_operations.count() > 0) {// new cacheSMART_VAR(AllSchemaKeys, all_keys) {if (OB_FAIL(replay_log(*schema_mgr_for_cache, schema_operations, all_keys))) {LOG_WARN("replay_log failed", KR(ret), K(schema_status), K(schema_operations));} else if (OB_FAIL(update_schema_mgr(sql_client, schema_status,*schema_mgr_for_cache, fetch_version, all_keys))){LOG_WARN("update schema mgr failed", KR(ret), K(schema_status));}}} else {}if (OB_SUCC(ret)) {schema_mgr_for_cache->set_schema_version(fetch_version);if (OB_FAIL(publish_schema(tenant_id))) {LOG_WARN("publish_schema failed", KR(ret), K(schema_status));} else {LOG_INFO("change schema version", K(schema_status), K(schema_version), K(core_schema_version));break;}}

小结

相比前面介绍过的配置项,系统变量的内部机制更加复杂,一个系统变量不仅同时具有 global 级别和 session 级别的副本,还可能在 session 上定义了一份缓存值。在新增系统变量时,要根据实际需求去定义变量的标签和缓存,在使用过程中也要注意取到的值是不是符合预期。

目前对配置项和系统变量的源码解析基本完成,前文中也偶尔提到过“合法性检查”这一概念,在下一篇文章中将会介绍它们是如何进行合法性检查的,以及相关的函数和规则是怎样定义的,欢迎大家持续关注。

参考文档

  1. 系统变量总览
  2. 什么是系统变量?如何使用系统变量?
  3. OceanBase源码

相关文章:

OceanBase 配置项系统变量实现及应用详解(4):新增系统变量

本专题的前几篇文章已经详细阐述了OceanBase的配置项和系统变量的基础用法&#xff0c;并对配置项的源码进行了剖析。但有一些同学可能还对系统变量的实现方式有兴趣&#xff0c;并希望能够像自定义配置项那样&#xff0c;新增一个系统变量。 本文将围绕“如何新增系统变量”这…...

`CAUTION: request is not finished yet!`

前言&#xff1a; 在一次与后台的接口联调中&#xff0c;数据量很大&#xff0c;导致页面卡顿&#xff0c;经排查&#xff0c;浏览器的某个接口显示CAUTION: request is not finished yet! 之前没遇到过这个错误&#xff0c;获取数据的接口开始进行请求&#xff0c;状态码返回…...

科研绘图系列:R语言GWAS曼哈顿图(Manhattan plot)

介绍 曼哈顿图(Manhattan Plot)是一种常用于展示全基因组关联研究(Genome-Wide Association Study, GWAS)结果的图形。GWAS是一种研究方法,用于识别整个基因组中与特定疾病或性状相关的遗传变异。 特点: 染色体表示:曼哈顿图通常将每个染色体表示为一个水平条,染色体…...

DjangoRF-11-创建testcases子应用--任务模块

这里先写任务应用&#xff0c;再写套件&#xff0c;然后写接口&#xff0c;最后再写请求&#xff0c; 这个是新的应用&#xff0c;要创建子应用&#xff0c;然后添加到settings的注册里面 1、和往常一样先写模型&#xff0c;序列化&#xff0c;权限&#xff0c;视图&#xf…...

服务器数据恢复—SAN环境下LUN被重复映射导致写操作不互斥的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台存储中有一组由6块硬盘组成的RAID6&#xff0c;划分为若干LUN&#xff0c;MAP到不同业务的SOLARIS操作系统服务器上。 服务器存储故障&#xff1a; 由于业务变化需要增加一台服务器&#xff0c;在存储在线的状态下将该存储中的某个LUN映…...

Linux系统安全加固:从防火墙到SELinux策略

1. iptables防火墙配置 •基础规则设定&#xff1a;学习如何设置iptable的基本规则&#xff0c;包括允许/拒绝特定端口的进出流量&#xff0c;限制特定IP地址的访问等。 •状态检查&#xff1a;利用iptables的状态检查功能&#xff0c;只允许已建立连接或相关联的流量通过&am…...

排序算法:归并排序,golang实现

目录 前言 归并排序 代码示例 1. 算法包 2. 归并排序代码 3. 模拟程序 4. 运行程序 5. 从大到小排序 归并排序主要操作 1. 合并 2. 分割&#xff08;Divide&#xff09;与递归排序&#xff08;Conquer&#xff09; 总体思想 循环次数测试 假如 10 条数据进行排序…...

CSS 的工作原理

我们已经学习了CSS的基础知识,它的用途以及如何编写简单的样式表。在本课中,我们将了解浏览器如何获取 CSS 和 HTML 并将其转换为网页。 先决条件:已安装基本软件,了解处理文件的基本知识以及 HTML 基础知识(学习 HTML 简介。目的:要了解浏览器如何解析 CSS 和 HTML 的基…...

买完就后悔?只需几步教你 Apple 怎么申请退款

苹果系统不同于 Android 系统的一点在于下载某一些 App 的时候需要付费才能下载&#xff0c;但是有时候在我们付费之后突然就不想要购买了怎么办呢&#xff1f;别急这可以申请退款&#xff0c;你知道 Apple 怎么申请退款吗&#xff1f;下面就带大家了解一下 Apple 申请退款的步…...

【保卫战】休闲小游戏 链游

...

如何构建自己的交易机器人开发环境

作者&#xff1a;老余捞鱼 原创不易&#xff0c;转载请标明出处及原作者。 写在前面的话&#xff1a; 本文主要讲解如何构建一个交易机器人开发环境。描述具体的步骤和工具&#xff0c;包括使用 GitHub Codespaces、Visual Studio Code&#xff08;VS Code&#xff09;…...

解决WordPress文章引用的图片不显示问题

在使用WordPress发布文章时&#xff0c;有时会遇到复制发布的文档中包含的外链图片无法正常显示的问题。然而&#xff0c;当我们将图片路径复制到浏览器中单独打开时&#xff0c;图片却可以正常显示。以下是解决这一问题的方法。 问题描述 当你在WordPress文章中引用外链图片…...

商业银行国际结算规模创新高,合合信息AI助力金融行业智能处理多版式文档

随着我国外贸新业态的快速增长&#xff0c;银行国际结算业务在服务实体经济发展、促进贸易投资便利化进程中发挥了越来越重要的作用。根据中国银行业协会近日发布的《中国贸易金融行业发展报告&#xff08;2023—2024&#xff09;》&#xff0c;2023年我国主要商业银行国际结算…...

数字芯片设计验证经验分享:将ASIC IP核移植到FPGA上——更新概念并推动改变以完成充满挑战的任务!

作者&#xff1a;Philipp Jacobsohn&#xff0c;SmartDV首席应用工程师 Sunil Kumar&#xff0c;SmartDV FPGA设计总监 本系列文章从数字芯片设计项目技术总监的角度出发&#xff0c;介绍了如何将芯片的产品定义与设计和验证规划进行结合&#xff0c;详细讲述了在FPGA上使用I…...

【Linux】Linux下的日志(日常级)

日志是日后工作中非常重要的一部分&#xff0c;现在写一份简单的日志项目可以帮助我们熟悉并理解原理。 目录 设计思路&#xff1a;一些实现细节&#xff1a;代码&#xff1a;日志的使用方法&#xff1a; 设计思路&#xff1a; 图示是我们的最终目的。 设计一个类&#xff0…...

手把手教你如何在Linux上轻松安装Python,告别编程入门难题

导语&#xff1a; Python作为当下最热门的编程语言之一&#xff0c;受到了越来越多人的喜爱。对于Linux用户来说&#xff0c;掌握如何在Linux上安装Python至关重要。今天&#xff0c;就让我带领大家一步步在Linux上安装Python&#xff0c;让你轻松迈入编程世界&#xff01; 一…...

XSS-labs靶场(超详解)1-20关——附原码

level1 原码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;charsetutf-8"> <script> window.alert function() { confirm("完成的不错&#xff0…...

【网络安全】LockBit病毒入侵揭秘:如何防范与应对

文章目录 前言 主要特征攻击手段演进历程主要威胁防范与对策 如何入门学习网络安全【黑客】 【----帮助网安学习&#xff0c;以下所有学习资料文末免费领取&#xff01;----】 大纲学习教程面试刷题 资料领取 前言 在数字时代&#xff0c;随着科技的飞速发展&#xff0c;网络…...

《开源大模型食用指南》适合中国宝宝的部署教程,基于Linux环境快速部署开源大模型

本项目是一个围绕开源大模型、针对国内初学者、基于 AutoDL 平台的中国宝宝专属大模型教程&#xff0c;针对各类开源大模型提供包括环境配置、本地部署、高效微调等技能在内的全流程指导&#xff0c;简化开源大模型的部署、使用和应用流程&#xff0c;让更多的普通学生、研究者…...

体验教程:通义灵码陪你备战求职季

本场景将带大家体验在技术面试准备场景下&#xff0c;如何通过使用阿里云通义灵码实现高效的编程算法题练习 、代码优化、技术知识查询等工作&#xff0c;帮助开发者提升实战能力&#xff0c;更加从容地应对面试挑战。主要包括&#xff1a; 1、模拟题练习&#xff1a;精心挑选…...

(070)爬楼梯

思路&#xff1a;一次爬一个或者一次爬两个楼梯,终止条件&#xff0c;即是当n1或n2时&#xff0c;完成操作&#xff0c;当n>2时&#xff0c;总方法就等于一次爬一个楼梯的方法数加上一次爬两个楼梯的方法数。 解法一&#xff1a;递归解法 if(n 1)return 1;if(n 2)return 2…...

el-table 表格序号列前端实现递增,切换分页不从头开始

<el-table-column type"index" width"55" label"序号" :index"hIndex"> </el-table-column> 分页 <el-pagination size-change"handleSizeChange" current-change"handleCurrentChange"> <…...

NSSCTF-Web题目27(Nginx漏洞、php伪协议、php解析绕过)

目录 [HNCTF 2022 WEEK2]easy_include 1、题目 2、知识点 3、思路 [NSSRound#8 Basic]MyDoor 4、题目 5、知识点 6、思路 [HNCTF 2022 WEEK2]easy_include 1、题目 2、知识点 nginx日志漏洞执行系统命令 3、思路 打开题目&#xff0c;出现源码 题目要我们上传一个fi…...

分割损失:Dice vs. IoU

NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 对于医学影像分割&#xf…...

SpringBoot整合Juint,ssm框架

目录 SpringBoot整合Juint 1.导入相关的依赖 2.创建测试类&#xff0c;使用注解SpringBootTest SpringBoot整合ssm框架 1.使用脚手架创建Spring项目 2.修改pom.xml 我先修改了SpringBoot的版本&#xff0c;修改为2.3.10.RELEASE&#xff0c;因为SpringBoot版本太高会出现…...

基于supervisor制作基于环境变量配置的redis

背景&#xff1a; redis 的镜像很多很多&#xff0c;但都需要直接修改配置文件&#xff0c;不符合我们公司当前环境变量解决一切容易配置的思路。 材料&#xff1a; 1、CentOS-Base.repo [base] nameCentOS-$releasever enabled1 failovermethodpriority baseurlhttp://mirr…...

动态规划part01 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯

509. 斐波那契数 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 …...

CSS实现图片边框酷炫效果

一、前言 我们在浏览一些网页时&#xff0c;经常会看到一些好看酷炫的元素边框效果&#xff08;如下图&#xff09;&#xff0c;那么这些效果是怎么实现的呢&#xff1f;我们知道&#xff0c;一般的边框&#xff0c;要么是实线&#xff0c;要么是虚线&#xff08;点状&#xf…...

遇到 MySQL 死锁问题如何解决?

终于来到死锁检查线程的第三步&#xff0c;可以解决死锁了。 作者&#xff1a;操盛春&#xff0c;爱可生技术专家&#xff0c;公众号『一树一溪』作者&#xff0c;专注于研究 MySQL 和 OceanBase 源码。 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0…...

Pyinstaller打包OSError: could not get source code【终极解决】

pyinstaller 打包的时候&#xff0c;发现只要是torch.jit.script装饰的函数&#xff0c;会报以下错误&#xff1a; Traceback (most recent call last):File "torch/_sources.py", line 25, in get_source_lines_and_fileFile "inspect.py", line 1123, i…...

企业注册资金需要实缴吗/广州网站优化服务商

【编者按】本文转载自个人博客Heidi格物志&#xff0c;作者为heidixie。本文通过对产品设计的理解、以及对用户本身的了解&#xff0c;对交互设计的理论、意识以及交互方法进行分析。 用户不需要成为一个设计师或者专业的学者&#xff0c;将产品研究透彻才去使用。你能理解用户…...

移动网站设计尺寸/树枝seo

系统功能结构图&#xff1a;功能模块设计&#xff1a;1、注册模块&#xff1a;游客用户可以系统进行账号注册&#xff0c;账号注册需要输入的数据&#xff0c;有用户名、密码、确认密码、邮箱、qq等&#xff0c;提交注册信息后&#xff0c;系统通过js代码判断用户输入的注册数据…...

asp做新闻网站/网络营销专业是做什么的

1. YOLOX的改进 YOLOX 以YOLO v3作为baseline主要做了以下改进&#xff1a; ①. 输入端的图像增强&#xff08;Mosaic、Mixup、RandomHorizontalFlip、ColorJitter、多尺度训练&#xff09; ②. Backbone&#xff08;Darknet53 SPP&#xff09; ③. Neck (FPN PAN) ④. Head…...

餐饮商家做网站的好处/百度首页官网

不充钱&#xff0c;你怎么AC&#xff1f; 题目&#xff1a;http://codevs.cn/problem/1091/ 大家都写的 DFS&#xff0c;然而我想到了一种贪心的做法&#xff0c;重点是可以A 普遍的贪心是每次删掉该深度子树最大的点&#xff0c;但是如果有一边卡一条链就会WA 我们何不进一步考…...

网站的首页页面布局怎么做/谷歌浏览器app下载安装

如图 行尾空格不能使用下划线 解决方法 点击右上角的“文件” 选择“选项” 找到常规与保存 找到兼容性选项 勾选为尾部空格添加下划线 然后确定即可...

做网站和开发app有什么不同/怎么自己创建一个网站

> undefined 与 null 相等&#xff0c;但不恒等&#xff08;&#xff09; > 1、一个是 number 一个是 string 时&#xff0c;会尝试将 string 转换为 number > 2、尝试将 boolean 转换为 number&#xff0c;0 或 1 > 3、尝试将 Object 转换成 number 或string&…...