RocketMQ-NameServer详解

前言

​ RocketMQ架构上主要分为四部分, Broker、Producer、Consumer、NameServer,其他三个都会与NameServer进行通信。

Producer:

​ **消息发布的角色,可集群部署。**通过NameServer集群获得Topic的路由信息,包括Topic下面有哪些Queue,这些Queue分布在哪些Broker上等。(Producer只会将消息发送到Master节点,因此只需与Master节点建立连接)。

Consumer:

​ **消息消费的角色,可集群部署。**通过NameServer集群获得Topic的路由信息,连接到对应的Broker上拉取和消费消息。(Master和Slave都可以拉取消息,因此Consumer会与Master和Slave都建立连接)。

Broker:

主要负责消息的存储、投递和查询以及服务高可用保证。

介绍

​ NameServer 是专为 RocketMQ 设计的轻量级名称服务,也是一个简单的Topic路由注册中心。具有简单、可集群横向扩展、无状态,节点之间互不通信等特点。

NameServer 通常以集群的方式部署,各实例间相互不进行信息通讯,只是互为备份,达到高可用的效果。

NameServer具有两大功能:Broker管理和topic路由信息管理。

Broker管理:

​ **NameServer接受Broker的注册请求,处理请求数据作为路由信息的基础数据。**对broker进行心跳检测机制,检测是否还存活(120s);

Topic路由信息管理:

​ 每个NameServer都保存整个Broker集群的路由信息,用于Producer和Conumser查询的路由信息,从而进行消息的投递和消费。

​ NameServer 仅仅处理其他模块的请求,而不会主动向其他模块发起请求。它其实本质上就是一个 NettyServer,它主要有 3 个模块:Topic 路由管理模块(RouteInfoManager)、通信模块(DefaultRequestProcessorClusterTestRequestProcessor)、KV 数据存储模块(KVConfigManager)。

RouteInfoManager

​ **RouteInfoManager 中存储 5 个 HashMap,这就是 NameServer 中主要存储的数据。它们仅存在于内存中,并不会持久化。**其中数据内容如下:

  • **topicQueueTable:保存 Topic 的队列信息,也是真正的路由信息。**队列信息中包含了其所在的 Broker 名称和读写队列数量。
  • brokerAddrTable:保存 Broker 信息,包含其名称、集群名称、主备 Broker 地址。
  • clusterAddrTable:保存 Cluster信息,包含每个集群中所有的 Broker 名称列表。
  • brokerLiveTable:Broker 状态信息,包含当前所有存活的 Broker,和它们最后一次上报心跳的时间。
  • filterServerTable:Broker 上的 FilterServer 列表,用于类模式消息过滤,该机制在 4.4 版本后被废弃。

RequestProcessor 继承了 AsyncNettyRequestProcessor。作为 NameServer 的请求处理器,根据不同种类的请求做不同类型的处理。 其中 KV_CONFIG 类型的请求用于 KVConfig 模块,当前不会用到。其他请求类型由 Broker 和 Producer、Consumer 发起。

​ KVConfigManager 内部保存了一个二级 HashMapconfigTable,并且会将该对象进行持久化。

心跳机制

Broker
  • 每隔 30s 向 NameServer 集群的每台机器都发送心跳包,包含自身 Topic 队列的路由信息。
  • 当有 Topic 改动(创建/更新),Broker 会立即发送 Topic 增量信息到 NameServer,同时触发 NameServer 的数据版本号发生变更(+1)。
NameServer
  • 将路由信息保存在内存中。它只被其他模块调用(被 Broker 上传,被客户端拉取),不会主动调用其他模块。
  • 启动一个定时任务线程,每隔 10s 扫描 brokerAddrTable 中所有的 Broker 上次发送心跳时间,如果超过 120s 没有收到心跳,则从存活 Broker 表中移除该 Broker。
Client
  • 生产者第一次发送消息时,向 NameServer 拉取该 Topic 的路由信息。
  • 消费者启动过程中会向 NameServer 请求 Topic 路由信息。
  • 每隔 30s 向 NameServer 发送请求,获取它们要生产/消费的 Topic 的路由信息。

