duckdb 插件机制研究

news2024/9/20 4:38:52

本文研究 duckdb 内置的 extensions 工作机制。

插件架构

在 duckdb 源码内,内置了一组原生插件,位于顶层 extension 目录下:

extensions
除此之外,还支持 Out-of-Tree Extension,简单说就是独立的插件,不是集成在源码树中。独立编译好后,可以 运行时载入到 duckdb 中。
这里有一个细节:为了安全,任何 Out-of-Tree Extension,必须通过 duckdb 官方签名后,才能被正确载入。这个可以一定程度上防范载入有安全风险的第三方插件。

插件原理

为了明白原理,先简单看几个插件的核心注册逻辑。以 dbgen、httpfs、parquet 三个插件为例。

  • dbgen
// https://github.com/duckdb/duckdb/blob/main/extension/tpch/tpch_extension.cpp
namespace duckdb {

static void LoadInternal(DuckDB &db) {
	auto &db_instance = *db.instance;

	TableFunction dbgen_func("dbgen", {}, DbgenFunction, DbgenBind);
	dbgen_func.named_parameters["sf"] = LogicalType::DOUBLE;
	dbgen_func.named_parameters["overwrite"] = LogicalType::BOOLEAN;
	dbgen_func.named_parameters["catalog"] = LogicalType::VARCHAR;
	dbgen_func.named_parameters["schema"] = LogicalType::VARCHAR;
	dbgen_func.named_parameters["suffix"] = LogicalType::VARCHAR;
	dbgen_func.named_parameters["children"] = LogicalType::UINTEGER;
	dbgen_func.named_parameters["step"] = LogicalType::UINTEGER;
	ExtensionUtil::RegisterFunction(db_instance, dbgen_func);

	// create the TPCH pragma that allows us to run the query
	auto tpch_func = PragmaFunction::PragmaCall("tpch", PragmaTpchQuery, {LogicalType::BIGINT});
	ExtensionUtil::RegisterFunction(db_instance, tpch_func);

	// create the TPCH_QUERIES function that returns the query
	TableFunction tpch_query_func("tpch_queries", {}, TPCHQueryFunction, TPCHQueryBind, TPCHInit);
	ExtensionUtil::RegisterFunction(db_instance, tpch_query_func);

	// create the TPCH_ANSWERS that returns the query result
	TableFunction tpch_query_answer_func("tpch_answers", {}, TPCHQueryAnswerFunction, TPCHQueryAnswerBind, TPCHInit);
	ExtensionUtil::RegisterFunction(db_instance, tpch_query_answer_func);
}

void TpchExtension::Load(DuckDB &db) {
	LoadInternal(db);
}

std::string TpchExtension::GetQuery(int query) {
	return tpch::DBGenWrapper::GetQuery(query);
}

std::string TpchExtension::GetAnswer(double sf, int query) {
	return tpch::DBGenWrapper::GetAnswer(sf, query);
}

std::string TpchExtension::Name() {
	return "tpch";
}

std::string TpchExtension::Version() const {
#ifdef EXT_VERSION_TPCH
	return EXT_VERSION_TPCH;
#else
	return "";
#endif
}

} // namespace duckdb

extern "C" {

DUCKDB_EXTENSION_API void tpch_init(duckdb::DatabaseInstance &db) {
	duckdb::DuckDB db_wrapper(db);
	duckdb::LoadInternal(db_wrapper);
}

DUCKDB_EXTENSION_API const char *tpch_version() {
	return duckdb::DuckDB::LibraryVersion();
}
}
  • httpfs

