RocketMQ 消费者分类与分组

文章目录

  • 消费者分类
    • PushConsumer
      • PushConsumer 内部原理
      • 使用注意事项
    • SimpleConsumer
      • invisibleDuration 消息不可见时间
  • 消费者分组(消费者负载均衡)
    • 广播消费和共享消费
    • 负载均衡策略
    • 多个消费者消费顺序消息
      • 多消费者消费顺序消息示例
  • 消费者分组管理
    • 关闭自动创建消费者分组
    • 使用 admin tool 工具管理消费者分组
      • updateSubGroup 更新或修改订阅关系(更新或修改消费者分组信息)
      • deleteSubGroup 从 Broker 删除订阅关系(删除消费者分组)

消费者分类

RocketMQ 支持 PushConsumer 、 SimpleConsumer 以及 PullConsumer 这三种类型的消费者。每一种类型的消费者处理逻辑都回经过 消息获取—>消息处理—>消费状态提交 3 个阶段,只是它们每一个的实现方式不一样。

对比项PushConsumerSimpleConsumerPullConsumer
实现方式使用监听器回调接口返回消费结果,消费者仅允许在监听器范围内处理消费逻辑。业务方自行实现消息处理,并主动调用接口返回消费结果。业务方自行按队列拉取消息,并可选择性地提交消费结果
消费并发度管理由SDK管理消费并发度由业务方消费逻辑自行管理消费线程由业务方消费逻辑自行管理消费线程
负载均衡粒度5.0 SDK是消息粒度,更均衡,早期版本是队列维度消息粒度,更均衡队列粒度,吞吐攒批性能更好,但容易不均衡
接口灵活度高度封装,不够灵活原子接口,可灵活自定义原子接口,可灵活自定义
适用场景适用于无自定义流程的业务消息开发场景适用于需要高度自定义业务流程的业务开发场景仅推荐在流处理框架场景下集成使用

在实际使用场景中,PullConsumer 仅推荐在流处理框架中集成使用,大多数消息收发场景使用 PushConsumer 和 SimpleConsumer 就可以满足需求。PullConsumer 在gRPC协议客户端中目前还尚未实现。

相同的 ConsumerGroup 下严禁混用 PullConsumer 和其他两种消费者,否则会导致消息消费异常。

PushConsumer

PushConsumers 是一种高度封装的消费者类型,消费消息仅通过消费监听器处理业务并返回消费结果。消息的获取、消费状态提交以及消费重试都通过 RocketMQ 的客户端SDK完成。我们的示例目前都是使用的 PushConsumer ,这里我们就不再贴示例代码了。

PushConsumer 中我们的实际消费代码 是通过消费监听器 MessageListener 实现,其 public ConsumeResult consume(MessageView messageView) 方法的执行结果分如下几种情况:

  • 返回ConsumeResult.SUCCESS:表示该消息处理成功,服务端按照消费结果更新消费进度
  • 返回ConsumeResult.FAILURE:示该消息处理失败,需要根据消费重试逻辑判断是否进行重试消费
  • 方法内部异常:该结果按照消费失败处理,需要根据消费重试逻辑判断是否进行重试消费

这里涉及到 PushConsumer 的重试逻辑,这个我们会在后续 《RocketMQ 消息重试机制》一章中统一说明

PushConsumer 内部原理

在PushConsumer类型中,消息的实时处理能力是基于SDK内部的典型Reactor线程模型实现的。如下图所示,SDK内置了一个长轮询线程,先将消息异步拉取到SDK内置的缓存队列中,再分别提交到消费线程中,触发监听器执行本地消费逻辑

在这里插入图片描述

使用注意事项

  • 禁止在 PushConsumer 监听方法中再次定义线程来分发处理。因为这会导致消息尚未处理完成,而监听方法提前返回处理成功,导致如果线程执行失败,无法进入重试逻辑。
  • 如果消息处理时间不可预估(直接时间超长)或业务逻辑复杂需要用到多线程时,请使用 SimpleConsumer。

PushConsumer 默认的处理线程数量为 20,可通过 PushConsumerBuilder.setConsumptionThreadCount() 设置线程数