启动流程

  1. 由启动脚本调用 NamesrvStartup#main 函数触发启动流程
  2. NamesrvStartup#createNamesrvController 函数中先解析命令行参数,然后初始化 NameServer 和 Netty remote server 配置,最后创建 NamesrvController 的实例。
  3. NamesrvStartup#start 初始化 NamesrvController;调用 NamesrvController#start() 方法,启动 Netty remoting server;最后注册关闭钩子函数,在 JVM 线程关闭之前,关闭 Netty remoting server 和处理线程池,关闭定时任务线程。

NamesrvController 实例是 NameServer 的核心控制器,它的初始化方法 initialize() 先加载 KVConfig manager,然后初始化 Netty remoting server。最后添加 2 个定时任务:一个每 10s 打印一次 KV 配置,一个每 10s 扫描 Broker 列表,移除掉线的 Broker。

为什么选择重新开发一个NameServer?

​ RocketMQ的架构设计决定了只需一个轻量级的元数据服务器,只需保持最终一致,所以AP模式直接pass。

NameServer互相独立,彼此没有通信关系,由于Broker向每个NameServer注册自己的路由信息,所以每个NameServer都保存一份完整的路由信息。

​ 单台NameServer挂掉,Broker仍然可以向其它NameServer同步路由信息,不影响其他NameServer,所以Producer,Consumer仍然可以动态感知Broker的路由的信息。

源码分析

NameServer的路由数据来源是broker注册提供,然后内部加工处理,而路由的数据的使用者是producer和consumer。

1、路由数据结构

​ RouteInfoManager是NameServer核心逻辑类,其代码作用就是维护路由信息管理,提供路由注册/查询等核心功能。

由于路由信息都是保存在NameServer应用内存里,其本质就是维护HashMap,而为了防止并发操作,添加了ReentrantReadWriteLock读写锁。

​ RouteInfoManager源码结构如下:

img

QueueData 属性解析:
/*** 队列信息*/
public class QueueData implements Comparable<QueueData> {// 队列所属的Broker名称private String brokerName;// 读队列数量 默认:16private int readQueueNums;// 写队列数量 默认:16private int writeQueueNums;//todo Topic的读写权限(2是写 4是读 6是读写)private int perm;/** 同步复制还是异步复制--对应TopicConfig.topicSysFlag* {@link org.apache.rocketmq.common.sysflag.TopicSysFlag}*/private int topicSynFlag;...省略...}

map: topicQueueTable 数据格式demo(json):

{"TopicTest":[{"brokerName":"broker-a","perm":6,"readQueueNums":4,"topicSynFlag":0,"writeQueueNums":4}]
}
BrokerData 属性解析:
/*** broker的数据:Master与Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0 表示Master,非0表示Slave。*/
public class BrokerData implements Comparable<BrokerData> {// broker所属集群private String cluster;// brokerNameprivate String brokerName;// 同一个brokerName下可以有一个Master和多个Slave,所以brokerAddrs是一个集合// brokerld=O表示 Master,大于 O表示从 Slaveprivate HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;// 用于查找broker地址private final Random random = new Random();...省略...}

map: brokerAddrTable 数据格式demo(json):

{"broker-a":{"brokerAddrs":{"0":"172.16.62.75:10911"},"brokerName":"broker-a","cluster":"DefaultCluster"}
}
BrokerLiveInfo 属性解析:
/***  存放存活的Broker信息,当前存活的 Broker,该信息不是实时的,NameServer 每10S扫描一次所有的 broker,根据心跳包的时间得知 broker的状态,*  该机制也是导致当一个 Broker 进程假死后,消息生产者无法立即感知,可能继续向其发送消息,导致失败(非高可用)*/
class BrokerLiveInfo {//最后一次更新时间private long lastUpdateTimestamp;//版本号信息private DataVersion dataVersion;//Netty的Channelprivate Channel channel;//HA Broker的地址 是Slave从Master拉取数据时链接的地址,由brokerIp2+HA端口构成private String haServerAddr;...省略...}

map: brokerLiveTable 数据格式demo(json):