namespace duckdb {

static void LoadInternal(DatabaseInstance &instance) {
	S3FileSystem::Verify(); // run some tests to see if all the hashes work out
	auto &fs = instance.GetFileSystem();

	fs.RegisterSubSystem(make_uniq<HTTPFileSystem>());
	fs.RegisterSubSystem(make_uniq<HuggingFaceFileSystem>());
	fs.RegisterSubSystem(make_uniq<S3FileSystem>(BufferManager::GetBufferManager(instance)));

	auto &config = DBConfig::GetConfig(instance);

	// Global HTTP config
	// Single timeout value is used for all 4 types of timeouts, we could split it into 4 if users need that
	config.AddExtensionOption("http_timeout", "HTTP timeout read/write/connection/retry", LogicalType::UBIGINT,
	                          Value(30000));
	config.AddExtensionOption("http_retries", "HTTP retries on I/O error", LogicalType::UBIGINT, Value(3));
	config.AddExtensionOption("http_retry_wait_ms", "Time between retries", LogicalType::UBIGINT, Value(100));
	config.AddExtensionOption("force_download", "Forces upfront download of file", LogicalType::BOOLEAN, Value(false));
	// Reduces the number of requests made while waiting, for example retry_wait_ms of 50 and backoff factor of 2 will
	// result in wait times of  0 50 100 200 400...etc.
	config.AddExtensionOption("http_retry_backoff", "Backoff factor for exponentially increasing retry wait time",
	                          LogicalType::FLOAT, Value(4));
	config.AddExtensionOption(
	    "http_keep_alive",
	    "Keep alive connections. Setting this to false can help when running into connection failures",
	    LogicalType::BOOLEAN, Value(true));
	config.AddExtensionOption("enable_server_cert_verification", "Enable server side certificate verification.",
	                          LogicalType::BOOLEAN, Value(false));
	config.AddExtensionOption("ca_cert_file", "Path to a custom certificate file for self-signed certificates.",
	                          LogicalType::VARCHAR, Value(""));
	// Global S3 config
	config.AddExtensionOption("s3_region", "S3 Region", LogicalType::VARCHAR, Value("us-east-1"));
	config.AddExtensionOption("s3_access_key_id", "S3 Access Key ID", LogicalType::VARCHAR);
	config.AddExtensionOption("s3_secret_access_key", "S3 Access Key", LogicalType::VARCHAR);
	config.AddExtensionOption("s3_session_token", "S3 Session Token", LogicalType::VARCHAR);
	config.AddExtensionOption("s3_endpoint", "S3 Endpoint", LogicalType::VARCHAR);
	config.AddExtensionOption("s3_url_style", "S3 URL style", LogicalType::VARCHAR, Value("vhost"));
	config.AddExtensionOption("s3_use_ssl", "S3 use SSL", LogicalType::BOOLEAN, Value(true));
	config.AddExtensionOption("s3_url_compatibility_mode", "Disable Globs and Query Parameters on S3 URLs",
	                          LogicalType::BOOLEAN, Value(false));

	// S3 Uploader config
	config.AddExtensionOption("s3_uploader_max_filesize", "S3 Uploader max filesize (between 50GB and 5TB)",
	                          LogicalType::VARCHAR, "800GB");
	config.AddExtensionOption("s3_uploader_max_parts_per_file", "S3 Uploader max parts per file (between 1 and 10000)",
	                          LogicalType::UBIGINT, Value(10000));
	config.AddExtensionOption("s3_uploader_thread_limit", "S3 Uploader global thread limit", LogicalType::UBIGINT,
	                          Value(50));

	// HuggingFace options
	config.AddExtensionOption("hf_max_per_page", "Debug option to limit number of items returned in list requests",
	                          LogicalType::UBIGINT, Value::UBIGINT(0));

	auto provider = make_uniq<AWSEnvironmentCredentialsProvider>(config);
	provider->SetAll();

	CreateS3SecretFunctions::Register(instance);
	CreateBearerTokenFunctions::Register(instance);
}

void HttpfsExtension::Load(DuckDB &db) {
	LoadInternal(*db.instance);
}
std::string HttpfsExtension::Name() {
	return "httpfs";
}

std::string HttpfsExtension::Version() const {
#ifdef EXT_VERSION_HTTPFS
	return EXT_VERSION_HTTPFS;
#else
	return "";
#endif
}

} // namespace duckdb

extern "C" {

DUCKDB_EXTENSION_API void httpfs_init(duckdb::DatabaseInstance &db) {
	LoadInternal(db);
}

DUCKDB_EXTENSION_API const char *httpfs_version() {
	return duckdb::DuckDB::LibraryVersion();
}
}
  • parquet
