RocketMQ5.0.0路由中心NameServer

news2024/10/6 8:22:56

一、NameServer概览

NameServer是RocketMQ的注册中心,是消息存储Broker、生产者、消费者沟通的桥梁。NameServer集群之间是相互独立的,Broker启动时向所有NameServer注册中心注册。通过DLedger构建NameServer集群,实现如主从切换等功能。

启动NameServer:启动注册中心,维护路由信息、周期检测Broker发送的心跳包

Broker注册:Broker启动时向所有NameServer发送心跳包、长连接NameServer

Broker删除:NameServer启动定时任务检测Broker是否发送心跳包

生产者发送消息:创建Topic时,向NameServer获取Broker路由信息;

发送消息时,直接向Broker发送消息并消息ACK确认

消费者消费消息:根据PUSH/PULL模式消费消息,消费ACK确认

二、启动NameServer

NameServer启动入口是org.apache.rocketmq.namesrv.NamesrvStartup#main,该方法调用链如下图。核心方法是:org.apache.rocketmq.namesrv.NamesrvController#initialize。

// 初始化NameServer控制器
public boolean initialize() {
    // kvConfig配置加载
    loadConfig();
    // 初始化Netty的server、client
    initiateNetworkComponents();
    // 初始化defaultExecutor、clientRequestExecutor线程池
    initiateThreadExecutors();
    // 路由注册,仅支持临时路由
    registerProcessor();
    // 启动定时任务:5s扫描brokerLiveTable,10min打印日志
    startScheduleService();
    // 初始化SSL上下文
    initiateSslContext();
    // 注册RPC钩子
    initiateRpcHooks();
    return true;
}

三、路由元数据

NameServer管理路由实现类:org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager,该类中主要属性如下代码所示。

// 主题的路由列表信息,消息发送时根据这个列表进行负载均衡
private final Map<String/* topic */, Map<String, QueueData>> topicQueueTable;
// Broker的基础信息
private final Map<String/* brokerName */, BrokerData> brokerAddrTable;
// Broker所在的集群信息
private final Map<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// Broker的状态信息,心跳检查
private final Map<BrokerAddrInfo/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
// Broker的FilterServer列表
private final Map<BrokerAddrInfo/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
// 主题的每个broker的队列映射信息
private final Map<String/* topic */, Map<String/*brokerName*/, TopicQueueMappingInfo>> topicQueueMappingInfoTable;

// 定时批量移除过期Broker服务线程
private final BatchUnregistrationService unRegisterService;

其中brokerLiveTable是Broker向NameServer注册的心跳包信息缓存表,如下UML图所示。NameServer收到Broker发送的心跳包,则更新BrokerLiveInfo下的lastUpdateTimestamp。

四、Broker注册

1.Broker发送心跳包

Broker启动时,向所有NameServer注册,并启动定时任务30s周期发送心跳包。下图是org.apache.rocketmq.broker.BrokerStartup#main的调用链。

org.apache.rocketmq.broker.BrokerController#start方法中,向所有NameServer注册,并启动定时任务30s周期发送心跳包,代码如下。

if (!isIsolated && !this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) {
    changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID);
    this.registerBrokerAll(true, false, true);
}

// broker启动定时任务,每个30s向所有NameServer发送心跳包
scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) {
    @Override
    public void run2() {
        try {
            if (System.currentTimeMillis() < shouldStartTime) {
                BrokerController.LOG.info("Register to namesrv after {}", shouldStartTime);
                return;
            }
            if (isIsolated) {
                BrokerController.LOG.info("Skip register for broker is isolated");
                return;
            }
            // broker注册
            BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
        } catch (Throwable e) {
            BrokerController.LOG.error("registerBrokerAll Exception", e);
        }
    }
}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS));

if (this.brokerConfig.isEnableSlaveActingMaster()) {
    scheduleSendHeartbeat();

    scheduledFutures.add(this.syncBrokerMemberGroupExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) {
        @Override
        public void run2() {
            try {
                BrokerController.this.syncBrokerMemberGroup();
            } catch (Throwable e) {
                BrokerController.LOG.error("sync BrokerMemberGroup error. ", e);
            }
        }
    }, 1000, this.brokerConfig.getSyncBrokerMemberGroupPeriod(), TimeUnit.MILLISECONDS));
}