 {"172.16.62.75:10911":{"channel":{"active":true,"inputShutdown":false,"open":true,"outputShutdown":false,"registered":true,"writable":true},"dataVersion":{"counter":2,"timestamp":1630907813571},"haServerAddr":"172.16.62.75:10912","lastUpdateTimestamp":1630907814074}
}

brokerAddrTable -Map 数据格式demo(json)

{"DefaultCluster":["broker-a"]}

​ 从RouteInfoManager维护的HashMap数据结构和QueueData、BrokerData、BrokerLiveInfo类属性得知,NameServer维护的信息既简单但极其重要。

2、路由注册

路由注册流程:

roker主动注册的几种情况:

  1. 启动时向集群中所有的NameServer注册
  2. 定时30s向集群中所有NameServer发送心跳包注册
  3. 当broker中topic信息发送变更(新增/修改/删除)发送心跳包注册。

​ NameServer中注册的核心处理逻辑是RouteInfoManager#registerBroker

public RegisterBrokerResult registerBroker(final String clusterName, final String brokerAddr,final String brokerName, final long brokerId,final String haServerAddr,//TopicConfigSerializeWrapper比较复杂的数据结构,主要包含了broker上所有的topic信息final TopicConfigSerializeWrapper topicConfigWrapper,final List<String> filterServerList, final Channel channel) {RegisterBrokerResult result = new RegisterBrokerResult();try {try {this.lock.writeLock().lockInterruptibly(); // 锁//1: 此处维护 clusterAddrTable 集群元数据Set<String> brokerNames = this.clusterAddrTable.get(clusterName);if (null == brokerNames) {brokerNames = new HashSet<String>();this.clusterAddrTable.put(clusterName, brokerNames);}brokerNames.add(brokerName);//2:此处维护 brokerAddrTable broker元数据boolean registerFirst = false;//是否第一次注册(如果Topic配置信息发生变更或者该broker为第一次注册)BrokerData brokerData = this.brokerAddrTable.get(brokerName);if (null == brokerData) {registerFirst = true;brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());this.brokerAddrTable.put(brokerName, brokerData);}//3: 此处维护 topicQueueTable  主题队列数据,数据更新操作方法在:createAndUpdateQueueDataString oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);registerFirst = registerFirst || (null == oldAddr);if (null != topicConfigWrapper&& MixAll.MASTER_ID == brokerId) { //小知识点:只处理主节点请求,因为备节点的topic信息是同步主节点的// 如果Topic配置信息发生变更或者该broker为第一次注册if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())|| registerFirst) {ConcurrentMap<String, TopicConfig> tcTable = topicConfigWrapper.getTopicConfigTable();if (tcTable != null) {for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {                      this.createAndUpdateQueueData(brokerName, entry.getValue());}}}}//4: 此处维护:brokerLiveTable数据,关键点:BrokerLiveInfo构造器第一个参数:System.currentTimeMillis(),用于存活判断BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,new BrokerLiveInfo(System.currentTimeMillis(),topicConfigWrapper.getDataVersion(),channel,haServerAddr));if (null == prevBrokerLiveInfo) {log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr);}//5-维护:filterServerTable 数据if (filterServerList != null) {if (filterServerList.isEmpty()) {this.filterServerTable.remove(brokerAddr);} else {this.filterServerTable.put(brokerAddr, filterServerList);}}//返回值(如果当前broker为slave节点)则将haServerAddr、masterAddr等信息设置到result返回值中if (MixAll.MASTER_ID != brokerId) {String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);if (masterAddr != null) {BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);if (brokerLiveInfo != null) {result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());result.setMasterAddr(masterAddr);}}}} finally {this.lock.writeLock().unlock();}} catch (Exception e) {log.error("registerBroker Exception", e);}return result;
}

​ 从源码中分析,Broker注册的路由信息对于NameServer来说,其实就是维护clusterAddrTable、brokerAddrTable、topicQueueTable、brokerLiveTable、filterServerTable。

3、路由删除

​ **路由删除有两种方式,一种是broker主动上报删除,一种是NameServer主动删除。**从根本上来说,是删除clusterAddrTable、brokerAddrTable、topicQueueTable、brokerLiveTable、filterServerTable相关信息.

1)Broker主动上报删除

Broker在正常被关闭的情况下,会执行unregisterBroker指令。向NameServer发送取消注册请求,之后执行删除信息操作(加写锁)。

​ 1-1)直接删除 brokerLiveTable 信息,无需判断时间

​ 1-2)删除 filterServerTable 信息