void ParquetExtension::Load(DuckDB &db) {
	auto &db_instance = *db.instance;
	auto &fs = db.GetFileSystem();
	fs.RegisterSubSystem(FileCompressionType::ZSTD, make_uniq<ZStdFileSystem>());

	auto scan_fun = ParquetScanFunction::GetFunctionSet();
	scan_fun.name = "read_parquet";
	ExtensionUtil::RegisterFunction(db_instance, scan_fun);
	scan_fun.name = "parquet_scan";
	ExtensionUtil::RegisterFunction(db_instance, scan_fun);

	// parquet_metadata
	ParquetMetaDataFunction meta_fun;
	ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(meta_fun));

	// parquet_schema
	ParquetSchemaFunction schema_fun;
	ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(schema_fun));

	// parquet_key_value_metadata
	ParquetKeyValueMetadataFunction kv_meta_fun;
	ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(kv_meta_fun));

	// parquet_file_metadata
	ParquetFileMetadataFunction file_meta_fun;
	ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(file_meta_fun));

	CopyFunction function("parquet");
	function.copy_to_bind = ParquetWriteBind;
	function.copy_to_initialize_global = ParquetWriteInitializeGlobal;
	function.copy_to_initialize_local = ParquetWriteInitializeLocal;
	function.copy_to_sink = ParquetWriteSink;
	function.copy_to_combine = ParquetWriteCombine;
	function.copy_to_finalize = ParquetWriteFinalize;
	function.execution_mode = ParquetWriteExecutionMode;
	function.copy_from_bind = ParquetScanFunction::ParquetReadBind;
	function.copy_from_function = scan_fun.functions[0];
	function.prepare_batch = ParquetWritePrepareBatch;
	function.flush_batch = ParquetWriteFlushBatch;
	function.desired_batch_size = ParquetWriteDesiredBatchSize;
	function.file_size_bytes = ParquetWriteFileSize;
	function.serialize = ParquetCopySerialize;
	function.deserialize = ParquetCopyDeserialize;
	function.supports_type = ParquetWriter::TypeIsSupported;

	function.extension = "parquet";
	ExtensionUtil::RegisterFunction(db_instance, function);

	// parquet_key
	auto parquet_key_fun = PragmaFunction::PragmaCall("add_parquet_key", ParquetCrypto::AddKey,
	                                                  {LogicalType::VARCHAR, LogicalType::VARCHAR});
	ExtensionUtil::RegisterFunction(db_instance, parquet_key_fun);

	auto &config = DBConfig::GetConfig(*db.instance);
	config.replacement_scans.emplace_back(ParquetScanReplacement);
	config.AddExtensionOption("binary_as_string", "In Parquet files, interpret binary data as a string.",
	                          LogicalType::BOOLEAN);
}

std::string ParquetExtension::Name() {
	return "parquet";
}

std::string ParquetExtension::Version() const {
#ifdef EXT_VERSION_PARQUET
	return EXT_VERSION_PARQUET;
#else
	return "";
#endif
}

} // namespace duckdb

#ifdef DUCKDB_BUILD_LOADABLE_EXTENSION
extern "C" {

DUCKDB_EXTENSION_API void parquet_init(duckdb::DatabaseInstance &db) { // NOLINT
	duckdb::DuckDB db_wrapper(db);
	db_wrapper.LoadExtension<duckdb::ParquetExtension>();
}

DUCKDB_EXTENSION_API const char *parquet_version() { // NOLINT
	return duckdb::DuckDB::LibraryVersion();
}
}
#endif

上面三个插件里,分别出现了如下注册字眼:

// tpch
	TableFunction tpch_query_answer_func("tpch_answers", {}, TPCHQueryAnswerFunction, TPCHQueryAnswerBind, TPCHInit);
	ExtensionUtil::RegisterFunction(db_instance, tpch_query_answer_func);
	
// parquet
	auto &db_instance = *db.instance;
	auto &fs = db.GetFileSystem();
	fs.RegisterSubSystem(FileCompressionType::ZSTD, make_uniq<ZStdFileSystem>());
	ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(meta_fun));