2.NameServer接收到心跳包

接受请求的总入口org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor#processRequest,注册Broker请求码为RequestCode.REGISTER_BROKER,NameServer注册Broker的核心方法是:org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#registerBroker,如下代码所示。

/*
    注册broker(处理心跳包)
    NameServer与broker保持长连接,NameServer每次收到心跳包将更新关于broker相关信息:
        topicQueueTable 、 brokerAddrTable 、 brokerLiveTable 、 filterServerTable
    注意:
        a. 注册时,加上写锁,防止并发修改主题路由表
        b. 维护BrokerData,先从brokerAddrTable获取,不存在则新增
        c. 是否第一次注册,是否主从,主从是否切换
        d. 创建或更新topic路由元数据,填充topicConfigTable,即:为默认主题自动注册路由信息
        e. 更新broker存活信息BrokerLiveInfo(执行路由删除的重要依据)
 */
public RegisterBrokerResult registerBroker(
    final String clusterName,
    final String brokerAddr,
    final String brokerName,
    final long brokerId,
    final String haServerAddr,
    final String zoneName,
    final Long timeoutMillis,
    final Boolean enableActingMaster,
    final TopicConfigSerializeWrapper topicConfigWrapper,
    final List<String> filterServerList,
    final Channel channel) {
    RegisterBrokerResult result = new RegisterBrokerResult();
    try {
        // 当前线程获取写锁,除非当前线程中断
        this.lock.writeLock().lockInterruptibly();

        //init or update the cluster info
        // 没有则创建,有则添加
        Set<String> brokerNames = ConcurrentHashMapUtils.computeIfAbsent((ConcurrentHashMap<String, Set<String>>) this.clusterAddrTable, clusterName, k -> new HashSet<>());
        brokerNames.add(brokerName);

        boolean registerFirst = false;

        BrokerData brokerData = this.brokerAddrTable.get(brokerName);
        if (null == brokerData) {
            // true时,第一次注册
            registerFirst = true;
            brokerData = new BrokerData(clusterName, brokerName, new HashMap<>());
            this.brokerAddrTable.put(brokerName, brokerData);
        }

        boolean isOldVersionBroker = enableActingMaster == null;
        brokerData.setEnableActingMaster(!isOldVersionBroker && enableActingMaster);
        brokerData.setZoneName(zoneName);

        Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();

        boolean isMinBrokerIdChanged = false;
        long prevMinBrokerId = 0;
        if (!brokerAddrsMap.isEmpty()) {
            prevMinBrokerId = Collections.min(brokerAddrsMap.keySet());
        }

        if (brokerId < prevMinBrokerId) {
            isMinBrokerIdChanged = true;
        }

        //Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT>
        //The same IP:PORT must only have one record in brokerAddrTable
        brokerAddrsMap.entrySet().removeIf(item -> null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey());

        //If Local brokerId stateVersion bigger than the registering one,
        String oldBrokerAddr = brokerAddrsMap.get(brokerId);
        if (null != oldBrokerAddr && !oldBrokerAddr.equals(brokerAddr)) {
            BrokerLiveInfo oldBrokerInfo = brokerLiveTable.get(new BrokerAddrInfo(clusterName, oldBrokerAddr));

            if (null != oldBrokerInfo) {
                long oldStateVersion = oldBrokerInfo.getDataVersion().getStateVersion();
                long newStateVersion = topicConfigWrapper.getDataVersion().getStateVersion();
                if (oldStateVersion > newStateVersion) {
                    log.warn("Registered Broker conflicts with the existed one, just ignore.: Cluster:{}, BrokerName:{}, BrokerId:{}, " +
                            "Old BrokerAddr:{}, Old Version:{}, New BrokerAddr:{}, New Version:{}.",
                        clusterName, brokerName, brokerId, oldBrokerAddr, oldStateVersion, brokerAddr, newStateVersion);
                    //Remove the rejected brokerAddr from brokerLiveTable.
                    brokerLiveTable.remove(new BrokerAddrInfo(clusterName, brokerAddr));
                    return result;
                }
            }
        }

        if (!brokerAddrsMap.containsKey(brokerId) && topicConfigWrapper.getTopicConfigTable().size() == 1) {
            log.warn("Can't register topicConfigWrapper={} because broker[{}]={} has not registered.",
                topicConfigWrapper.getTopicConfigTable(), brokerId, brokerAddr);
            return null;
        }

        String oldAddr = brokerAddrsMap.put(brokerId, brokerAddr);
        registerFirst = registerFirst || (StringUtils.isEmpty(oldAddr));

        // 主broker
        boolean isMaster = MixAll.MASTER_ID == brokerId;
        // 首要的从broker
        boolean isPrimeSlave = !isOldVersionBroker && !isMaster
            && brokerId == Collections.min(brokerAddrsMap.keySet());

        if (null != topicConfigWrapper && (isMaster || isPrimeSlave)) {
            /*
                创建或更新Topic路由元数据,填充topicConfigTable,即:为主题自动注册路由信息
             */

            // topic路由元数据
            ConcurrentMap<String, TopicConfig> tcTable =
                topicConfigWrapper.getTopicConfigTable();
            if (tcTable != null) {
                for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
                    if (registerFirst || this.isTopicConfigChanged(clusterName, brokerAddr,
                        topicConfigWrapper.getDataVersion(), brokerName,
                        entry.getValue().getTopicName())) {
                        final TopicConfig topicConfig = entry.getValue();
                        if (isPrimeSlave) {
                            // Wipe write perm for prime slave
                            topicConfig.setPerm(topicConfig.getPerm() & (~PermName.PERM_WRITE));
                        }
                        // 创建或更新topic自动注册路由信息
                        this.createAndUpdateQueueData(brokerName, topicConfig);
                    }
                }
            }

            if (this.isBrokerTopicConfigChanged(clusterName, brokerAddr, topicConfigWrapper.getDataVersion()) || registerFirst) {
                TopicConfigAndMappingSerializeWrapper mappingSerializeWrapper = TopicConfigAndMappingSerializeWrapper.from(topicConfigWrapper);
                Map<String, TopicQueueMappingInfo> topicQueueMappingInfoMap = mappingSerializeWrapper.getTopicQueueMappingInfoMap();
                //the topicQueueMappingInfoMap should never be null, but can be empty
                for (Map.Entry<String, TopicQueueMappingInfo> entry : topicQueueMappingInfoMap.entrySet()) {
                    if (!topicQueueMappingInfoTable.containsKey(entry.getKey())) {
                        topicQueueMappingInfoTable.put(entry.getKey(), new HashMap<>());
                    }
                    //Note asset brokerName equal entry.getValue().getBname()
                    //here use the mappingDetail.bname
                    topicQueueMappingInfoTable.get(entry.getKey()).put(entry.getValue().getBname(), entry.getValue());
                }
            }
        }

        /*
            更新broker存活信息表,默认120s后执行删除路由信息的重要依据(心跳检测)
         */
        BrokerAddrInfo brokerAddrInfo = new BrokerAddrInfo(clusterName, brokerAddr);
        BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddrInfo,
            new BrokerLiveInfo(
                System.currentTimeMillis(),
                timeoutMillis == null ? DEFAULT_BROKER_CHANNEL_EXPIRED_TIME : timeoutMillis,
                topicConfigWrapper == null ? new DataVersion() : topicConfigWrapper.getDataVersion(),
                channel,
                haServerAddr));
        if (null == prevBrokerLiveInfo) {
            log.info("new broker registered, {} HAService: {}", brokerAddrInfo, haServerAddr);
        }

        // 注册broker的FilterServer列表(一个broker会关联多个FilterServer消息过滤服务器)
        if (filterServerList != null) {
            if (filterServerList.isEmpty()) {
                this.filterServerTable.remove(brokerAddrInfo);
            } else {
                this.filterServerTable.put(brokerAddrInfo, filterServerList);
            }
        }

        // 该broker为从,则更新主broker地址
        if (MixAll.MASTER_ID != brokerId) {
            String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
            if (masterAddr != null) {
                BrokerAddrInfo masterAddrInfo = new BrokerAddrInfo(clusterName, masterAddr);
                BrokerLiveInfo masterLiveInfo = this.brokerLiveTable.get(masterAddrInfo);
                if (masterLiveInfo != null) {
                    result.setHaServerAddr(masterLiveInfo.getHaServerAddr());
                    result.setMasterAddr(masterAddr);
                }
            }
        }

        if (isMinBrokerIdChanged && namesrvConfig.isNotifyMinBrokerIdChanged()) {
            notifyMinBrokerIdChanged(brokerAddrsMap, null,
                this.brokerLiveTable.get(brokerAddrInfo).getHaServerAddr());
        }
    } catch (Exception e) {
        log.error("registerBroker Exception", e);
    } finally {
        this.lock.writeLock().unlock();
    }

    return result;
}