​ 1-3)维护删除 brokerAddrTable 信息

​ 1-4)维护删除 clusterAddrTable 信息

​ 1-5)根据 BrokerName,从 topicQueueTable 中移除该 Broker 的队列。也就是维护删除 topicQueueTable 信息

2)NameServer主动删除:

​ NameServer定时(10s)扫描brokerLiveTable,检测上次心跳包与当前系统时间的时间差,如果时间戳大于120s,则需要移除该Broker信息。

​ 2-1)判断是否有时间戳大于120s的,若是有,则执行移除操作

​ 2-2)移除前,加读锁,查询需要删除的broker信息。

​ 2-3)根据broker信息brokerAddrFound(加写锁),删除相关信息brokerLiveTable、filterServerTable、brokerAddrTable、clusterAddrTable、topicQueueTable 信息。

4、路由发现

​ 当Topic路由出现变化后,NameServer不会主动推送给客户端,而是由生产端和消费端定时拉取主题最新的路由,所以路由信息非实时的。NameServer 收到客户端获取路由信息请求后,调用 DefaultRequestProcessor#getRouteInfoByTopic() 方法,返回 Topic 路由信息。

​ 源码中,实际上是调用 RouteInfoManager#pickupTopicRouteData() 方法从topicQueueTable、brokerAddrTable、filterServerTable这些Map中查询数据,组装给TopicRouteData,然后返回给客户端使用。

如果该主题为顺序消息,从 KVConfig 中获取顺序消息相关的配置,填充进 TopicRouteData 对象。之后将 TopicRouteData 对象编码,并返回给客户端。

RouteInfoManager#pickupTopicRouteData