// httpfs
	auto &fs = instance.GetFileSystem();
	fs.RegisterSubSystem(make_uniq<HTTPFileSystem>());
	fs.RegisterSubSystem(make_uniq<HuggingFaceFileSystem>());
	fs.RegisterSubSystem(make_uniq<S3FileSystem>(BufferManager::GetBufferManager(instance)));
	ExtensionUtil::RegisterSecretType(instance, secret_type);


可以看到,ExtensionUtil 在这里面扮演了很重要的角色。ExtensionUtil 支持注册普通函数、聚合函数、Table 函数、PragmaFunction、Collation、Secret 等。对于 Duckdb 的框架,了解到这一层已经差不多了,至于 ExtensionUtil 的实现,大家可以根据自己系统的特性灵活处理,本质上无非就是针对每一个类型的扩展,设计一套可扩展、可查找、高性能的功能集合接口。

class ExtensionUtil {
public:
	//! Register a new scalar function - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, ScalarFunction function);
	//! Register a new scalar function set - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, ScalarFunctionSet function);
	//! Register a new aggregate function - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, AggregateFunction function);
	//! Register a new aggregate function set - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, AggregateFunctionSet function);
	//! Register a new table function - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, TableFunction function);
	//! Register a new table function set - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, TableFunctionSet function);
	//! Register a new pragma function - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, PragmaFunction function);
	//! Register a new pragma function set - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, PragmaFunctionSet function);

	//! Register a CreateSecretFunction
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, CreateSecretFunction function);

	//! Register a new copy function - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, CopyFunction function);
	//! Register a new macro function - throw an exception if the function already exists
	DUCKDB_API static void RegisterFunction(DatabaseInstance &db, CreateMacroInfo &info);

	//! Register a new collation
	DUCKDB_API static void RegisterCollation(DatabaseInstance &db, CreateCollationInfo &info);

	//! Returns a reference to the function in the catalog - throws an exception if it does not exist
	DUCKDB_API static ScalarFunctionCatalogEntry &GetFunction(DatabaseInstance &db, const string &name);
	DUCKDB_API static TableFunctionCatalogEntry &GetTableFunction(DatabaseInstance &db, const string &name);

	//! Add a function overload
	DUCKDB_API static void AddFunctionOverload(DatabaseInstance &db, ScalarFunction function);
	DUCKDB_API static void AddFunctionOverload(DatabaseInstance &db, ScalarFunctionSet function);
	DUCKDB_API static void AddFunctionOverload(DatabaseInstance &db, TableFunctionSet function);

	//! Registers a new type
	DUCKDB_API static void RegisterType(DatabaseInstance &db, string type_name, LogicalType type,
	                                    bind_type_modifiers_function_t bind_type_modifiers = nullptr);

	//! Registers a new secret type
	DUCKDB_API static void RegisterSecretType(DatabaseInstance &db, SecretType secret_type);

	//! Registers a cast between two types
	DUCKDB_API static void RegisterCastFunction(DatabaseInstance &db, const LogicalType &source,
	                                            const LogicalType &target, BoundCastInfo function,
	                                            int64_t implicit_cast_cost = -1);
};

} // namespace duckdb

另外,还用到了 RegisterSubSystem,这说明对于文件系统,Duckdb 也做了抽象,方便插件化。

instance.GetFileSystem().RegisterSubSystem(make_uniq<HTTPFileSystem>());

总结

插件机制是个好东西。为了你的系统能够获得这个好东西,最好是系统在第一天设计的时候就做好抽象,把每一类系统功能都抽象成集合,每一类功能都支持“运行时可寻址”。

在 OceanBase 中,最容易插件化的是系统函数,因为它数量众多,客观上从第一天起就逼迫 OceanBase 把它做成易扩展、易寻址的样子。

反面例子是聚合函数,OceanBase 在最初的时候,只支持很少的聚合函数,所以并没有对其做抽象,而是直接在 parsser 里就 hardcode 地抽取出了聚合函数信息,这导致在运行时几乎不可能对其做扩展。如下图所示:

src/parser/sql_parser_mysql_mode.y

aggfunc
至于 filesystem 支持、文件格式支持,OceanBase 更没有做好抽象,他们的插件化,也需要做大量改造才可能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1703257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零代码创建属于自己的情伤治愈者

前言 在这个社会物质文明生活发展迅速的年代&#xff0c;很多人都有心底里难以说出的痛楚&#xff0c;他们往往都与情伤相关&#xff0c;面对这样的情况&#xff0c;我们结合文心智能体设计出一款适合所有人的情伤治愈工具 体验智能体 文心智能体平台是一款基于自然语言处理和…

【鱼眼镜头10】等距Equidistant模型的Kannala-Brandt模型,opencv的鱼眼标定使用的模型。kalibr中的 pinhole + equidistant 都是指该模型。

Kannala Brandt 模型 / opencv中的fisheye / kalibr中的 pinhole equidistant 都是指该模型。 opencv https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html kalibr https://github.com/ethz-asl/kalibr/wiki/supported-models 在之前的博客【鱼眼镜头1】鱼眼…

linux centos磁盘清理相关

清理磁盘流程 1、查看磁盘挂载路径及使用率 df -h2、查看当前文件下文件大小 du -sh *3、制空文件内容 > 文件名 ###制空当前文件内容&#xff0c;直接清0 列子 >access.loglinux操作系统中&#xff0c;经常会遇到磁盘空间满的问题。遇到这样的问题&#xff0c;先查下…

实战 | 使用YoloV8实例分割识别猪的姿态(含数据集)

导 读 本文主要介绍如何使用YoloV8实例分割识别猪的姿态&#xff08;含数据集&#xff09;。 背景介绍 在本文中&#xff0c;我将介绍如何使用YoloV8在猪的自定义数据集上进行实例分割&#xff0c;以识别和跟踪它们的不同姿态。 数据集 使用的数据集来源于Kokkenborg Aps&…