五、路由删除

NameServer每隔5s扫描brokerLiveTable状态表,如BrokerLiveInfo的lastUpdateTimestamp时间戳距当前时间超过120s,则Broker失效,移除该Broker并关闭与Broker连接,同时更新topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable。

RocketMQ有两种情况触发删除Broker路由状态

  • NameServer定时扫描:brokerLiveTable检测上次心跳包与当前系统时间的时间差,如果差值大于120s,则移除该Broker信息;

  • Broker正常关闭:执行unregisterBroker指令。

NameServer定时扫描的核心实现方法是org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#scanNotActiveBroker,代码如下。

// 定时扫描brokerLiveTable(broker存活表),删除心跳过期的broker
public void scanNotActiveBroker() {
    try {
        log.info("start scanNotActiveBroker");
        for (Entry<BrokerAddrInfo, BrokerLiveInfo> next : this.brokerLiveTable.entrySet()) {
            // 上次心跳检测更新时间
            long last = next.getValue().getLastUpdateTimestamp();
            // 心跳过期超时时间
            long timeoutMillis = next.getValue().getHeartbeatTimeoutMillis();
            // 过期判定
            if ((last + timeoutMillis) < System.currentTimeMillis()) {
                // 关闭当前broker的Channel(关闭与broker的长连接)
                RemotingUtil.closeChannel(next.getValue().getChannel());
                log.warn("The broker channel expired, {} {}ms", next.getKey(), timeoutMillis);
                // 删除与该broker的路由信息
                this.onChannelDestroy(next.getKey());
            }
        }
    } catch (Exception e) {
        log.error("scanNotActiveBroker exception", e);
    }
}

