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开发工程师,都需要通过面试来展示自己的技能和知识。 在面试中,除了…...
【多线程案例】单例模式
单例模式是设计模式的一种,先谈谈什么是设计模式? 大家应该都知道棋谱、剑谱之类的,就是一些“高手”在经历过长期的累计之后,更具经验写出的具有固定套路的处理“方法”,只要按照这个套路来,在对局之中必然…...
阿里云部署SpringBoot项目启动后被杀进程的问题
阿里云部署SpringBoot项目启动后被杀进程的问题 最近部署在公司虚拟主机上的SpringBoot项目频繁被杀,这个虚拟主机是个杂货铺,部署着各种项目,时间跨度还大,不同的人负责,个人自扫门前雪,不管他人瓦上霜&a…...
git仓库推送错误
错误背景 从github克隆仓库后,想推送到gitee,在推送时遇到 error: src refspec master does not match any. error: failed to push some refs to <REMOTE_URL>解决方法 rm -rf .github git init git add -A git commit -m "init for gite…...
计网第五章(运输层)(三)
一、UDP协议和TCP协议的对比 1、UDP无连接,TCP面向连接 使用UDP协议的通信双方可以随时发送数据,使用TCP协议的通信双方必须先进行3次握手建立连接,才能发送数据,最后还要进行4次挥手才能释放连接。 2、UDP支持单播、多播以及广…...
OpenCV 07(图像滤波器)
一、卷积 什么是图片卷积? 图像卷积就是卷积核在图像上按行滑动遍历像素时不断的相乘求和的过程 步长 步长就是卷积核在图像上移动的步幅. 上面例子中卷积核每次移动一个像素步长的结果, 如果将这个步长修改为2, 结果会如何? 为了充分扫描图片, 步长一般设为1. padding …...
uniapp项目实践总结(十三)封装文件操作方法
导语:在日常 APP 开发过程中,经常要进行文件的保存、读取列表以及查看和删除文件等操作,接下来就看一下具体的方法。 目录 原理分析方法实现实战演练案例展示 原理分析 主要是以下 API。 uni.saveFile:保存文件到本地缓存列表…...
程序地址空间
✅<1>主页::我的代码爱吃辣 📃<2>知识讲解:Linux——程序地址空间 ☂️<3>开发环境:Centos7 💬<4>前言:我们一直随口就能说出来的栈区,堆区,常量…...
HBS 家庭总线驱动和接收芯片MS1192,应用于电话及相关设备、空调设备、安全设备、AV 装置
MS1192 是适用于 HBS 总线规范(日本电子工业协会) 的适配器芯片,具备发送、接收数据的功能。在发送接收 单元中,采用 AMI 编码方式,可使用双绞线进行互联,信 号传输采用差分方式。 芯片采用单电源…...
IO和进程day08(消息队列、共享内存、信号灯集)
今日任务 1.代码 inversion.c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include …...
【数据结构】—堆排序以及TOP-K问题究极详解(含C语言实现)
食用指南:本文在有C基础的情况下食用更佳 🔥这就不得不推荐此专栏了:C语言 ♈️今日夜电波:ルミネセンス—今泉愛夏 1:01 ━━━━━━️💟──────── 5:05 …...
Python语言概述
视频版教程 Python3零基础7天入门实战视频教程 Python作为一门非常流行的高级编程语言,自从22年开始,TIOBE编程语言排行榜Python一直排第一。 Python简洁高效,丰富的应用场景,受到广大程序员,科研工作者的喜爱。 …...
李可做的网站/app开发教程
SVG 可缩放矢量图形(Scalable Vector Graphics) demo 存在形式: SVG 是使用 XML 来描述二维图形和绘图程序的语言 在html中的引入方式 1、<embed src"helloworld.svg" />2、<object data"rect.svg" width"…...
音乐 版权 做视频网站/个人自己免费建网站
转载自:https://blog.csdn.net/shaoxiaohu1/article/details/40272531 有这样一幅图, 我们想获取其中的连通区域,可以使用以下代码: src_img_name blue_sky_white_clound_002594.jpg; img imread(src_img_name);% get binary …...
英文b2b网站建设/网站管理和维护的主要工作有哪些
mysql中多个类型不同的字段组成一个唯一性索引,不会造成索引失效,但是如果多列字段组成的唯一性索引中有一个字段值是null,唯一性索引将会失效,采取的办法是把默认为null的值设置默认的值 建普通索引 CREATE INDEX 索引名称 ON …...
广东网站制作哪家强/宁波网站推广优化公司电话
build.gradle中的minSdkVersion设置过小,为15,改为29即可。...
政府网站建设 文件/bittorrentkitty磁力猫
学习内容大致内容: 1、架构师筑基(Linux基础与进阶Netty框架Mysql并发编程JVM性能调优Tomcat) 2、开源框架(主要涉及SSM框架) 3、高性能架构(Mysql高性能存储实战Redis缓存数据库Zookpeer分布式消息中间…...
云南网站建设产品介绍/佛山疫情最新消息
Oracle Drop表并未直接删除 drop table xx purge drop表 执行drop table xx 语句 drop后的表被放在回收站(user_recyclebin)里,而不是直接删除掉。这样,回收站里的表信息就可以被恢复,或彻底清除。 通过查询回收站user_recyclebin获取…...