【智能算法】青蒿素优化算法(AO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;C Yuan受到青蒿素药物治疗疟疾过程启发&#xff0c;提出了青蒿素优化算法&#xff08;Artemisinin Optimization, AO&#xff09;。 2.算法原理 2.1算法思想 AO灵感来…

在C++中自定义命名空间,在命名空间中定义string变量,同时定义一个函数实现单词逆置

代码 #include <iostream> #include <cstring> using namespace std; namespace my_space {string s;void reverse(string s);//定义逆置函数 } using namespace my_space; void my_space::reverse(string s){int lens.size();int i0;int jlen-1;while(i<j){//…

MongoDB 和 AI 赋能行业应用:电信和媒体

欢迎阅读“MongoDB 和 AI 赋能行业应用”系列的第二篇。 本系列重点介绍 AI 应用于不同行业的关键用例&#xff0c;涵盖制造业和汽车行业、金融服务、零售、电信和媒体、保险以及医疗保健行业。 电信行业的经营环境以利润空间狭小为特点&#xff0c;尤其是在差异化极小的商品…

markdown画时序图的时候,如何自动显示每一条时序的序号

1: 现象描述 今天画时序图的时候&#xff0c;发现时序上面没有显示序号&#xff0c;看起来不够清晰&#xff0c;只有单纯的说明; 如下图所示 刚测试CSDN的时序图&#xff0c;默认是带序号的&#xff0c;看起来和实际使用的markdown工具有关系&#xff1b; 2&#xff1a;解决办…

MIT6.828 Lab2-1 Using gdb

Using gdb gdb使用&#xff1a; xv6 gdb调试方法 问题1&#xff1a; Looking at the backtrace output, which function called syscall? 按照提示开启gdb后键入&#xff1a; b syscall c layout src backtrace输出结果&#xff1a; (gdb) backtrace #0 syscall () at k…

nodejs开发入门01启动服务器

目录 1 创建项目2 初始化项目3 创建app.js4 服务器启动代码5 浏览器里访问6 部署到云服务器总结 nodejs是一个服务器运行环境&#xff0c;可以让我们搭建我们自己的服务器&#xff0c;接收客户端的请求&#xff0c;并给出响应。第一篇我们介绍一下服务器的搭建以及启动过程。 1…

Compose学习记录(1)

Compose学习记录(1) 简易使用HelloWorld。 新建一个工程&#xff0c;它已经默认启用了compose特性。MainActivity继承自 ComponentActivity&#xff0c;可以用compose来编写UI界面。 // sample 1: simple VersionsetContent {Text("Hello World.")}一个函数&#xf…

【项目】教你手把手完成博客系统(三)显示用户信息 | 实现退出登录 | 实现发布博客

文章目录 教你手把手完成博客系统&#xff08;三&#xff09;7.实现显示用户信息1.约定前后端交互接口2.前端通过ajax发起请求3.服务器处理请求 8.实现退出登录1.约定前后端的接口2.前端发起请求3.服务器处理请求 9.实现发布博客1.约定前后端的交互接口2.前端构造请求3.服务器处…

.NET调用阿里云人脸核身服务端 (ExecuteServerSideVerification)简易流程保姆级教学

需要注意的是&#xff0c;以下内容仅限基础调用 功能说明 该功能是输入核验人的姓名和身份证以及人脸照片&#xff0c;去阿里库里面匹配&#xff0c;3个信息是否一致&#xff0c;一致则验证通过&#xff0c;需要注意的是&#xff0c;人脸有遮挡&#xff0c;或者刘海&#xff0…

三分钟“手撕”顺序表与ArrayList

前言&#xff1a; 实现顺序表的代码放开头&#xff0c;供大家更好的查阅&#xff0c;每个方法都有代码的实现。 其次我会讲解Java自带的ArrayList的实例&#xff0c;扩容机制ArrayList使用方法&#xff0c;遍历以及它的优缺点。 目录 一、自己实现的顺序表 二、Java的ArrayLi…

Flutter中如何让Android的手势导航栏完全透明?

Flutter 开发中 安卓机器都有 像ios 的手势操作栏&#xff0c; 也就是屏幕底下的 那条线。 但这条线默认是有颜色的 &#xff08;像下面这样&#xff09; 一、全屏幕方式 void main() {// 全屏沉浸式SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []…

解决Error: error:0308010C:digital envelope routines::unsupported的四种解决方案

问题描述&#xff1a; 报错&#xff1a;Error: error:0308010C:digital envelope routines::unsupported 报错原因&#xff1a; 主要是因为 nodeJs V17 版本发布了 OpenSSL3.0 对算法和秘钥大小增加了更为严格的限制&#xff0c;nodeJs v17 之前版本没影响&am…

ROS学习笔记(16):夹缝循迹

0.前言 在笔记的第15期对巡墙驾驶的原理进行了简单讲解&#xff0c;而这期我们来讲一下夹缝循迹&#xff0c;也常被叫follow the gap&#xff0c;也更新一些概念。 1.探索式路径规划与避障 1.概念 无预先建图的路径规划叫探索式路径规划&#xff0c;例如巡墙循迹和夹缝循迹&…

解决 Spring Boot 应用启动失败的问题:Unexpected end of file from server

解决 Spring Boot 应用启动失败的问题&#xff1a;Unexpected end of file from server 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的…

启动docker报错:Failed to listen on Docker Socket for the API.

说明&#xff1a; 1、安装部署docker完成后&#xff0c;启动docker报错&#xff1a;Failed to listen on Docker Socket for the API&#xff0c;如下图所示&#xff1a; 2、将SocketGroupdocker更改成&#xff1a;SocketGrouproot即可 一、解决方法&#xff1a; 1、执行命令…

nodejs安装配置

nodejs安装 打开nodejs官网(https://nodejs.org/en/download/package-manager)&#xff0c;参考安装步骤操作。 更新镜像源 输入以下命令&#xff0c;将npm的镜像源设置为淘宝镜像。网上资料中&#xff0c;淘宝镜像地址多为https://registry.npm.taobao.org&#xff0c;这个…