六、路由发现

1.生产者路由发现

生产者生产消息时,根据topic会到NameServer获取Broker路由信息缓存到本地,实体类是主题发布信息org.apache.rocketmq.client.impl.producer.TopicPublishInfo,其属性如下。

// 是否顺序消息
private boolean orderTopic = false;
// 是否有topic路由信息
private boolean haveTopicRouterInfo = false;
// topic的消息队列
private List<MessageQueue> messageQueueList = new ArrayList<MessageQueue>();
// 每选择一次消息队列,该值增加1(ThreadLocal<Integer>)
private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex();
// 主题路由信息
private TopicRouteData topicRouteData;

获取主题发布信息的核心实现方法是org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#tryToFindTopicPublishInfo,代码如下。

/**
 * 获取主题路由发布信息
 * 获取逻辑:
 * step1: 本地生产者有缓存该topic路由信息和消息队列,则直接返回
 * step2: 本地生产者没有缓存,则从NameServer查找主题路由信息
 * step3:         没有缓存,从NameServer查找不到,则isDefault是否采用默认主题路由(defaultMQProducer.getCreateTopicKey() —— AUTO_CREATE_TOPIC_KEY_TOPIC)
 * @param topic 主题
 * @return 主题发布信息
 */
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
    // 获取生产者缓存的主题发布信息
    TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
    // 生产者没有主题发布信息或没有消息队列,则创建并更新NameServer主题路由信息
    if (null == topicPublishInfo || !topicPublishInfo.ok()) {
        // 本地生产者创建
        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
        // 更新NameServer主题路由信息
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
    }

    // 本地有缓存,则直接获取
    if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
        return topicPublishInfo;
    } else {
        // isDefault为true,采用默认主题发送消息
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
    }
}

需要注意的是,RocketMQ路由发现是非实时的,当Topic路由出现变化后,NameServer不主动推送给客户端,而是由客户端定时拉取主题最新的路由。