总结:PushConsumer 内置实现了消息的消费、确认、顺序消息的顺序性、异常重试等多项功能,使用起来比较简单,但其扩展性没有 SimpleConsumer 好,不允许使用异步化和自定义处理流程。

SimpleConsumer

SimpleConsumer 的使用涉及多个接口调用,由业务逻辑按需调用接口获取消息,然后分发给业务线程处理消息,最后按照处理的结果调用提交接口,返回服务端当前消息的处理结果。示例如下:

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.SimpleConsumer;
import org.apache.rocketmq.client.apis.message.MessageView;import java.time.Duration;
import java.util.Collections;
import java.util.List;public class SimpleConsumerDemo {public static void main(String[] args) throws ClientException {// 用于提供:生产者、消费者、消息对应的构建类 BuilderClientServiceProvider provider = ClientServiceProvider.loadService();// 构建配置类(包含端点位置、认证以及连接超时等的配置)ClientConfiguration configuration = ClientConfiguration.newBuilder()// endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081.setEndpoints(MyMQProperties.ENDPOINTS).build();// 设置过滤条件(这里为使用 tag 进行过滤)String tag = "ORDER_SUBMIT";FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);SimpleConsumer simpleConsumer = provider.newSimpleConsumerBuilder()// 设置消费者分组。.setConsumerGroup("MY_ORDER_SUBMIT_GROUP")// 设置接入点。.setClientConfiguration(configuration)// 设置预绑定的订阅关系。.setSubscriptionExpressions(Collections.singletonMap("MY_NORMAL_TOPIC", filterExpression))// 设置从服务端接受消息的最大等待时间.setAwaitDuration(Duration.ofSeconds(3)).build();//  一次获取多少个消息int maxMessageNum = 10;// 获取消息后,这些消息多长时间内,对其他消费者不可见Duration invisibleDuration = Duration.ofSeconds(30);try {List<MessageView> messageViewList = simpleConsumer.receive(maxMessageNum,invisibleDuration);// 循环处理所有取出的消息messageViewList.forEach(messageView -> {// TODO 业务代码System.out.println("simpleConsumer 消费消息:" + messageView);try {// 处理完成需要手动编写 ACK 代码提交消费结果simpleConsumer.ack(messageView);} catch (ClientException e) {e.printStackTrace();throw new RuntimeException(e);}});}catch (Exception e){// 处理 receive 获取消息时的异常(比如:消息拉取失败),常常需要重新获取消息e.printStackTrace();}}}

相对于 PushConsumer 的高度封装, SimpleConsumer 更加灵活,适用于需要异步处理、需要高度自定义消费的消息。

invisibleDuration 消息不可见时间

invisibleDuration 此参数十分重要,需要根据消息消费的时长来设定,该参数除了设定消息的不可见时间,还涉及到消息的重试与重试时间间隔。甚至还有一个方法:simpleConsumer.changeInvisibleDuration(); 和 simpleConsumer.changeInvisibleDurationAsync() 来在必要时修改延长这个不可见时间。关于消费重试的相关问题,这个我们会在后续 《RocketMQ 消息重试机制》一章中统一说明。

消费者分组(消费者负载均衡)

消费者分组,在gRPC协议客户端代码中就是一个字符串如 setConsumerGroup(“MY_FIFO_GROUP”),但其有十分重要的作用。

消费者分组是 RocketMQ 系统中承载多个消费行为一致的消费者的负载均衡分组,消费者分组并不是运行实体,而是一个逻辑资源。通过消费者分组内初始化多个消费者实现消费性能的水平扩展以及高可用容灾。

广播消费和共享消费

RocketMQ 的领域模型中,一条消息可以由多个消费者分组订阅,一个消费组中又可以初始化多个消费者,同一个消费组的消费者共享消费消息,不同消费组的间的消费者是广播消费。

在这里插入图片描述

  • 消费组间广播消费 :如上图所示,每个消费者分组只初始化唯一一个消费者,每个消费者可消费到消费者分组内所有的消息,各消费者分组都订阅相同的消息,以此实现单客户端级别的广播一对多推送效果。
  • 消费组内共享消费 :如上图所示,每个消费者分组下初始化了多个消费者,这些消费者共同分担消费者分组内的所有消息,实现消费者分组内流量的水平拆分和均衡负载。

关于广播消费的情况,我们在前面的章节 《RocketMQ 发送事务消息》的示例中已经使用。

负载均衡策略

RocketMQ 5.x + gRPC 客户端,默认是消息粒度的负载均衡策略,同一消费组中的多个消费者按照消息的粒度平均分摊主题中的所有消息,但具体某个消息被分发到哪个消费者是随机的。

消费者获取某条消息后,服务端会将该消息加锁,保证这条消息对其他消费者不可见,直到该消息消费成功或消费超时,因此,即使多个消费者同时消费同一队列的消息,服务端也可保证消息不会被多个消费者重复消费。

在这里插入图片描述

多个消费者消费顺序消息

在这里插入图片描述

如上图所述,队列Queue1中有4条顺序消息,这4条消息属于同一消息组G1,存储顺序由M1到M4。在消费过程中,前面的消息M1、M2被消费者Consumer A1处理时,只要消费状态没有提交,消费者A2是无法并行消费后续的M3、M4消息的,必须等前面的消息提交消费状态后才能消费后面的消息。

多消费者消费顺序消息示例

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;import java.nio.ByteBuffer;
import java.util.Collections;public class FifoConsumerManyDemo {public static void main(String[] args) throws ClientException {// 用于提供:生产者、消费者、消息对应的构建类 BuilderClientServiceProvider provider = ClientServiceProvider.loadService();// 构建配置类(包含端点位置、认证以及连接超时等的配置)ClientConfiguration configuration = ClientConfiguration.newBuilder()// endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081.setEndpoints(MyMQProperties.ENDPOINTS).build();// 设置过滤条件(这里为使用 tag 进行过滤)String tag = "ORDER_CREATE";FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);// 构建消费者provider.newPushConsumerBuilder().setClientConfiguration(configuration)// 设置消费者分组.setConsumerGroup("MY_FIFO_GROUP")// 设置主题与消费者之间的订阅关系.setSubscriptionExpressions(Collections.singletonMap("MY_FIFO_TOPIC", filterExpression)).setMessageListener(messageView -> {System.out.println(messageView);ByteBuffer rs = messageView.getBody();byte[] rsByte = new byte[rs.limit()];rs.get(rsByte);if(new String(rsByte).contains("user2")) {System.out.println("consumer1 Message body:" + new String(rsByte));// 处理消息并返回消费结果。System.out.println("consumer1 Consume message successfully, messageId=" + messageView.getMessageId());}return ConsumeResult.SUCCESS;}).build();provider.newPushConsumerBuilder().setClientConfiguration(configuration)// 设置消费者分组.setConsumerGroup("MY_FIFO_GROUP")// 设置主题与消费者之间的订阅关系.setSubscriptionExpressions(Collections.singletonMap("MY_FIFO_TOPIC", filterExpression)).setMessageListener(messageView -> {System.out.println(messageView);ByteBuffer rs = messageView.getBody();byte[] rsByte = new byte[rs.limit()];rs.get(rsByte);if(new String(rsByte).contains("user2")) {System.out.println("consumer2 Message body:" + new String(rsByte));// 处理消息并返回消费结果。System.out.println("consumer2 Consume message successfully, messageId=" + messageView.getMessageId());}return ConsumeResult.SUCCESS;}).build();// 如果不需要再使用 PushConsumer,可关闭该实例。// pushConsumer.close();}}

生产者代码,请参考《RocketMQ 发送顺序消息》一章。为了方便验证,我们在示例中判断了单个用户的所有消息消费情况做打印,看看结果是否是根据单个用户顺序的。

消费者分组管理

RocketMQ 5.x 开始,除了我们前面说的 Topic 不建议自动创建外,消费者分组也不建议自动创建了。消费者分组也可以通过 mqadmin 命令来添加或删除。

关闭自动创建消费者分组

5.1.3 版本的 RocketMQ目前是默认开启字段添加主题的,可以配置 borker 参数 autoCreateSubscriptionGroup 为 false 来禁用。关于如何配置,我们会在后续文章中说明。我们建议 Topic 和 消费者分组都禁用自动创建。避免在使用上的混乱,给主题和消费者分组的管理上带来不必要的麻烦。

使用 admin tool 工具管理消费者分组

前面我们提到,消费者分组是否重要, 因为消息的负载均衡策略、重试机制、顺序消费等属性都与之相关。在 ~/stroe/config/subscriptionGroup.json 文件中,可以看到我们在顺序消息的消费者中定义的消费者分组(MY_FIFO_GROUP)信息,信息如下:

"MY_FIFO_GROUP":{"attributes":{},// 当前消费者 brokerId,-i 参数"brokerId":0,"consumeBroadcastEnable":false,// 是否是广播模式,-d 参数"consumeEnable":true, // 分组是否允许消费,-s 参数"consumeFromMinEnable":false,// 是否从最小offset开始消费,-m 参数"consumeMessageOrderly":true,// 是否顺序消费,-o 参数"consumeTimeoutMinute":15,"groupName":"MY_FIFO_GROUP","groupRetryPolicy":{ // 重试策略"type":"CUSTOMIZED"},"groupSysFlag":0,// 当消费者数量变化时是否通知其他消费者负载均衡,-a 参数"notifyConsumerIdsChangedEnable":true,"retryMaxTimes":16, // 最大重试次数"retryQueueNums":1, // 重试队列数// 如果当前broker执行消费慢,使用另一个broker的id,-w 参数"whichBrokerWhenConsumeSlowly":1 
},

Store 目录的配置,可以通过 ./mqadmin getBrokerConfig 命令查看 Broker 的相关配置中的 storePathRootDir 对应的位置。此地址默认为 ~/stroe

updateSubGroup 更新或修改订阅关系(更新或修改消费者分组信息)

查看命令选项

$> ./mqadmin updateSubGroup -husage: mqadmin updateSubGroup [-a <arg>] [--attributes <arg>] [-b <arg>] [-c <arg>] [-d <arg>] -g <arg> [-h][-i <arg>] [-m <arg>] [-n <arg>] [-o <arg>] [-p <arg>] [-q <arg>] [-r <arg>] [-s <arg>] [-w <arg>]-a,--notifyConsumerIdsChanged <arg>       notify consumerId changed--attributes <arg>                     attribute(+a=b,+c=d,-e)-b,--brokerAddr <arg>                     create subscription group to which broker-c,--clusterName <arg>                    create subscription group to which cluster-d,--consumeBroadcastEnable <arg>         broadcast-g,--groupName <arg>                      consumer group name-h,--help                                 Print help-i,--brokerId <arg>                       consumer from which broker id-m,--consumeFromMinEnable <arg>           from min offset-n,--namesrvAddr <arg>                    Name server address list, eg: '192.168.0.1:9876;192.168.0.2:9876'-o,--consumeMessageOrderly <arg>          consume message orderly-p,--groupRetryPolicy <arg>               the json string of retry policy ( exp:{"type":"EXPONENTIAL","exponentialRetryPolicy":{"initial":5000,"max":7200000,"multiplier":2}}{"type":"CUSTOMIZED","customizedRetryPolicy":{"next":[1000,5000,10000]}} )-q,--retryQueueNums <arg>                 retry queue nums-r,--retryMaxTimes <arg>                  retry max times-s,--consumeEnable <arg>                  consume enable-w,--whichBrokerWhenConsumeSlowly <arg>   which broker id when consume slowly

使用此命令,我们可以添加消费者分组,并且可以设置分组相关信息,比如-o 顺序消费,-r 最大重试次数等。-o 参数我们在《RocketMQ 发送顺序消息》一章使用了。

示例

./mqadmin updateSubGroup -n 127.0.0.1:9876 -g MY_FIFO_GROUP -o true -c DefaultCluster

-n、-c、-g 参数为必须

deleteSubGroup 从 Broker 删除订阅关系(删除消费者分组)

查看命令选项

$> ./mqadmin deleteSubGroup -husage: mqadmin deleteSubGroup [-b <arg>] [-c <arg>] -g <arg> [-h] [-n <arg>] [-r <arg>]-b,--brokerAddr <arg>     delete subscription group from which broker-c,--clusterName <arg>    delete subscription group from which cluster-g,--groupName <arg>      subscription group name-h,--help                 Print help-n,--namesrvAddr <arg>    Name server address list, eg: '192.168.0.1:9876;192.168.0.2:9876'

示例

$> ./mqadmin deleteSubGroup -n 127.0.0.1:9876 -g MY_FIFO_GROUP -c DefaultCluster

消费重试的相关问题我们将在下一章《RocketMQ 消息重试机制》中详细说明。

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

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

相关文章

IDEA最新激 20活23码

人狠话不多 大家好&#xff0c;最近Intelli Idea官方的校验规则进行了更新&#xff0c;之前已经成功激20活23的Idea可能突然无法使用了。 特地从网上整理了最新、最稳定的激20活23码分享给大家&#xff0c;希望可以帮助那些苦苦为寻找Idea激20活23码而劳累的朋友们。 本激23…

外国固定资产管理系统功能有哪些

很多公司都在寻找提高自己资产管理效益的方法。为了满足这一要求&#xff0c;国外的固定资产管理系统已经发展成多种形式。以下是国外一些常见的固定资产管理系统的特点:自动化和智能化:许多现代固定资产管理系统采用自动化和数字化技术&#xff0c;以简化流程&#xff0c;减少…

Windows本地mysql 的安装教程(一步一步进行安装)

目录 1 下载安装包2 安装 1 下载安装包 下载网址&#xff1a; https://dev.mysql.com/downloads/ 选择这个 2 安装 编写MySQL配置文件 在解压目录下新建my.ini文件 将下面文本拷贝进my,ini文件中 [mysqld] # 设置3306端口 port3306 # 设置mysql的安装目录 ----------…

18.备忘录模式(Memento)

意图&#xff1a;在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态&#xff0c;这样就可以在以后将该对象恢复到原先保存的状态。 上下文&#xff1a;某些对象的状态在转换过程中&#xff0c;可能由于某种需要&#xff0c;要求…

Qt重写QTreeWidget实现拖拽

介绍 此文章记录QTreeWidget的重写进度&#xff0c;暂时停滞使用&#xff0c;重写了QTreeWidget的拖拽功能&#xff0c;和绘制功能&#xff0c;自定义了数据结构&#xff0c;增加复制&#xff0c;粘贴&#xff0c;删除&#xff0c;准备实现动态刷新数据支持千万数据动态刷新&a…

Docker 容器数据卷

是什么 卷就是目录或文件&#xff0c;存在于一个或多个容器中&#xff0c;由docker挂载到容器&#xff0c;但不属于联合文件系统&#xff0c;因此能够绕过Union File System提供一些用于持续存储或共享数据的特性&#xff1a;卷的设计目的就是数据的持久化&#xff0c;完全独立…

[设计模式] 浅谈SOLID设计原则

目录 单一职责原则开闭原则里氏替换原则接口隔离原则依赖倒转原则 SOLID是一个缩写词&#xff0c;代表以下五种设计原则 单一职责原则 Single Responsibility Principle, SRP开闭原则 Open-Closed Principle, OCP里氏替换原则 Liskov Substitution Principle, LSP接口隔离原则 …

Linux 中的make/makefile

一&#xff1a;背景 make是一个命令工具&#xff0c;是一个解释makefifile中指令的命令工具&#xff0c;一般来说&#xff0c;大多数的IDE都有这个命令&#xff0c;比如&#xff1a;Delphi的make&#xff0c;Visual C的nmake&#xff0c;Linux下GNU的make。可见&#xff0c;mak…

【Xilinx】基于MPSoC的OpenAMP实现(一)

【Xilinx】基于MPSoC的OpenAMP实现&#xff08;一&#xff09; 一、开发环境1、开发思路2、下载官方bsp包 二、编译Linux1、配置petalinux环境变量2、创建工程3、进入目录4、设置缓存目录&#xff08;重点&#xff1a;可离线编译&#xff0c;加快编译速度&#xff09;5、配置u-…

JMeter断言之JSON断言

JSON断言 若服务器返回的Response Body为JSON格式的数据&#xff0c;使用JSON断言来判断测试结果是较好的选择。 首先需要根据JSON Path从返回的JSON数据中提取需要判断的实际结果&#xff0c;再设置预期结果&#xff0c;两者进行比较得出断言结果。 下面首先介绍JSON与JSON…

springboot01

目录 新建Maven工程&#xff0c;什么都不选 ​pom.xml加上 新建包top.cjz.controller 新建类HelloController ​新建类HelloApplication ​运行浏览器访问 新建Maven工程&#xff0c;什么都不选 pom.xml加上 <!--springboot工程需要继承的父工程--> <parent…

第六次面试、第一次复试

第六面&#xff1a; hr迟到&#xff0c;说是搞错了以为线下&#xff0c;我打电话过去才开始&#xff0c;问我想电话面还是视频&#xff0c;果断电话面 自我介绍 介绍了一下公司的工作 ................. 项目拷打&#xff1a; grpc数据如何传输的如何调用两个接口如何获取…

如何使用 Humata.ai:快速理解和总结文献

链接&#xff1a; Humata 简介 Humata.ai 是一个人工智能驱动的文献阅读助手&#xff0c;可以帮助用户快速理解和总结文献。它可以提取文献的关键信息&#xff0c;并以简洁易懂的语言生成摘要。此外&#xff0c;Humata.ai 还可以回答用户关于文献的问题&#xff0c;帮助用户…

用户与权限管理

文章目录 用户与权限管理1. 用户管理1.1 MYSQL用户1.2 登录MySQL服务器1.3 创建用户1.4 修改用户1.5 删除用户1.6 修改密码1. 修改当前用户密码2. 修改其他用户密码 1.7 MYSQL8密码管理 2. 权限管理2.1 权限列表2.2 授予权限的原则2.3 授予权限2.4 查看权限2.5 收回权限 3. 权限…

python快速实现带界面可点击的简易计算器

这篇文章将带你探索如何使用Python创建一个直观且实用的带界面计算器。我们将深入介绍如何利用Python的图形用户界面库&#xff0c;特别是Tkinter&#xff0c;来构建一个友好的用户界面&#xff0c;让你能够轻松进行数学运算。无论你是初学者还是有一定编程经验&#xff0c;本文…

基于Face++网络爬虫+人脸融合算法智能发型推荐程序——深度学习算法应用(含Python及打包exe工程源码)+爬虫数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境Pycharm 环境 模块实现1. Face.APl调用1&#xff09;Face.APl介绍2&#xff09;调用API 2. 数据爬取1&#xff09;网络数据爬取步骤2&#xff09;爬虫实现 3. 模型构建4. 用户界面设计1&#xff09;需要调用的库文…

竞赛 基于深度学习的人脸表情识别

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的人脸表情识别 该项目较…

学习记忆——宫殿篇——记忆宫殿——记忆桩——单间+客厅+厨房+厕所+书房+院子

文章目录 单间客厅厨房厕所书房院子 单间 水壶 水龙头 香皂 果汁机 电视 门空间 花 红酒 葡萄 不锈钢 白毛沙发 彩色垫子 吉他 皮椅 挂画 风扇 糖抱枕 盒子 花土 水晶腿 衣柜 笔 三环相框 水壶 壁挂 台灯 被 网球拍 足球 抽屉 闹钟 蝴蝶 心 斑马 三轮车 音响 椅子 碗 玩偶 烟灰…

【记录】Python 之于 C/C++ 区别

记录本人在 Python 上经常写错的一些地方&#xff08;C/C 写多了&#xff0c;再写 Python 有点切换不过来&#xff09; 逻辑判断符号用 and、or、!可以直接 10 < num < 30 比较大小分支语句&#xff1a;if、elif、else使用 、-&#xff0c;Python 中不支持 、- - 这两个…

BOA服务器移植

BOA服务器移植 1、源码下载 http://www.boa.org/ News! (last updated 23 February 2005) Latest Released Version (0.94.13) here (signature here) --- 下载地址1.1 boa简介&#xff1a; 其可执行代码只有大约60KB左右&#xff0c;Boa是一个单任务的HTTP服务器&#xff…