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 上定义了一份缓存值。在新增系统变量时,要根据实际需求去定义变量的标签和缓存,在使用过程中也要注意取到的值是不是符合预期。
目前对配置项和系统变量的源码解析基本完成,前文中也偶尔提到过“合法性检查”这一概念,在下一篇文章中将会介绍它们是如何进行合法性检查的,以及相关的函数和规则是怎样定义的,欢迎大家持续关注。
参考文档
- 系统变量总览
- 什么是系统变量?如何使用系统变量?
- OceanBase源码
相关文章:
OceanBase 配置项系统变量实现及应用详解(4):新增系统变量
本专题的前几篇文章已经详细阐述了OceanBase的配置项和系统变量的基础用法,并对配置项的源码进行了剖析。但有一些同学可能还对系统变量的实现方式有兴趣,并希望能够像自定义配置项那样,新增一个系统变量。 本文将围绕“如何新增系统变量”这…...
`CAUTION: request is not finished yet!`
前言: 在一次与后台的接口联调中,数据量很大,导致页面卡顿,经排查,浏览器的某个接口显示CAUTION: request is not finished yet! 之前没遇到过这个错误,获取数据的接口开始进行请求,状态码返回…...
科研绘图系列:R语言GWAS曼哈顿图(Manhattan plot)
介绍 曼哈顿图(Manhattan Plot)是一种常用于展示全基因组关联研究(Genome-Wide Association Study, GWAS)结果的图形。GWAS是一种研究方法,用于识别整个基因组中与特定疾病或性状相关的遗传变异。 特点: 染色体表示:曼哈顿图通常将每个染色体表示为一个水平条,染色体…...
DjangoRF-11-创建testcases子应用--任务模块
这里先写任务应用,再写套件,然后写接口,最后再写请求, 这个是新的应用,要创建子应用,然后添加到settings的注册里面 1、和往常一样先写模型,序列化,权限,视图…...
服务器数据恢复—SAN环境下LUN被重复映射导致写操作不互斥的数据恢复案例
服务器存储数据恢复环境: 一台存储中有一组由6块硬盘组成的RAID6,划分为若干LUN,MAP到不同业务的SOLARIS操作系统服务器上。 服务器存储故障: 由于业务变化需要增加一台服务器,在存储在线的状态下将该存储中的某个LUN映…...
Linux系统安全加固:从防火墙到SELinux策略
1. iptables防火墙配置 •基础规则设定:学习如何设置iptable的基本规则,包括允许/拒绝特定端口的进出流量,限制特定IP地址的访问等。 •状态检查:利用iptables的状态检查功能,只允许已建立连接或相关联的流量通过&am…...
排序算法:归并排序,golang实现
目录 前言 归并排序 代码示例 1. 算法包 2. 归并排序代码 3. 模拟程序 4. 运行程序 5. 从大到小排序 归并排序主要操作 1. 合并 2. 分割(Divide)与递归排序(Conquer) 总体思想 循环次数测试 假如 10 条数据进行排序…...
CSS 的工作原理
我们已经学习了CSS的基础知识,它的用途以及如何编写简单的样式表。在本课中,我们将了解浏览器如何获取 CSS 和 HTML 并将其转换为网页。 先决条件:已安装基本软件,了解处理文件的基本知识以及 HTML 基础知识(学习 HTML 简介。目的:要了解浏览器如何解析 CSS 和 HTML 的基…...
买完就后悔?只需几步教你 Apple 怎么申请退款
苹果系统不同于 Android 系统的一点在于下载某一些 App 的时候需要付费才能下载,但是有时候在我们付费之后突然就不想要购买了怎么办呢?别急这可以申请退款,你知道 Apple 怎么申请退款吗?下面就带大家了解一下 Apple 申请退款的步…...
【保卫战】休闲小游戏 链游
...
如何构建自己的交易机器人开发环境
作者:老余捞鱼 原创不易,转载请标明出处及原作者。 写在前面的话: 本文主要讲解如何构建一个交易机器人开发环境。描述具体的步骤和工具,包括使用 GitHub Codespaces、Visual Studio Code(VS Code)…...
解决WordPress文章引用的图片不显示问题
在使用WordPress发布文章时,有时会遇到复制发布的文档中包含的外链图片无法正常显示的问题。然而,当我们将图片路径复制到浏览器中单独打开时,图片却可以正常显示。以下是解决这一问题的方法。 问题描述 当你在WordPress文章中引用外链图片…...
商业银行国际结算规模创新高,合合信息AI助力金融行业智能处理多版式文档
随着我国外贸新业态的快速增长,银行国际结算业务在服务实体经济发展、促进贸易投资便利化进程中发挥了越来越重要的作用。根据中国银行业协会近日发布的《中国贸易金融行业发展报告(2023—2024)》,2023年我国主要商业银行国际结算…...
数字芯片设计验证经验分享:将ASIC IP核移植到FPGA上——更新概念并推动改变以完成充满挑战的任务!
作者:Philipp Jacobsohn,SmartDV首席应用工程师 Sunil Kumar,SmartDV FPGA设计总监 本系列文章从数字芯片设计项目技术总监的角度出发,介绍了如何将芯片的产品定义与设计和验证规划进行结合,详细讲述了在FPGA上使用I…...
【Linux】Linux下的日志(日常级)
日志是日后工作中非常重要的一部分,现在写一份简单的日志项目可以帮助我们熟悉并理解原理。 目录 设计思路:一些实现细节:代码:日志的使用方法: 设计思路: 图示是我们的最终目的。 设计一个类࿰…...
手把手教你如何在Linux上轻松安装Python,告别编程入门难题
导语: Python作为当下最热门的编程语言之一,受到了越来越多人的喜爱。对于Linux用户来说,掌握如何在Linux上安装Python至关重要。今天,就让我带领大家一步步在Linux上安装Python,让你轻松迈入编程世界! 一…...
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("完成的不错࿰…...
【网络安全】LockBit病毒入侵揭秘:如何防范与应对
文章目录 前言 主要特征攻击手段演进历程主要威胁防范与对策 如何入门学习网络安全【黑客】 【----帮助网安学习,以下所有学习资料文末免费领取!----】 大纲学习教程面试刷题 资料领取 前言 在数字时代,随着科技的飞速发展,网络…...
《开源大模型食用指南》适合中国宝宝的部署教程,基于Linux环境快速部署开源大模型
本项目是一个围绕开源大模型、针对国内初学者、基于 AutoDL 平台的中国宝宝专属大模型教程,针对各类开源大模型提供包括环境配置、本地部署、高效微调等技能在内的全流程指导,简化开源大模型的部署、使用和应用流程,让更多的普通学生、研究者…...
体验教程:通义灵码陪你备战求职季
本场景将带大家体验在技术面试准备场景下,如何通过使用阿里云通义灵码实现高效的编程算法题练习 、代码优化、技术知识查询等工作,帮助开发者提升实战能力,更加从容地应对面试挑战。主要包括: 1、模拟题练习:精心挑选…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...
ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
书籍“之“字形打印矩阵(8)0609
题目 给定一个矩阵matrix,按照"之"字形的方式打印这个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为:1,…...