2.消费者路由发现

消费者路由发现逻辑实现比较复杂,已消费组的模式展开,详细见后续章节。

七、参考资料

https://www.cnblogs.com/qdhxhz/p/11094624.html

https://blog.csdn.net/yuanchangliang/article/details/119155557

https://blog.csdn.net/m0_37543627/article/details/128542505

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

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

相关文章

【笔记】大话设计模式24-28

【笔记】大话设计模式24-28 文章目录【笔记】大话设计模式24-2824 职责链模式24.1 Example24.2 定义24.3 Show me the code24.4 总结25 中介者模式25.1 Example25.2 定义25.3 Show me the code25.4 总结26 享元模式26.1 Example26.2 定义26.3 Show me the code26.4 总结27 解释…

aws s3 参与s3game寻找宝藏游戏挑战学习s3对象存储

参考资料 Pirates S3game workshop http://s3game-level1.s3-website.us-east-2.amazonaws.com/level1.html https://blog.benclmnt.com/notes/s3-game/ https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/index.html 强烈推荐这种寓教于乐的方式学…

【ROS2 入门】ROS 2 actions 概述

大家好&#xff0c;我是虎哥&#xff0c;从今天开始&#xff0c;我将花一段时间&#xff0c;开始将自己从ROS1切换到ROS2&#xff0c;在上一篇中&#xff0c;我们一起了解ROS 2中Parameters&#xff0c; 这一篇&#xff0c;我们主要会围绕ROS中另外一个重要的概念“Actions ”来…

Linux 系统调用的实现(x86_64)

目录 1、系统调用的定义 1.1 SYSCALL_METADATA宏 1.2 __SYSCALL_DEFINEx定义 2、系统调用表-sys_call_table数组的定义 3、用户态系统调用流程 kernel 5.10 1、系统调用的定义 系统调用的定义我们其实都不陌生&#xff0c;类似这样的函数SYSCALL_DEFINE0&#xff0c; SYSC…

C语言常见错误汇总

1 数组遍历时使用sizeof(a) 任务&#xff1a;有个数组&#xff0c;找出第二大的数&#xff0c;并且打印出来&#xff08;使用*操作数组元素个数&#xff0c;不要使用[]&#xff09; #include<stdio.h> int main01() {int a[] { 100,100,100,234,123,500,32,68,41,99,1…

code.org免费的少儿编程入门平台

现在市面上的少儿编程课&#xff0c;都是先花9.9就能体验几节课&#xff0c;然后要花几千块才能继续学习后面的课程。这些钱大可不必花。 现在给大家推荐一个免费的网站&#xff0c;code.org&#xff0c;它是一个非营利组织创办的网站&#xff0c;目标是让每个学生都能像生物、…

高并发系统设计 --多级缓存

为了提高系统的性能&#xff0c;一般会引入“缓存机制”&#xff0c;将部分热点数据存入缓存中&#xff0c;用空间换取时间&#xff0c;以达到快速响应的目的。 我们对缓存的认知停留在redis&#xff0c;但其实缓存远远不是只有redis的&#xff0c;从客户端发起请求开始&#…

MySQL整体使用》导入数据、约束、多表查询、事务、变量类型、资源占用

我发的MySQL相关内容&#xff1a; C#基础知识体系框架图&#xff0c;及起对应我发过的博客 linux安装mysql8配置使用&#xff0c;并解决常见问题 MySQL常用命令&#xff08;DQL&#xff09; 执行脚本命令&#xff0c;本地生成SQL文件后在服务器执行 // 进入mysql命令控制 m…

svg绘(viewBox viewport preserveAspectRatio)代替png图片等

当我们的代码中需要一个小图标的时候没必要去iconfont进行下载图标使用 要是下载的png格式那么容量还很大 远不如svg 直接自己代码写 记住svg的坐标朝向和数学坐标轴不一样 实现下图添加的小图标 <svg width"20px" height"20px" style"border: …

2023java面试之Zookeeper基础

