Nacos-Go-Sdk代码逻辑解析
文章目录
- 初始化服务,获取nacosClient
- 获取配置
- 源码逻辑
- 获取json配置
- 发布配置
- 监听配置变化
- 源码逻辑
- listenConfigExecutor调度longPulling
- longPulling
- callListener
- 监听表 中 配置 的 结构
- 监听功能的调用逻辑
初始化服务,获取nacosClient
-
Nacos Client Config
- 是用于配置 Nacos 客户端的选项
- 它包含了客户端连接 Nacos 服务器所需的配置信息
- 连接的命名空间(NamespaceId)
- 连接超时时间(TimeoutMs)
- 是否在启动时不加载缓存(NotLoadCacheAtStart)
- 日志级别(LogLevel)等。
-
Nacos Server Config
- 是用于配置 Nacos 服务器的选项
- 它包含了 Nacos 服务器的配置信息
- 服务器的 IP 地址(IpAddr)
- 端口号(Port)
- 上下文路径(ContextPath)
- 协议(Scheme)等。
-
结构:
-
type NacosClient struct {client config_client.IConfigClient } // ClientConfigOptions 存储Nacos ClientConfig的部分配置项 type ClientConfigOptions struct {NamespaceId string `json:"namespaceId"`TimeoutMs uint64 `json:"timeoutMs"`NotLoadCacheAtStart bool `json:"notLoadCacheAtStart"`LogLevel string `json:"logLevel"`AppendToStdout bool `json:"appendToStdout"`LogDir string `json:"logDir"`CacheDir string `json:"cacheDir"` }type ServerConfigOptions struct {IpAddr string `json:"ipAddr"`Port uint64 `json:"port"`ContextPath string `json:"contextPath"`Scheme string `json:"scheme"` }
-
-
初始化
// Nacos Client Config
namespace := "3ac59d8c-8213-4619-859a-a00477496ae4"
ccOpts := nacos.ClientConfigOptions{NamespaceId: namespace,TimeoutMs: 100000000,NotLoadCacheAtStart: true,LogLevel: "debug",AppendToStdout: true,LogDir: "./config",CacheDir: "./config",
}
cfg.NacosClient = &ccOpts// Nacos Server Config
scOpt := nacos.ServerConfigOptions{IpAddr: "nacos.dev.surreal-ai.com",Port: 443,ContextPath: "/nacos",Scheme: "https",
}
cfg.NacosServer = &scOpt
- 创建
-
func NewNacosClient(ccOpts *ClientConfigOptions, scOpt *ServerConfigOptions) (*NacosClient, error) {//new 返回指针sc := []constant.ServerConfig{{IpAddr: scOpt.IpAddr,Port: scOpt.Port,ContextPath: scOpt.ContextPath,Scheme: scOpt.Scheme,},}cc := constant.ClientConfig{NamespaceId: ccOpts.NamespaceId,TimeoutMs: ccOpts.TimeoutMs,NotLoadCacheAtStart: ccOpts.NotLoadCacheAtStart,LogDir: ccOpts.LogDir,CacheDir: ccOpts.CacheDir,LogLevel: ccOpts.LogLevel,AppendToStdout: ccOpts.AppendToStdout,}// a more graceful way to create config clientclient, err := clients.NewConfigClient(vo.NacosClientParam{ClientConfig: &cc,ServerConfigs: sc,},)if err != nil {panic(err)}// 创建新的NacosClient实例并将config_client.IConfigClient包装在其中nacosClient := &NacosClient{client: client,}return nacosClient, nil }
-
获取配置
func (c *NacosClient) GetString(dataid string, group string) (string, error) {content, err := c.client.GetConfig(vo.ConfigParam{DataId: dataid,Group: group,})if err != nil {return content, err}return content, nil
}
源码逻辑
-
GetConfig
-
func (client *ConfigClient) GetConfig(param vo.ConfigParam) (content string, err error) {content, err = client.getConfigInner(param)if err != nil {return "", err}return client.decrypt(param.DataId, content) } -
每次请求获取配置
-
-
getConfigInner
func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content string, err error) {if len(param.DataId) <= 0 {err = errors.New("[client.GetConfig] param.dataId can not be empty")return "", err}if len(param.Group) <= 0 {err = errors.New("[client.GetConfig] param.group can not be empty")return "", err}clientConfig, _ := client.GetClientConfig()cacheKey := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)content, err = client.configProxy.GetConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)if err != nil {logger.Errorf("get config from server error:%+v ", err)if _, ok := err.(*nacos_error.NacosError); ok {nacosErr := err.(*nacos_error.NacosError)if nacosErr.ErrorCode() == "404" {cache.WriteConfigToFile(cacheKey, client.configCacheDir, "")logger.Warnf("[client.GetConfig] config not found, dataId: %s, group: %s, namespaceId: %s.", param.DataId, param.Group, clientConfig.NamespaceId)return "", nil}if nacosErr.ErrorCode() == "403" {return "", errors.New("get config forbidden")}}content, err = cache.ReadConfigFromFile(cacheKey, client.configCacheDir)if err != nil {logger.Errorf("get config from cache error:%+v ", err)return "", errors.New("read config from both server and cache fail")}} else {cache.WriteConfigToFile(cacheKey, client.configCacheDir, content)}return content, nil
}
- 整体逻辑:
- 先尝试从配置服务器获取配置信息
- 如果获取失败则从缓存中获取,如果获取成功则将获取到的配置写入缓存。
- 先判断参数是否合法
- 获取客户端参数和 缓存key
- 向nacos获取
- 获取成功
- 写入对应的缓存文件返回
- 获取失败
- 首先在日志中记录获取配置失败的错误信息。然后,检查错误类型是否为
*nacos_error.NacosError- 如果是,进一步判断错误码。
- 如果错误码是 “404”,表示配置不存在
- 则将空配置写入缓存,并在日志中记录相应的警告信息,然后返回空内容。
- 如果是,进一步判断错误码。
- 如果错误码是 “403”
- 表示无权限访问配置,函数会返回对应的错误信息。
- 如果不是 “404” 或 “403”
- 则表示从配置服务器获取配置信息失败
- 这时尝试从缓存文件中读取配置数据
- 使用
cache.ReadConfigFromFile()方法从缓存中获取配置内容。 - 如果从缓存中获取失败,函数会返回对应的错误信息。
- 使用
- 首先在日志中记录获取配置失败的错误信息。然后,检查错误类型是否为
- 获取成功
- 如果从配置服务器获取配置信息成功(即
err为nil)- 则将获取到的配置内容写入缓存中,使用
cache.WriteConfigToFile()方法。
- 则将获取到的配置内容写入缓存中,使用
获取json配置
func (c *NacosClient) GetObject(dataid string, group string, obj interface{}) error {val, err := c.client.GetConfig(vo.ConfigParam{DataId: dataid,Group: group,})if err != nil {return err}// 假设配置值是JSON格式的,可以使用json.Unmarshal将其解析到传入的obj中err = json.Unmarshal([]byte(val), obj)if err != nil {return err}return nil
}
发布配置
// PublishConfig is used to publish a configuration to Nacos.
func (c *NacosClient) PublishConfig(dataId, group, content string) error {_, err := c.client.PublishConfig(vo.ConfigParam{DataId: dataId,Group: group,Content: content,})if err != nil {return err}return nil
}
监听配置变化
func (c *NacosClient) Listen(dataid string, group string, callback func(namespace, group, dataId, data string)) error {err := c.client.ListenConfig(vo.ConfigParam{DataId: dataid,Group: group,OnChange: func(namespace, group, dataId, data string) {callback(namespace, group, dataId, data)},})if err != nil {return err}return nil
}
源码逻辑
func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {if len(param.DataId) <= 0 {err = errors.New("[client.ListenConfig] DataId can not be empty")return err}if len(param.Group) <= 0 {err = errors.New("[client.ListenConfig] Group can not be empty")return err}clientConfig, err := client.GetClientConfig()if err != nil {err = errors.New("[checkConfigInfo.GetClientConfig] failed")return err}key := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)var cData cacheDataif v, ok := client.cacheMap.Get(key); ok {cData = v.(cacheData)cData.isInitializing = true} else {var (content stringmd5Str string)if content, _ = cache.ReadConfigFromFile(key, client.configCacheDir); len(content) > 0 {md5Str = util.Md5(content)}listener := &cacheDataListener{listener: param.OnChange,lastMd5: md5Str,}cData = cacheData{isInitializing: true,dataId: param.DataId,group: param.Group,tenant: clientConfig.NamespaceId,content: content,md5: md5Str,cacheDataListener: listener,taskId: client.cacheMap.Count() / perTaskConfigSize,}}client.cacheMap.Set(key, cData)return
}
- 参数param为vo.ConfigParam类型
- 表示要监听的配置信息,包括DataId、Group和OnChange等。
- 如果DataId或Group为空,
- 则返回对应的错误信息。
- 首先获取客户端的配置信息,然后根据DataId、Group和NamespaceId构建一个缓存键key。
- 读取监听表 : cacheMap[key]
- 如果监听表中存在对应的配置信息 (cacheMap中存在这个key)
- 则将该配置信息的isInitializing字段设置为true
- 如果监听表中不存在对应的配置信息
- 则从缓存文件中读取内容并计算md5值,创建一个新的cacheData对象
- 并且设置isInitializing字段设置为true
- 如果监听表中存在对应的配置信息 (cacheMap中存在这个key)
- 将该对象加入cacheMap 中,由longPulling 进行监听
- 读取监听表
- 假如有的话,将设置为true
- 没有的话,从缓存文件中读取配置,创建新的 对象放入 监听表中
listenConfigExecutor调度longPulling
func (client *ConfigClient) listenConfigExecutor() func() error {return func() error {// 计算当前监听器的数量listenerSize := client.cacheMap.Count()// 计算总共需要的任务数量,每个任务处理的监听器数量为 perTaskConfigSizetaskCount := int(math.Ceil(float64(listenerSize) / float64(perTaskConfigSize)))// 获取当前正在执行的任务数量currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))// 根据任务数量的比较,进行任务的启动和停止if taskCount > currentTaskCount {// 有新的监听器加入,需要启动新的任务来处理新的监听器for i := currentTaskCount; i < taskCount; i++ {// 设置任务状态为运行中client.schedulerMap.Set(strconv.Itoa(i), true)// 创建新的定时器,定时触发长轮询操作 client.longPulling(i)go client.delayScheduler(time.NewTimer(1*time.Millisecond), 10*time.Millisecond, strconv.Itoa(i), client.longPulling(i))}// 更新当前任务数量为 taskCountatomic.StoreInt32(&client.currentTaskCount, int32(taskCount))} else if taskCount < currentTaskCount {// 有监听器停止监听,需要停止相应的任务for i := taskCount; i < currentTaskCount; i++ {// 检查相应任务是否在 schedulerMap 中,如果存在则将任务状态设置为停止if _, ok := client.schedulerMap.Get(strconv.Itoa(i)); ok {client.schedulerMap.Set(strconv.Itoa(i), false)}}// 更新当前任务数量为 taskCountatomic.StoreInt32(&client.currentTaskCount, int32(taskCount))}return nil}
}
- 根据监听的个数 计算当前需要的 任务协程
longPulling
// longPulling 是一个长轮询监听配置变化的方法。
// 参数 taskId 表示监听任务的标识,用于区分不同的监听任务。
// 该方法返回一个函数,该函数用于执行长轮询操作,监听配置变化。
func (client *ConfigClient) longPulling(taskId int) func() error {return func() error {var listeningConfigs stringinitializationList := make([]cacheData, 0)// 遍历缓存中的所有配置信息,根据 taskId 筛选出当前监听任务的配置信息for _, key := range client.cacheMap.Keys() {if value, ok := client.cacheMap.Get(key); ok {cData := value.(cacheData)if cData.taskId == taskId {// 如果配置数据正在初始化中,则将其加入 initializationList 列表if cData.isInitializing {initializationList = append(initializationList, cData)}// 构建监听配置列表listeningConfigs,用于发送给配置服务器进行监听if len(cData.tenant) > 0 {listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +cData.md5 + constant.SPLIT_CONFIG_INNER + cData.tenant + constant.SPLIT_CONFIG} else {listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +cData.md5 + constant.SPLIT_CONFIG}}}}// 如果有要监听的配置信息,则继续进行长轮询if len(listeningConfigs) > 0 {clientConfig, err := client.GetClientConfig()if err != nil {logger.Errorf("[checkConfigInfo.GetClientConfig] 获取客户端配置失败 err: %+v", err)return err}// 构建监听配置请求参数params,用于发送给配置服务器进行监听params := make(map[string]string)params[constant.KEY_LISTEN_CONFIGS] = listeningConfigsvar changed string// 发送监听配置的请求,进行长轮询changedTmp, err := client.configProxy.ListenConfig(params, len(initializationList) > 0, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)if err == nil {changed = changedTmp} else {// 如果监听配置出现错误,尝试处理错误情况if _, ok := err.(*nacos_error.NacosError); ok {// 如果返回的错误是NacosError类型,则将监听结果设为变更信息(changedTmp)changed = changedTmp} else {// 否则,记录错误日志并返回错误logger.Errorf("[client.ListenConfig] 监听配置错误 err: %+v", err)}return err}// 对于初始化中的配置数据,将其isInitializing字段设为false,表示配置数据已经初始化完毕for _, v := range initializationList {v.isInitializing = falseclient.cacheMap.Set(util.GetConfigCacheKey(v.dataId, v.group, v.tenant), v)}// 根据监听结果changed,判断是否有配置发生了变更,如果有则通知相应的监听器进行处理if len(strings.ToLower(strings.Trim(changed, " "))) == 0 {logger.Info("[client.ListenConfig] 配置无变更")} else {logger.Info("[client.ListenConfig] 配置发生变更: " + changed)client.callListener(changed, clientConfig.NamespaceId)}}// 返回nil,表示长轮询监听成功完成return nil}
}
- 遍历监听表,拼接listeningConfigs
-
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +cData.md5 + constant.SPLIT_CONFIG_INNER + cData.tenant + constant.SPLIT_CONFIG
-
- 发送请求
- 全部的 isInitializing = false , 一次长轮训结束
- 根据返回信息处理
- 假如为空,则未变化
- 不为空
- client.callListener(changed, clientConfig.NamespaceId) 处理变化的值
callListener
// Execute the Listener callback func()
// 执行监听器的回调函数
// 当配置发生变更时,通过该方法通知相应的监听器进行处理。
func (client *ConfigClient) callListener(changed, tenant string) {// 解码配置变更字符串changedDecoded, _ := url.QueryUnescape(changed)// 使用分隔符 "\u0001" 拆分配置变更信息,得到一个 changedConfigs 切片,每个元素表示一个配置项的变更内容。changedConfigs := strings.Split(changedDecoded, "\u0001")// 遍历 changedConfigs,每个元素代表一个配置项的变更信息。for _, config := range changedConfigs {// 使用分隔符 "\u0002" 拆分配置变更项,得到一个 attrs 切片,包含了配置项的 DataId 和 Group 信息,以及其他可能的变更内容。attrs := strings.Split(config, "\u0002")// 如果 attrs 的长度大于等于 2,表示配置项的 DataId 和 Group 信息是有效的,可以根据这些信息从缓存中获取相应的配置数据。if len(attrs) >= 2 {// 从缓存中获取配置数据if value, ok := client.cacheMap.Get(util.GetConfigCacheKey(attrs[0], attrs[1], tenant)); ok {cData := value.(cacheData)// 获取配置内容,并计算新的 MD5 值content, err := client.getConfigInner(vo.ConfigParam{DataId: cData.dataId,Group: cData.group,})if err != nil {// 获取配置内容出错,记录错误日志并继续处理下一个配置变更项logger.Errorf("[client.getConfigInner] DataId:[%s] Group:[%s] Error:[%+v]", cData.dataId, cData.group, err)continue}// 更新配置数据cData.content = contentcData.md5 = util.Md5(content)// 如果 MD5 值与之前的不同,则表示配置发生了变更,调用监听器的回调函数处理配置变更。if cData.md5 != cData.cacheDataListener.lastMd5 {go cData.cacheDataListener.listener(tenant, attrs[1], attrs[0], cData.content)cData.cacheDataListener.lastMd5 = cData.md5client.cacheMap.Set(util.GetConfigCacheKey(cData.dataId, cData.group, tenant), cData)}}}}
}

- 解析出每个变化的配置项信息 ,分隔符"\u0001"
- 遍历
- 解析出具体信息 ,分隔符\u0002"
- 解析出的信息长度大于等于 2,表示配置项的 DataId 和 Group 信息是有效的
- 从监听表 中 拿 这个 配置数据
- getConfigInner 从远端 获取对应的最新数据
- 更新配置数据到
- 假如与之前的MD5 不一样
- 调用 自己设置的回调函数
- 更新监听表
监听表 中 配置 的 结构
type cacheData struct {isInitializing booldataId stringgroup stringcontent stringtenant stringcacheDataListener *cacheDataListener // 回调函数的封装md5 stringappName stringtaskId int
}
- cacheDataListener
-
type cacheDataListener struct {listener vo.Listener //type Listener func(namespace, group, dataId, data string)lastMd5 string }
-
监听功能的调用逻辑

相关文章:
Nacos-Go-Sdk代码逻辑解析
文章目录 初始化服务,获取nacosClient获取配置源码逻辑 获取json配置发布配置监听配置变化源码逻辑listenConfigExecutor调度longPullinglongPullingcallListener监听表 中 配置 的 结构 监听功能的调用逻辑 初始化服务,获取nacosClient Nacos Client Co…...
检测opencv是否安装成功
winr打开命令提示符窗口: 输入 : python,进入python模块; 然后输入 import cv2出现>>>就代表opencv安装成功了; 同样的,也可以检测numpy等是否安装成功 最后输入 : exit() 退出python模块...
如果你是独立开发者,你是先写前端还是先写后端?
当我们站在独立开发的路口时,一个重要的抉择就摆在了我们面前:是先着手前端开发还是后端开发?这看似简单的问题,却蕴含着许多深刻的考虑和决策。无论你是准备构建一个复杂的分布式系统还是一个引人入胜的用户界面,接下…...
Pytorch intermediate(四) Language Model (RNN-LM)
前一篇中介绍了一种双向的递归神经网络,将数据进行正序输入和倒序输入,兼顾向前的语义以及向后的语义,从而达到更好的分类效果。 之前的两篇使用递归神经网络做的是分类,可以发现做分类时我们不需要使用时序输入过程中产生的输出&…...
C++零碎记录(十)
17. 继承对象内存 17.1 查询继承对象所占内存 #include <iostream> using namespace std; #include<string>//继承中的对象模型class Base { public:int m_A; protected:int m_B; private:int m_C; };//公共继承 class Son:public Base {int m_D; };//利用开发人…...
人类学习 vs. 机器学习
摘要: 机器学习与人类学习的范式有一定的联系. 本文发掘这些联系, 作用是指导人类的学习. 1. 什么是学习? 对于人类而言, 学习是改造大脑皮层的过程. 我们会发现, 不同人学习不同东西的能力也不一样, 如有些人数学厉害, 有些人音乐厉害. 同时, 也有些牛人, 学习到了学习的方…...
【LeetCode-中等题】15. 三数之和
文章目录 题目方法一:哈希表 题目 方法一:哈希表 将四数之和 借助哈希表简化成两数之和 class Solution {public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {int res 0; //结果集数量Map<Integer,Integer> map n…...
Apache Tomcat漏洞复现
文章目录 弱口令启动环境漏洞复现 本地文件包含启动环境漏洞复现 弱口令 启动环境 来到vulhub/tomcat/tomcat8/靶场 cd vulhub/tomcat/tomcat8/安装环境并启动: sudo docker-compose up -d && sudo docker-compose up -d修改端口后启动: su…...
C++模版基础
代码地址 gitgithub.com:CHENLitterWhite/CPPWheel.git 专栏介绍 本专栏会持续更新关于STL中的一些概念,会先带大家补充一些基本的概念,再慢慢去阅读STL源码中的需要用到的一些思想,有了一些基础之后,再手写一些STL代码。 (如果你…...
解决 Elasticsearch 分页查询记录超过10000时异常
查询结果中 hits.total.value 值最大为10000的限制 解决方法: 1、请求设置rest_total_hits_as_inttrue 注意参数需要放在请求头上 builder.addHeader("rest_total_hits_as_int","true"); 2、修改setting的值 #设置返回最大记录条数为1000000 PUT /in…...
百度千帆大模型文心一言api调用
注册百度智能云账号并申请文心千帆大模型资格 https://login.bce.baidu.com/ https://cloud.baidu.com/product/wenxinworkshop 创建应用用于获取access_token 创建应用成功后,可以获取到API Key和Secret Key 获取access_token curl https://aip.baidubce.com/oauth/2.0/to…...
关于HTTP协议的概述
HTTP 的报文大概分为三大部分。第一部分是请求行,第二部分是请求的首部,第三部分才是请求的正文实体。 POST 往往是用来创建一个资源的,而 PUT 往往是用来修改一个资源的。 Accept-Charset,表示客户端可以接受的字符集。防止传过…...
ATFX汇市:8月名义与核心CPI走势分化,美国通胀率算升高还是降低?
ATFX汇市:据美国劳工部昨日公布的数据,8月份,美国名义CPI增速最新值3.7%,高于前值3.2%,高于预期值3.6%,显示高通胀问题有抬头迹象。同一时间公布的8月核心CPI年率最新值4.3%,低于前值4.7%&#…...
c++ 中的函数指针
以下图片演示了c中函数指针的用法。如下图可见,把函数地址赋值给函数指针,用函数名或者函数名的地址,都可以,c编译器不报错。即 ptr f 和 ptr &f 都对。但准确的话,函数名就是地址,在编译时候&#x…...
奶牛个体识别 奶牛身份识别
融合YOLOv5s与通道剪枝算法的奶牛轻量化个体识别方法 Light-weight recognition network for dairy cows based on the fusion of YOLOv5s and channel pruning algorithm 论文链接 知网链接 DOI链接 该文章讨论了奶牛花斑、光照条件、不同剪枝方法、不同剪枝率对准确率的影响…...
【力扣每日一题】2023.9.13 检查骑士巡视方案
目录 题目: 示例: 分析: 代码: 题目: 示例: 分析: 题目给我们一个n*n大小的矩阵,矩阵的元素表示骑士已经行动的次数,问我们骑士能不能按照矩阵里元素顺序来巡视整个…...
【Vue】关于CSS样式绑定整理
因突发奇想设计一款组件,需要根据属性动态绑定样式,故而整理一些Vue的动态绑定样式方法(传参绑定类似,不做过多叙述),仅供参考.方式一: 直接在元素上绑定具体样式方式二: 定义属性对象,绑定到style,可以在style中使用定义的变量方式二: 通过引入自定义组件引入style数据,直接绑…...
Sql语句大全--更新
今天抽空整理下项目中的Sql语句 项目中用到的Sql语句大全 Update 语句 Update 语句 Update语句update OLASF1.LLB set CBBTHCC 52 WHERE CBPOLNUMC201728534update OLASF1.LLB set CBBTHCC 01 WHERE CBPOLNUMC201728534update OLASF1.LB set CBBTHCC 01 WHERE CBPOLNUMC…...
Java面试八股文宝典:序言
序言: Java作为一门广泛应用于企业级应用开发的编程语言,一直以来都是技术面试中的重要话题。无论您是刚刚踏入编程世界的新手,还是经验丰富的Java开发工程师,都需要通过面试来展示自己的技能和知识。 在面试中,除了…...
【多线程案例】单例模式
单例模式是设计模式的一种,先谈谈什么是设计模式? 大家应该都知道棋谱、剑谱之类的,就是一些“高手”在经历过长期的累计之后,更具经验写出的具有固定套路的处理“方法”,只要按照这个套路来,在对局之中必然…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