public TopicRouteData pickupTopicRouteData(final String topic) {TopicRouteData topicRouteData = new TopicRouteData();boolean foundQueueData = false;boolean foundBrokerData = false;Set<String> brokerNameSet = new HashSet<String>();List<BrokerData> brokerDataList = new LinkedList<BrokerData>();topicRouteData.setBrokerDatas(brokerDataList);HashMap<String, List<String>> filterServerMap = new HashMap<String, List<String>>();topicRouteData.setFilterServerTable(filterServerMap);try {try {this.lock.readLock().lockInterruptibly();List<QueueData> queueDataList = this.topicQueueTable.get(topic);if (queueDataList != null) {topicRouteData.setQueueDatas(queueDataList);foundQueueData = true;Iterator<QueueData> it = queueDataList.iterator();while (it.hasNext()) {QueueData qd = it.next();brokerNameSet.add(qd.getBrokerName());}// 处理构建:BrokerData数据for (String brokerName : brokerNameSet) {BrokerData brokerData = this.brokerAddrTable.get(brokerName);if (null != brokerData) {BrokerData brokerDataClone = new BrokerData(brokerData.getCluster(), brokerData.getBrokerName(), (HashMap<Long, String>) brokerData.getBrokerAddrs().clone());brokerDataList.add(brokerDataClone);foundBrokerData = true;for (final String brokerAddr : brokerDataClone.getBrokerAddrs().values()) {List<String> filterServerList = this.filterServerTable.get(brokerAddr);filterServerMap.put(brokerAddr, filterServerList);}}}}} finally {this.lock.readLock().unlock();}} catch (Exception e) {log.error("pickupTopicRouteData Exception", e);}log.debug("pickupTopicRouteData {} {}", topic, topicRouteData);if (foundBrokerData && foundQueueData) {return topicRouteData;}return null;
}public class TopicRouteData extends RemotingSerializable {//topic排序的配置,和"ORDER_TOPIC_CONFIG"这个NameSpace有关,参照DefaultRequestProcessor#getRouteInfoByTopic,后续可讲解此小知识点private String orderTopicConf;// topic 队列元数据private List<QueueData> queueDatas;// topic分布的 broker元数据private List<BrokerData> brokerDatas;// broker上过滤服务器地址列表private HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;...省略...
}
为什么不用ConcurrentHashMap存储呢?

​ 首先ConcurrentHashMap如果是1.8之前,初始化容量之后,是不能扩容的。其容量默认是16,且锁的粒度是segment,那么最大并发度也就是容量大小。

​ 如果是1.8之后的版本是支持扩容,且在加锁方面有优化,调整为synchronized+CAS,但是同时会要求jdk版本。

​ 最主要是RocketMQ 中的路由信息比较多。

疑问

1、假设Broker异常宕机,导致生产者发送消息失败怎么解决?

​ 假设Broker异常宕机,NameServer至少等120s才将该Broker从路由信息中剔除,在Broker故障期间,消息生产者Producer根据topic获取到的路由信息包含已经宕机的Broker,会导致消息在短时间内发送失败。

​ 那么生产者是否也有相关的尝试策略?

之后再做补充…

总结

​ NameServer作为RocketMQ的“大脑”,保存着集群MQ的路由信息,具体就是记录维护Topic、Broker的信息,及监控Broker的运行状态,为client提供路由能力。

​ 而从源代码的角度总结:NameServer就是维护了多个HashMap,Broker的注册,Client的查询都是围绕其Map操作,当然为了解决并发问题添加了ReentrantReadWriteLock(读写锁)。

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

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

相关文章

数据结构与算法编程题15

设计一个算法&#xff0c;通过遍历一趟&#xff0c;将链表中所有结点的链接方向逆转&#xff0c;仍利用原表的存储空间。 #include <iostream> using namespace std;typedef int Elemtype; #define ERROR 0; #define OK 1;typedef struct LNode {Elemtype data; …

【从入门到起飞】JavaSE—多线程(3)(生命周期,线程安全问题,同步方法)

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f354;生命周期&#x1f384;线程的安全问题&#…

YOLOv7独家改进: Inner-IoU基于辅助边框的IoU损失,高效结合 GIoU, DIoU, CIoU,SIoU 等 | 2023.11

💡💡💡本文独家改进:Inner-IoU引入尺度因子 ratio 控制辅助边框的尺度大小用于计算损失,并与现有的基于 IoU ( GIoU, DIoU, CIoU,SIoU )损失进行有效结合 推荐指数:5颗星 新颖指数:5颗星 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c …

大模型AI Agent 前沿调研

前言 大模型技术百花齐放&#xff0c;越来越多&#xff0c;同时大模型的落地也在紧锣密鼓的进行着&#xff0c;其中Agent智能体这个概念可谓是火的一滩糊涂。 今天就分享一些Agent相关的前沿研究&#xff08;仅限基于大模型的AI Agent研究&#xff09;&#xff0c;包括一些论…

完美解决AttributeError: module ‘numpy‘ has no attribute ‘typeDict‘

文章目录 前言一、完美解决办法安装低版本1.21或者1.19.3都可以总结 前言 这个问题从表面看就是和numpy库相关&#xff0c;所以是小问题&#xff0c;经过来回调试安装numpy&#xff0c;发现是因为目前的版本太高&#xff0c;因此我们直接安装低版本numpy。也不用专门卸载目前的…

Qt全球峰会2023中国站 参会概要

Qt全球峰会2023中国站 参会概要 前言峰会议程签到 & Demo 演示开场致辞Qt Group 产品总监演讲&#xff08;产品开发的趋势-开放的软件、工具和框架&#xff09;产品战略QtQuick or QtWidgets&#xff08;c or qml&#xff09;Qt如何定义AI个人看法 Qt 在券商数字化转型和信…

【MySQL】内连接和外连接

内连接和外连接 前言正式开始内连接外连接左外连接右外连接 前言 前一篇讲多表查询的时候讲过笛卡尔积&#xff0c;其实笛卡尔积就算一种连接&#xff0c;不过前一篇讲的时候并没有细说连接相关的内容&#xff0c;本篇就来详细说说表的连接有哪些。 本篇博客中主要用到的还是…

中部A股第一城,长沙如何赢商?

文|智能相对论 作者|范柔丝 长沙的马路&#xff0c;都很有故事。 一条解放西路&#xff0c;是全国人民都争相打卡的娱乐地标&#xff1b;一条太平街&#xff0c;既承载了历史的厚重又演绎着现代的鲜活...... 但如果来到河西的桐梓坡路&#xff0c;风景会变得截然不同。 沿…

安装MySQL搭建论坛

课前默写&#xff1a; 1、nginx配置文件的区域有哪些 ①全局区域 ②events区域 ③http区域 2、区域模块的作用 全局区域模块主要是用户和工作进程 events区域模块配置最大连接数时需先配置:vim /etc/limits.conf 因为系统默认最大是1024 http区域模块&#xff1a;代理地…

BUUCTF [HBNIS2018]excel破解 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 得到的 flag 请包上 flag{} 提交。来源&#xff1a; https://github.com/hebtuerror404/CTF_competition_warehouse_2018 密文&#xff1a; 下载附件&#xff0c;得到一个attachment.xls文件。 解题思路&#xff…

计算机视觉的应用19-基于pytorch框架搭建卷积神经网络CNN的卫星地图分类问题实战应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用19-基于pytorch框架搭建卷积神经网络CNN的卫星地图分类问题实战应用。随着遥感技术和卫星图像获取能力的快速发展&#xff0c;卫星图像分类任务成为了计算机视觉研究中一个重要的挑战。为了促进这一…

git的用法

目录 一、为什么需要git 二、git基本操作 2.1、初始化git仓库 2.2、配置本地仓库的name和email 2.3、认识工作区、暂存区、版本库 三、git的实际操作 3.1 提交文件 3.2 查看git状态以及具体的修改 3.3 git版本回退 git reset 3.1 撤销修改 四、git分支管理 4.…

vue el-table (固定列+滚动列)【横向滚动条】确定滚动条是在列头还是列尾

效果图&#xff1a; 代码实现&#xff1a; html&#xff1a; <script src"//unpkg.com/vue2/dist/vue.js"></script> <script src"//unpkg.com/element-ui2.15.14/lib/index.js"></script> <div id"app" style&quo…

【数据结构】树如何定义 | 如何存储 | 实际应用

前言 如上图&#xff0c;A中的孩子的个数是不固定的。我们无法精确的每个不同的根结点有多少个孩子。所以并不能精确知道需要定义多少个孩子节点。 struct TreeNode {int val;struct TreeNode* child1;struct TreeNode* child2;struct TreeNode* child3;//...//这样显然是不能…

springboot 拦截器中使用@Value注解为null

拦截器中获取配置参数为null 代码如下&#xff1a; 解决方式一&#xff1a; 检查你的WebMvcConfigurer实现类&#xff0c;比如我的是CCBWebMvcConfig 将拦截器以bean的形式注入&#xff1a; 我之前的写法是new 一个放进去的&#xff0c;这种会导致Value为null AutowiredJSCCB…

2014年10月6日 Go生态洞察:Go在Google I/O和Gopher SummerFest的应用

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

《微信小程序开发从入门到实战》学习二十七

3.4 开发参与投票页面 3.4.2 借用伪造数据开发功能 为了便于开发&#xff0c;新建一个编译模式&#xff1a; 之前没看文章&#xff0c;每次都习惯性填完投票创建的信息提交再跳转看效果。好累。 添加变异模式开发真方便。 另外&#xff0c;点击提交后没跳转到投票页面&#…

xorm源码学习

文章目录 XORM源码浅析及实践ORMORM vs. SQLXORM软件架构 ORM 引擎 Engine——DBM*core.DB Golang&#xff1a;database/sql 源码基本结构连接复用&#xff0c;提高性能。增加数据库连接池数量连接管理 database/sql主要内容&#xff1a;sql.DB创建数据库连接sql.Open()DB.conn…

Spring——感谢尚硅谷官方文档

Spring——尚硅谷学习笔记 1 Spring简介&#x1f47e;1.1 Spring概述1.2 Spring Framework1.2.1 Spring Framework特性1.2.2 Spring Framework五大功能模块 2 IOC-IOC容器思想&#x1f47e;IOC容器思想IOC在Spring中的实现 3 IOC-基于XML文件管理Bean&#x1f47e;3.1 准备工作…

2023亚太杯数学建模A题思路 - 采果机器人的图像识别技术

# 1 赛题 问题A 采果机器人的图像识别技术 中国是世界上最大的苹果生产国&#xff0c;年产量约为3500万吨。与此同时&#xff0c;中国也是世 界上最大的苹果出口国&#xff0c;全球每两个苹果中就有一个&#xff0c;全球超过六分之一的苹果出口 自中国。中国提出了一带一路倡议…