一、说说 Zookeeper 是什么&#xff1f;直译&#xff1a;从名字上直译就是动物管理员&#xff0c;动物指的是 Hadoop 一类的分布式软件&#xff0c;管理员三个字体现了 ZooKeeper 的特点&#xff1a;维护、协调、管理、监控。简述&#xff1a;有些软件你想做成集群或者分布式&a…

冯诺依曼体系结构

冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成&#xff1a; 输入单元&#xff1a;包括键盘, 鼠…

netbeans中配置maven

deploy-发布到远程maven库本节默认maven库为nexusnetbeans中按ctrl1&#xff0c;打开Project窗口&#xff1b;在Project窗口中找到相关的project或module,在项目名上点击鼠标右键&#xff1b;在弹出菜单中找到菜单“Run Maven”的子菜单“Goals”&#xff0c;并点击&#xff0c…

PCB封装创建(IC类+USB)

目录 一&#xff1a;IC类 封装原理图 规格参数选最大。创建过程 1.放置焊盘 2.我们需要八个上图焊盘&#xff0c;可以用特殊粘贴 3.丝印层设置 封装向导 右击0805R&#xff0c;选择footprint 输入焊盘尺寸 二&#xff1a;USB封装 原理图 创建过程 1.放置焊盘&#x…

SSM 03_SpringMVC REST风格 Postman SSM整合 拦截器

01-SpringMVC简介SpringMVC是隶属于Spring框架的一部分&#xff0c;主要是用来进行Web开发&#xff0c;是对Servlet进行了封装。SpringMVC是处于Web层的框架&#xff0c;所以其主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端&#xff0c;所…

元宇宙时代业务扩张,专精特新小巨人找到了增长“神器”

进入2023年&#xff0c;元宇宙时代正扑面而来。自从脸书公司更名为Meta以来&#xff0c;元宇宙就在全球迅速走红。《福布斯》认为&#xff0c;2030年全球元宇宙的市场规模有望高达5万亿美元。更为重要的是&#xff0c;元宇宙正在成为数实融合的新界面、未来商业的新型基础设施。…

如何在浏览器中安装使用Vue开发者工具?Vue开发者工具的安装使用?可直接提取插件安装使用

一个混迹于Github、Stack Overflow、开源中国、CSDN、博客园、稀土掘金、51CTO等 的野生程序员。 目标&#xff1a;分享更多的知识&#xff0c;充实自己&#xff0c;帮助他人 GitHub公共仓库&#xff1a;https://github.com/zhengyuzh 以github为主&#xff1a; 1、分享前端后端…

【阶段四】Python深度学习08篇:深度学习项目实战:循环神经网络SimpleRNN、LSTM进行淘宝商品评论文本情感分析

本篇的思维导图: 项目背景 随着信息化社会的发展,互联网成为方便、快捷的信息获取渠道之一。在电子商务和社会网站中,大量非结构化的评论文本作为最直观的用户体验数据被保存下来。如何利用这些文字信息归纳出用户对某一事、物的观点态度成为自然语言(NLP)领域一项…

RNN从理论到实战【实战篇】

来源&#xff1a;投稿 作者&#xff1a;175 编辑&#xff1a;学姐 昨天的文章中&#xff0c;我们学习了RNN的理论部分&#xff0c;本文来看如何实现它&#xff0c;包括堆叠RNN和双向RNN。从而理解它们的原理。最后看一个应用到词性标注任务的实战。 RNNCell 首先实现单时间步…

iMX6ULL —按键输入捕获与GPIO输入配置与高低电平读取

硬件介绍1.1 板子上按键原理图先来看原理图&#xff0c;我板子上有4个按键sw1~sw4:1.1.1 SW1SW1是板子的系统复位按键&#xff0c;不可编程使用1.1.2 SW2、SW3SW2&#xff1a;SNVS_TAMPER1&#xff0c;GPIO5_1平时是低电平&#xff0c;按下去是高电平。SW3&#xff1a;ONOFF它也…

2023年java面试题之zookeeper基础2

一、请描述一下 Zookeeper 的通知机制是什么&#xff1f;Zookeeper 允许客户端向服务端的某个 znode 注册一个 Watcher 监听&#xff0c;当服务端的一些指定事件触发了这个 Watcher &#xff0c;服务端会向指定客户端发送一个事件通知来实现分布式的通知功能&#xff0c;然后客…