启动rocketmq 报错_RocketMQ为什么要保证订阅关系的一致性?

前段时间有个朋友向我提了一个问题,他说在搭建 RocketMQ 集群过程中遇到了关于消费订阅的问题,具体问题如下:

749e80ffb9ca88837df563121afe9572.png

c0e19b3fb060d8b847ad885795b272df.png

然后他发了报错的日志给我看:

the consumer's subscription not exist

我第一时间在源码里找到了报错的位置:

org.apache.rocketmq.broker.processor.PullMessageProcessor#processRequest:

subscriptionData = consumerGroupInfo.findSubscriptionData(requestHeader.getTopic());if (null == subscriptionData) {  log.warn("the consumer's subscription not exist, group: {}, topic:{}", requestHeader.getConsumerGroup(), requestHeader.getTopic());  response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST);  response.setRemark("the consumer's subscription not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC));  return response;}

此处源码是将该 Topic 的订阅信息找出来,然而这里却没找到,所以报了消费订阅不存在的错误。

朋友还跟我讲了他的消费集群中,每个消费者订阅了自己的 Topic,他的消费组中 有 c1 和 c2 消费者,c1 订阅了 topicA,而 c2 订阅了 topicB。

这时我已经知道什么原因了,我先说一下消费者的订阅信息在 broker 中是以 group 来分组的,数据结构如下:

org.apache.rocketmq.broker.client.ConsumerManager:

private final ConcurrentMap<String/* Group */, ConsumerGroupInfo> consumerTable =  new ConcurrentHashMap<String, ConsumerGroupInfo>(1024);

这意味着集群中的每个消费者在向 broker 注册订阅信息的时候相互覆盖掉对方的订阅信息了,这也是为什么同一个消费组应该拥有完全一样的订阅关系的原因,而朋友在同一个消费组的每个消费者订阅关系都不一样,就出现了订阅信息相互覆盖的问题。

可是朋友这时又有疑惑了,他觉得每个消费者订阅自己的主题,貌似没问题啊,逻辑上也行的通,他不明白为什么 RocketMQ 不允许这样做,于是秉承着老司机的职业素养,下面我会从源码的角度深度分析 RocketMQ 消费订阅注册,消息拉取,消息队列负载与重新分布机制,让大家彻底弄清 RocketMQ 消费订阅机制。

消费者订阅信息注册

消费者在启动时会向所有 broker 注册订阅信息,并启动心跳机制,定时更新订阅信息,每个消费者都有一个 MQClientInstance,消费者启动时会启动这个类,启动方法中会启动一些列定时任务,其中:

org.apache.rocketmq.client.impl.factory.MQClientInstance#startScheduledTask:

this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {  @Override  public void run() {    try {      MQClientInstance.this.cleanOfflineBroker();      MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();    } catch (Exception e) {      log.error("ScheduledTask sendHeartbeatToAllBroker exception", e);    }  }}, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS);

上面是向集群内所有 broker 发送订阅心跳信息的定时任务,源码继续跟进去,发现会给集群中的每个 broker 都发送自己的 HeartbeatData,HeartbeatData 即是每个客户端的心跳数据,它包含了如下数据:

// 客户端IDprivate String clientID;// 生产者信息private Set producerDataSet = new HashSet();// 消费者信息private Set consumerDataSet = new HashSet();

其中消费者信息包含了客户端订阅的主题信息。

我们继续看看 broker 如何处理 HeartbeatData 数据,客户端发送 HeartbeatData 时的请求类型为 HEART_BEAT,我们直接找到 broker 处理 HEART_BEAT 请求类型的逻辑:

org.apache.rocketmq.broker.processor.ClientManageProcessor#heartBeat:

public RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand request) {  RemotingCommand response = RemotingCommand.createResponseCommand(null);  // 解码,获取 HeartbeatData  HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class);  ClientChannelInfo clientChannelInfo = new ClientChannelInfo(    ctx.channel(),    heartbeatData.getClientID(),    request.getLanguage(),    request.getVersion()  );  // 循环注册消费者订阅信息  for (ConsumerData data : heartbeatData.getConsumerDataSet()) {    // 按消费组获取订阅配置信息    SubscriptionGroupConfig subscriptionGroupConfig =      this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(      data.getGroupName());    boolean isNotifyConsumerIdsChangedEnable = true;    if (null != subscriptionGroupConfig) {      isNotifyConsumerIdsChangedEnable = subscriptionGroupConfig.isNotifyConsumerIdsChangedEnable();      int topicSysFlag = 0;      if (data.isUnitMode()) {        topicSysFlag = TopicSysFlag.buildSysFlag(false, true);      }      String newTopic = MixAll.getRetryTopic(data.getGroupName());      this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(        newTopic,        subscriptionGroupConfig.getRetryQueueNums(),        PermName.PERM_WRITE | PermName.PERM_READ, topicSysFlag);    }    // 注册消费者订阅信息    boolean changed = this.brokerController.getConsumerManager().registerConsumer(      data.getGroupName(),      clientChannelInfo,      data.getConsumeType(),      data.getMessageModel(),      data.getConsumeFromWhere(),      data.getSubscriptionDataSet(),      isNotifyConsumerIdsChangedEnable    );    // ...    response.setCode(ResponseCode.SUCCESS);    response.setRemark(null);    return response;  }

在这里我们可以看到,broker 收到 HEART_BEAT 请求后,将请求数据解压获取 HeartbeatData,根据 HeartbeatData 里面的消费订阅信息,循环进行注册:

org.apache.rocketmq.broker.client.ConsumerManager#registerConsumer:

public boolean registerConsumer(final String group, final ClientChannelInfo clientChannelInfo,                                ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere,                                final Set subList, boolean isNotifyConsumerIdsChangedEnable) {  // 获取消费组内的消费者信息  ConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group);  // 如果消费组的消费者信息为空,则新建一个  if (null == consumerGroupInfo) {    ConsumerGroupInfo tmp = new ConsumerGroupInfo(group, consumeType, messageModel, consumeFromWhere);    ConsumerGroupInfo prev = this.consumerTable.putIfAbsent(group, tmp);    consumerGroupInfo = prev != null ? prev : tmp;  }  boolean r1 =    consumerGroupInfo.updateChannel(clientChannelInfo, consumeType, messageModel,                                    consumeFromWhere);  // 更新订阅信息,订阅信息是按照消费组存放的,因此这步骤就会导致同一个消费组内的各个消费者客户端的订阅信息相互被覆盖  boolean r2 = consumerGroupInfo.updateSubscription(subList);  if (r1 || r2) {    if (isNotifyConsumerIdsChangedEnable) {      this.consumerIdsChangeListener.handle(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel());    }  }  this.consumerIdsChangeListener.handle(ConsumerGroupEvent.REGISTER, group, subList);  return r1 || r2;}

这步骤是 broker 更新消费者订阅信息的核心方法,如果消费组的消费者信息 ConsumerGroupInfo 为空,则新建一个,从名字可知道,订阅信息是按照消费组进行存放的,因此在更新订阅信息时,订阅信息是按照消费组存放的,这步骤就会导致同一个消费组内的各个消费者客户端的订阅信息相互被覆盖

消息拉取

在 MQClientInstance 启动时,会启动一条线程来处理消息拉取任务:

org.apache.rocketmq.client.impl.factory.MQClientInstance#start:

// Start pull servicethis.pullMessageService.start();

pullMessageService 继承了 ServiceThread,而 ServiceThread 实现了 Runnable 接口,它的 run 方法实现如下:

org.apache.rocketmq.client.impl.consumer.PullMessageService#run:

@Overridepublic void run() {  while (!this.isStopped()) {    try {      // 从 pullRequestQueue 中获取拉取消息请求对象      PullRequest pullRequest = this.pullRequestQueue.take();      // 执行消息拉取      this.pullMessage(pullRequest);    } catch (InterruptedException ignored) {    } catch (Exception e) {      log.error("Pull Message Service Run Method exception", e);    }  }}

消费端拿到 PullRequest 对象进行拉取消息,pullRequestQueue 是一个阻塞队列,如果 pullRequest 数据为空,执行 take() 方法会一直阻塞,直到有新的 pullRequest 拉取任务进来,这里是一个很关键的步骤,你可能会想,pullRequest 什么时候被创建然后放入 pullRequestQueue?pullRequest 它是在 RebalanceImpl 中创建,它是 RocketMQ 消息队列负载与重新分布机制的实现

消息队列负载与重新分布

从上面消息拉取源码分析可知,pullMessageService 启动时由于 pullRequestQueue 中没有 pullRequest 对象,会一直阻塞,而在 MQClientInstance 启动时,同样会启动一条线程来处理消息队列负载与重新分布任务:

org.apache.rocketmq.client.impl.factory.MQClientInstance#start:

// Start rebalance servicethis.rebalanceService.start();

rebalanceService 同样继承了 ServiceThread,它的 run 方法如下:

@Overridepublic void run() {  while (!this.isStopped()) {    this.waitForRunning(waitInterval);    this.mqClientFactory.doRebalance();  }}

继续跟进去:

org.apache.rocketmq.client.impl.consumer.RebalanceImpl#doRebalance:

public void doRebalance(final boolean isOrder) {  // 获取消费者所有订阅信息  Map subTable = this.getSubscriptionInner();  if (subTable != null) {    for (final Map.Entry entry : subTable.entrySet()) {      final String topic = entry.getKey();      try {        // 消息队列负载与重新分布        this.rebalanceByTopic(topic, isOrder);      } catch (Throwable e) {        if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {          log.warn("rebalanceByTopic Exception", e);        }      }    }  }  this.truncateMessageQueueNotMyTopic();}

这里主要是获取客户端订阅的主题,并根据主题进行消息队列负载与重新分布,subTable 存储了消费者的订阅信息,消费者进行消息订阅时会填充到里面,我们接着往下:

org.apache.rocketmq.client.impl.consumer.RebalanceImpl#rebalanceByTopic:

Set mqSet = this.topicSubscribeInfoTable.get(topic);List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);

rebalanceByTopic 方法是实现 Consumer 端负载均衡的核心,我们这里以集群模式的消息队列负载与重新分布,首先从 topicSubscribeInfoTable 中获取订阅主题的队列信息,接着随机从集群中的一个 broker 中获取消费组内某个 topic 的订阅客户端 ID 列表,这里需要注意的是,为什么从集群内任意一个 broker 就可以获取订阅客户端信息呢?前面的分析也说了,消费者客户端启动时会启动一个线程,向所有 broker 发送心跳包。

org.apache.rocketmq.client.impl.consumer.RebalanceImpl#rebalanceByTopic:

// 如果 主题订阅信息mqSet和主题订阅客户端不为空,就执行消息队列负载与重新分布if (mqSet != null && cidAll != null) {  List mqAll = new ArrayList();  mqAll.addAll(mqSet);  // 排序,确保每个消息队列只分配一个消费者  Collections.sort(mqAll);  Collections.sort(cidAll);  // 消息队列分配算法  AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;  // 执行算法,并得到队列重新分配后的结果对象allocateResult  List allocateResult = null;  try {    allocateResult = strategy.allocate(      this.consumerGroup,      this.mQClientFactory.getClientId(),      mqAll,      cidAll);  } catch (Throwable e) {    log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(),              e);    return;  }  // ...}

以上是消息负载均衡的核心逻辑,RocketMQ 本身提供了 5 种负载算法,默认使用 AllocateMessageQueueAveragely 平均分配算法,它分配算法特点如下:

假设有消费组 g1,有消费者 c1 和 c2,c1 订阅了 topicA,c2 订阅了 topicB,集群内有 broker1 和broker2,假设 topicA 有 8 个消息队列,broker_a(q0/q1/q2/q3) 和 broker_b(q0/q1/q2/q3),前面我们知道 findConsumerIdList 方法会获取消费组内所有消费者客户端 ID,topicA 经过平均分配算法进行分配之后的消费情况如下:

c1:broker_a(q0/q1/q2/q3)

c2:broker_b(q0/q1/q2/q3)

问题就出现在这里,c2 根本没有订阅 topicA,但根据分配算法,却要加上 c2 进行分配,这样就会导致这种情况有一半的消息被分配到 c2 进行消费,被分配到 c2 的消息队列会延迟十几秒甚至更久才会被消费,topicB 同理

下面我用图表示 topicA 和 topicB 经过 rebalance 之后的消费情况:

a9675cbc7196243855584b5253476bbf.png

至于为什么会报 the consumer's subscription not exist,我们继续往下撸:

org.apache.rocketmq.client.impl.consumer.RebalanceImpl#rebalanceByTopic:

if (mqSet != null && cidAll != null) {  // ...  Set allocateResultSet = new HashSet();  if (allocateResult != null) {    allocateResultSet.addAll(allocateResult);  }  // 用户重新分配后的结果allocateResult来更新当前消费者负载的消息队列缓存表processQueueTable,并生成 pullRequestList 放入 pullRequestQueue 阻塞队列中  boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);  if (changed) {    log.info(      "rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",      strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),      allocateResultSet.size(), allocateResultSet);    this.messageQueueChanged(topic, mqSet, allocateResultSet);  }}

以上代码逻辑主要是拿 mqSet 和 cidAll 进行消息队列负载与重新分布,得到结果 allocateResult,它是一个 MessageQueue 列表,接着用 allocateResult 更新消费者负载的消息队列缓存表 processQueueTable,生成 pullRequestList 放入 pullRequestQueue 阻塞队列中:

org.apache.rocketmq.client.impl.consumer.RebalanceImpl#updateProcessQueueTableInRebalance:

List pullRequestList = new ArrayList();// 循环执行,将mqSet订阅数据封装成PullRequest对象,并添加到pullRequestList中for (MessageQueue mq : mqSet) {  // 如果缓存列表不存在该订阅信息,说明这次消息队列重新分配后新增加的消息队列  if (!this.processQueueTable.containsKey(mq)) {    if (isOrder && !this.lock(mq)) {      log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);      continue;    }    this.removeDirtyOffset(mq);    ProcessQueue pq = new ProcessQueue();    long nextOffset = this.computePullFromWhere(mq);    if (nextOffset >= 0) {      ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);      if (pre != null) {        log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);      } else {        log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);        PullRequest pullRequest = new PullRequest();        pullRequest.setConsumerGroup(consumerGroup);        pullRequest.setNextOffset(nextOffset);        pullRequest.setMessageQueue(mq);        pullRequest.setProcessQueue(pq);        pullRequestList.add(pullRequest);        changed = true;      }    } else {      log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);    }  }}// 将pullRequestList添加到PullMessageService中的pullRequestQueue阻塞队列中,以唤醒PullMessageService线程执行消息拉取this.dispatchPullRequest(pullRequestList);

前面我们讲到消息拉取是从 pullRequestQueue 阻塞队列中拿 pullRequest 执行拉取的,以上方法就是创建 pullRequest 的地方。

源码分析到这里,就可以弄清楚为什么会报 the consumer's subscription not exist 这个错误了:

假设有消费者组 g1,g1下有消费者 c1 和消费者 c2,c1 订阅了 topicA,c2 订阅了 topicB,此时c2 先启动,将 g1 的订阅信息更新为 topicB,c1 随后启动,将 g1 的订阅信息覆盖为 topicA,c1 的 Rebalance 负载将 topicA 的 pullRequest 添加到 pullRequestQueue 中,而恰好此时 c2 心跳包又将 g1 的订阅信息更新为 topicB,那么此时 c1 的 PullMessageService 线程拿到 pullRequestQueue 中 topicA 的 pullRequest 进行消息拉取,然而在 broker 端找不到消费者组 g1 下 topicA 的订阅信息(因为此时恰好被 c2 心跳包给覆盖了),就会报消费者订阅信息不存在的错误了

近期热文

RocketMQ消息发送的高可用设计

关于RocketMQ Topic的创建机制,我还有一些细节上的思考

深度解析RocketMQ Topic的创建机制

RocketMQ源码分析之路由中心

RocketMQ的消费模式

分布式事务中间件Seata的设计原理

我对支付平台架构设计的一些思考

Mybatis-spring源码分析之注册Mapper Bean

mybatis-plus源码分析之sql注入器

基于Jenkins Pipeline自动化部署

Dubbo服务暴露之注册地址和端口

Dubbo全链路追踪日志的实现

3daaed920b56cf09ab8b1e1a5536a412.png

长按可以订阅

如果你也有 RocketMQ 方面的问题

欢迎留言,另外

点个在看,欧气满满8f5de9fe202370da1b6c4fc355f0745b.png

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

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

相关文章

scala rest_使用路标的Scala和Java的Twitter REST API

scala rest如果您已经阅读了此博客上的其他文章&#xff0c;您可能会知道我喜欢创建各种数据集的可视化。 我刚刚开始一个小项目&#xff0c;在这里我想可视化来自Twitter的一些数据。 为此&#xff0c;我想直接从Twitter检索有关关注者的信息和个人资料信息。 我实际上开始寻找…

MySql中关于某列中相同数值连续出现次数的统计

MySql中关于某列中相同数值连续出现次数的统计 原表如下&#xff1a; www.2cto.com 100 101 102 100 100 103 104 102 102 105 106 101 101 输出如下&#xff1a; www.2cto.com 100 1 101 2 102 3 100 4 100 4 103 5 104 6 10…

设计模式之- 外观模式(Facade Pattern)

外观模式 外观模式(Facade Pattern)&#xff1a;外部与一个子系统的通信必须通过一个统一的外观对象进行&#xff0c;为子系统中的一组接口提供一个一致的界面&#xff0c;外观模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。外观模式又称为门面模式&…

python的while和for循环

while语句&#xff0c;提供了编写通用循环的一种方法&#xff0c;而for语句是用来遍历序列对象内的元素&#xff0c;并对每个元素运行一个代码块。break,continue用在循环内&#xff0c;跳出整个循环或者跳出一次循环。 一、while循环 1、一般格式 格式&#xff1a;首行以及测试…

go build 无文件_GO笔记之详解GO的编译执行流程

上篇文章介绍了Golang在不同系统下的安装&#xff0c;并完成了经典的Hello World案例。在这个过程中&#xff0c;我们用到了go run命令&#xff0c;它完成源码从编译到执行的整个过程。今天来详细介绍下这个过程。简单理解&#xff0c;go run 可等价于 go build 执行。 build命…

使用Spring Security和jdbc的Spring Boot第2部分

在上一篇文章中&#xff0c;我们基于Spring Security发出请求的默认表架构实现了安全性。 考虑到用户和角色&#xff0c;应用程序开发人员使用适合其需求的架构。 Spring使我们能够指定所需的查询&#xff0c;以便检索用户名&#xff0c;密码和角色等信息。 我们的自定义表将…

MySQL的一些简单语句

mysql 统计 表的数量&#xff1a;SELECT COUNT(1) FROM information_schema.TABLES WHERE TABLE_SCHEMA 你的数据库; MySQL的一些基础语句&#xff1a; 行是记录 列是字段 创建库 CREATE DATABASE [IF NOT EXISTS] 数据库名 [参数[ 参数] [ 参数]...]; 参数: CHARACTER …

【题解】Atcoder ARC#90 F-Number of Digits

Atcoder刷不动的每日一题... 首先注意到一个事实&#xff1a;随着 \(l, r\) 的增大&#xff0c;\(f(r) - f(l)\) 会越来越小。考虑暴力处理出小数据的情况&#xff0c;我们可以发现对于左端点 \(f(l) < 7\) 的情况下&#xff0c;右端点的最大限度为 \(\frac{10^8}{8} 10^7…

java分页查询_面试官:数据量很大,分页查询很慢,有什么优化方案?

准备工作一般分页查询使用子查询优化使用 id 限定优化使用临时表优化关于数据表的id说明《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与…

Python逐行读取文件内容

f open("foo.txt") # 返回一个文件对象 line f.readline() # 调用文件的 readline()方法 while line:print line, # 后面跟 , 将忽略换行符# print(line, end )   # 在 Python 3中使用line f.readline()f.close() 也…

原型模式精讲

原型模式是一种创建型模式,也是属于创建对象的一种方式,像西游记里面的孙悟空吹猴毛也属于原型模式,克隆出来了一群的猴子猴孙,还有细胞的分裂,spring中的Bean的生命周期好像有一个单例还有个原型&#xff0c;那个原型就是每次请求都复制一个对象出来,官方的定义是:用原型实例指…

Python中map()函数浅析

MapReduce的设计灵感来自于函数式编程&#xff0c;这里不打算提MapReduce&#xff0c;就拿python中的map()函数来学习一下。 文档中的介绍在这里&#xff1a; map(function, iterable, ...) Apply function to every item of iterable and return a list of the results. If ad…

选择与循环:剪刀石头布_Python之石头剪刀布小游戏(史上最详细步骤)

​嗨&#xff0c;各位好呀&#xff0c;我是真小凡。相信你如果是一个刚学习Python的小白&#xff0c;一定会很想做一个自己的Python小游戏&#xff08;我就是这样子的&#xff09;&#xff0c;那么今天我们就一起实操一下&#xff01;首先要清楚&#xff0c;做一个项目必须的流…

cometd_CometD:Java Web应用程序的Facebook类似聊天

cometd聊天就像吃一块蛋糕或喝一杯热咖啡一样容易。 您是否曾经考虑过自己开发聊天程序&#xff1f; 您知道&#xff0c;聊天不容易。 但是&#xff0c;如果您是开发人员&#xff0c;并且阅读了本文的最后部分&#xff0c;则可以尝试自行开发一个聊天应用程序&#xff0c;并允许…

常用的C#正则表达式!

"^\d$" //非负整数&#xff08;正整数 0&#xff09; "^[0-9]*[1-9][0-9]*$" //正整数 "^((-\d)|(0))$" //非正整数&#xff08;负整数 0&#xff09; "^-[0-9]*[1-9][0-9]*$" //负整数 "^-?\d$" //整数 "^\d(\.\d)…

java excel 导出图片_JAVA 使用 POI 导出 EXCEL 自定义背景颜色

开发中常用表格导入和导出 Excel 是常见的功能。在这里分享下使用 POI 导出表格的简单实现&#xff0c;也是为大家提供个思路吧&#xff0c;抛砖引玉&#xff0c;话不多说直接上代码。1、项目引入 maven 依赖<!-- 2、导出表格数据接口RequestMapping3、导出效果如下图以上就…

清北学堂Day 3 游记

爆炸&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 上午&#xff1a;emmmm我今天要争取进前40&#xff08;flag 1&#xff09; 拿到试题&#xff0c;瞬间感受到了zhx长者的恶意......两道方案数题&#xff0c;我要凉了啊。 T1:这是道傻逼题&#xff0c;我20分…

隐马尔可夫模型(HMM)攻略

隐马尔可夫模型 (Hidden Markov Model&#xff0c;HMM) 最初由 L. E. Baum 和其它一些学者发表在一系列的统计学论文中&#xff0c;随后在语言识别&#xff0c;自然语言处理以及生物信息等领域体现了很大的价值。平时&#xff0c;经常能接触到涉及 HMM 的相关文章&#xff0c;一…

python中列表,元组,字符串如何互相转换

python中有三个内建函数&#xff1a;列表&#xff0c;元组和字符串&#xff0c;他们之间的互相转换使用三个函数&#xff0c;str(),tuple()和list(),具体示例如下所示: >>> s "xxxxx" >>> list(s) [x, x, x, x, x] >>> tuple(s) (x, x, …

mysql 查看表v空间自增涨_mysql文件结构及InnoDB引擎表空间整理

一、Mysql 的目录结构1、bin目录用于放置一些可执行文件&#xff0c;如mysql.exe、mysqld.exe、mysqlshow.exe等。2、data目录用于放置一些日志文件及数据库3、include目录用于存放一些头文件&#xff0c;如&#xff1a;mysql.h、mysql_ername.h等。4、lib目录用于放置一些库文…