模拟RabbitMQ实现消息队列【项目】

news2024/9/25 11:20:03

文章目录

  • 1. 项目介绍
      • 什么是RabbitMQ?
  • 2. 开发环境
  • 3. 技术选型
      • 3.1ProtoBuf使用介绍:
      • 3.2 Muduo库
      • 3.3 SQLite3
          • 什么是SQLIte?
          • 为什么要用SQLite?
      • 3.4 Gtest
          • 什么是Gtest
  • 4. 需求分析
      • 4.1 核心概念
      • 4.2 核心API
      • 4.3 交换机类型
      • 4.4 持久化
      • 4.5 网络通信
      • 4.6 消息应答
  • 5. 模块划分
    • 5.1 服务端模块
      • 5.1.1 持久化数据管理中心模块
        • 1.交换机管理:
        • 2. 队列管理:
        • 3. 绑定管理:
        • 4. 消息管理:
      • 5.1.2 虚拟机管理模块
        • 1. 虚拟机管理信息:
        • 2. 虚拟机对外操作:
        • 3. 虚拟机管理操作(暂未实现):
      • 5.1.3 交换路由模块
      • 5.1.4 消费者管理模块
      • 5.1.5 信道管理模块
      • 5.1.6 连接管理模块
      • 5.1.7 Broker服务器管理模块
    • 5.2 客户端模块
      • 5.2.1 消费者管理
      • 5.2.2 信道请求模块
      • 5.2.3 通信连接模块
    • 5.3 项目模块关系图
  • 6. 项目效果简单演示
  • 7. 总结

1. 项目介绍

什么是RabbitMQ?

​RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。

主要用途:

  • 异步处理无需即时返回且耗时的操作,提高系统吞吐量。
  • 解耦生产者和消费者,提高系统灵活性。
  • 实现分布式系统的集成。

RabbitMQ实际上是实现了一个基于AMQP的生产者消费者模型。生产者消费者模型是后端开发的常用编程方式,它有诸多好处:

  • 解耦合。
  • 并发处理。
  • 支持忙闲不均。
  • 削谷填峰等。

在实际的后端开发中,尤其是分布式系统里,跨主机之间使用生产者消费者模型,也是很普遍的需求。因此我们以AMQP为核心封装一个独立的服务器程序。这样的服务程序我们就称为消息队列(Message Queue)。市面上成熟的消息队列有很多:

  • Rabbit
  • Kafka
  • RocketMQ
  • ActiveMQ等

2. 开发环境

  • Linux(ubuntu-22.04)
  • VSCode
  • g++/gdb
  • Makefile

3. 技术选型

  • 主开发语言:C++
  • 序列化框架:ProtoBuf二进制序列化
  • 网络通信:自定义应用层协议 + muduo库(对长TCP长连接的封装,并使用epoll的事件驱动模式实现高并发服务器与客户端)
  • 数据管理数据库:SQLite3
  • 单元测试框架:Gtest

3.1ProtoBuf使用介绍:

ProtoBuf使用流程

3.2 Muduo库

Muduo是由陈硕大佬开发,基于非阻塞IO和事件驱动的C++高并发TCP网络编程库。它是一款基于主从Reactor模型的网络库,使用的线程模型是one loop per thread,所谓one loop per thread指的是:

  • 一个线程只能有一个事件循环(EventLoop),用于响应计时器和IO事件。
  • 一个文件描述符只能由一个线程进行读写,也就是说一个TCP连接必须归属于某个EventLoop管理。

Reactor模型

3.3 SQLite3

什么是SQLIte?

SQLite是一个进程内的轻量级数据库,它实现了自给自足的、无服务器的、零配置的、事务性的SQL数据库引擎。我们不需要再系统中配置。SQLite引擎不是一个独立的进程。可以按应用程序需求进行静态或动态链接。SQLite直接访问其存储文件。

为什么要用SQLite?
  • 不需要一个单独的服务器进程或操作的系统(无服务器的)。
  • SQLite不需要配置。
  • 一个完整的SQLite数据库存储在一个单一的跨平台的磁盘文件。
  • SQLite非常小,是轻量级的。完全配置时小于400KiB,省略可选功能时小于250KiB。
  • SQLite自给自足,不需要任何外部的依赖
  • SQLite事务完全兼容ACID,允许从多个进程或线程安全访问。
  • SQLite支持SQL92标准的大多数查询语言功能。
  • SQLite使用ANSI-C编写,并提供了简单和易于使用的API
  • SQLite可在UNIX(Linux,Mac OS-X,Android,iOS)和Windows(Win32,WinCE,WinRT)中运行。

3.4 Gtest

什么是Gtest

Gtest是一个跨平台的C++单元测试框架,由google公司发布。gtest是为了在不同平台上为编写C++单元测试而生成的。提供了丰富的断言、致命和非致命、参数化等等。

4. 需求分析

4.1 核心概念

  • 生产者(Producer)
  • 消费者(Consumer)
  • 中间人(Broker)
  • 发布(Publish)
  • 订阅(Subscribe)
  • 一个生产者,一个消费者
    1:1
  • N个生产者,N个消费者
    N:N
    其中Broker Server为核心部分,负责消息的存储和转发。
    而在AMQP模型中,也就是消息中间件服务器Broker中,又存在以下概念:
  • 虚拟机(VirtualHost):类似于MySQl的“database”,是一个逻辑上的集合。一个BrokerServer上可以存在多个VirtualHost。但我们这里为了简化,只提供一个VirtualHost。
  • 交换机(Exchange):生产者先把消息发送到Broker中的Exchange上,再根据不同的匹配规则,把消息转发给不同的Queue。
  • 队列(Queue):真正用来存储消息的部分,每个消费者自己决定从哪个Queue上读取消息。
  • 绑定(Binding):Exchange和Queue之间的关联关系,两者可理解为“多对多”关系,使用一个关联表就可以把这两个概念关联起来。(一个Exchange可以绑定多个Queue,一个Queue也可以被多个Exchange绑定)
  • 消息(Message):传递的内容。
    在这里插入图片描述

简单总体模型
上述数据结构,既需要再内存中存储,也需要在硬盘中存储

  • 内存存储:方便使用
  • 硬盘存储:重启数据不丢失

4.2 核心API

对于Broker来说,要通过以下核心API实现消息队列的基本功能。

  1. 创建交换机(DeclareExchange)
  2. 销毁交换机(DeleteExchange)
  3. 创建队列(DeclareQueue)
  4. 销毁队列(DeleteQueue)
  5. 创建绑定(Bind)
  6. 解除绑定(UnBind)
  7. 发布消息(BasicPublish)
  8. 订阅消息(BasicConsume)
  9. 确认消息(BasicAck)
  10. 取消订阅(BasicCancel)

而Producer和Consumer则通过网络远程调用这些API,实现生产者消费者模型。

4.3 交换机类型

对于RAbbitMQ,主要支持四种交换机类型:

  • Direct
  • Fanout
  • Topic
  • Header

其中Header比较复杂且少见。常用的是前三种类型,项目中也主要实现这三种:

  • Direct:生产者发送消息时,直接指定该交换机绑定的队列名。
  • Fanout:生产者发送的消息会被复制到该交换机所有队列中,也就是广播。
  • Topic:队列与交换机绑定时,指定一个字符串bindingKey,发送的消息里有指定字符串routingKey。当routingKey与bindingKey满足一定的匹配条件时,把消息投递到对应队列。

4.4 持久化

Exchange、Queue、Binding、Message等数据都有持久化需求,当程序重启 / 主机重启,保证上述内容不丢失。

4.5 网络通信

生产者和消费者都是客户端程序,Broker是服务端程序,通过网络进行通信。
在通信过程中,客户端要提供对应的API实现对服务器的操作。

  1. 创建Connection
  2. 关闭Connection
  3. 创建Channel(OpenChannel)
  4. 关闭Channel(CloseChannel)
  5. 创建交换机(DeclareExchange)
  6. 销毁交换机(DeleteExchange)
  7. 创建队列(DeclareQueue)
  8. 销毁队列(DeleteQueue)
  9. 创建绑定(QueueBind)
  10. 解除绑定(QueueUnBind)
  11. 发布消息(BasicPublish)
  12. 订阅消息(BasicConsume)
  13. 确认消息(BasicAck)
  14. 取消订阅(BasicCancel)

在Broker的基础上,还要增加Connection操作和Channel操作:

  • Connection对应一个TCP连接
  • Channel是Connection中的逻辑通道

一个Connection中可以包含多个Channel(一个Connection能被多个Channel使用)。Channel和Channel之间数据独立,不会互相干扰。这样能更好地复用TCP连接,达到长连接的效果,避免频繁创建关闭TCP连接。

4.6 消息应答

被消费的消息,需要消费者客户端进行应答。应答模式分为两种:

  • 自动应答:消费者只要消费了消息,就算应答完毕。Broker直接删除这个消息。
  • 手动应答:消费者手动调用应答接口,Broker收到应答请求后,才真正删除这个消息(未应答时,消息位于待确认队列,没有被真正删除)。

手动应答目的是为了保证消费者处理成功了,在一些对数据可靠性要求较高的场景比较常见。

5. 模块划分

5.1 服务端模块

5.1.1 持久化数据管理中心模块

在数据管理模块中管理交换机、队列、队列绑定、消息等数据。

1.交换机管理:
  • 管理信息:名称、类型(Direct等)、是否持久化标志、是否(无人使用时)自动删除标志、其它参数(作为扩展)。
  • 管理操作:恢复历史数据、声明、删除、获取、判断是否存在。
2. 队列管理:
  • 管理信息:名称、是否持久化标志、是否独占标志、是否(无人使用时)自动删除标志、其它参数。
  • 管理操作:恢复历史数据、声明、删除、获取、判断是否存在。
3. 绑定管理:
  • 管理信息:交换机名称、队列名称、绑定主题(bindingKey)。
  • 管理操作:恢复历史数据、绑定、解绑、解除交换机关联绑定信息、解除队列关联绑定信息、获取交换机关联绑定信息。、获取指定绑定信息等。
4. 消息管理:
  1. 管理信息:
  • 属性:消息ID、路由主题(routingKey)、持久化模式标志
  • 消息内容
  • 有效标志(配合持久化需要)
  • 持久化位置(消息在文件中的偏移量)
  • 持久化消息长度(该消息存储在文件中的长度)
  1. 管理操作:恢复历史消息、向指定队列新增消息、获取指定队列队首消息、确认移除消息。

这几个概念数据都要在内存和硬盘中存储。

  • 以内存存储为主,保证快速查找信息进行处理。
  • 以硬盘存储为辅,保证服务器重启后,之前的信息可以正常保持。

5.1.2 虚拟机管理模块

因为交换机、队列、绑定、都是以虚拟机为单元整体进行操作。因此虚拟机是对以上数据管理模块的整合模块。

1. 虚拟机管理信息:
  • 交换机数据管理模块句柄
  • 队列数据管理模块句柄
  • 绑定数据管理模块句柄
  • 消息数据管理模块句柄
2. 虚拟机对外操作:
  • 提供虚拟机内交换机声明,交换机删除操作(删除时同时需要删除交换机关联的绑定信息)
  • 提供虚拟机队列声明、队列删除操作(删除时同时需要删除队列关联的绑定信息以及消息管理)
  • 提供虚拟机内交换机 - 队列绑定、解绑操作
  • 获取交换机相关绑定信息
  • 发布消息、消费消息、确认消息等
3. 虚拟机管理操作(暂未实现):
  • 创建虚拟机
  • 查询虚拟机
  • 删除虚拟机

5.1.3 交换路由模块

当客户端发送一条消息到交换机后,这条消息应该转发给该交换机绑定的哪些队列中?

由交换路由模块决定的。绑定信息中有bindingKey,而每条发布的消息中有routingKey。能否入队取决于两个要素:交换机类型和Key

  • 直接交换(Direct):将消息入队到绑定信息中bindingKey与消息routingKey一致的队列中
  • 广播交换(Fanout):将消息入队到该交换机的所有绑定队列中
  • 主题交换(Topic):将消息入队到绑定信息中bindingKey与消息routingKey匹配成功的队列中

bindingKey
由数字字母下划线构成,并使用 . 分成若干部分。
如: news.music.# 用于表示交换机绑定的当前队列是一个用于发布音乐新闻的队列。
支持 *# 两种通配符,但 * # 只能作为 . 切分出来的单独部分,不能和其他数字字母混用。如:

  • __a.*.b__是合法的,而 a.*a.b 是不合法的
  • * 可以匹配任意一个单词(注意不是字母)
  • # 可以匹配任意零个或多个单词(注意不是字母)
  • 注意: *# 不能相邻,因为 # 完全可以替代 * 的功能

routingKey
由数字字母下划线构成,并且可以使用 . 划分成若干部分。如:

  • news.music.pop ,表示当前发布的消息是一个流行音乐的新闻。

5.1.4 消费者管理模块

消费者管理是以队列为单元的,因为每个消费者都会在开始的时候订阅一个队列的消息,当队列中有消息后,会将消息轮询推送给订阅该队列的消费者(负载均衡)。
因此操作流程为:从队列关联的消息管理中取出消息,从队列关联的消费者中取出一个消费者,将消息推送给消费者。

  1. 消费者信息:
  • 消费者标识tag
  • 订阅队列名称
  • 自动应答标志(决定一条消息推送给消费者后,是否需要等待收到确认后再删除消息)
  • 消息处理回调函数指针(一个消息发布后被Push到线程池,调用传入Push的事件处理函数,函数内部选择队列关联的消息和消费者,接着调用消费者的消息处理回调函数将消息发送给消费者客户端)
void(const std::string &tag, const BasicProperties& bp, const std::string &body)
  1. 消费者管理:添加、删除、获取指定队列消费者,移除队列所有消费者等操作。

5.1.5 信道管理模块

在AMQP模型中,除了通信连接Connection概念外,还有一个Channel概念。Channel是针对Connection连接的一个更细粒度的通信信道,多个Channel可以使用同一个Connection进行通信,同时一个Connection之间的Channel之间互相独立。

信道模块是再次将上述模块进行整合提供服务的模块。

  1. 管理信息:
  • 信道ID
  • 信道关联的消费者 / 生产者
  • 信道关联的连接
  • 信道关联的虚拟机
  • 工作线程池(一条消息被发布到队列后,需要将消息推送给订阅了该队列的消费者,该工作由线程池完成)
  1. 管理操作:
  • 交换机的声明 / 删除
  • 队列的声明 / 删除
  • 交换机 - 队列的绑定 / 解绑
  • 消息的订阅 / 取消订阅
  • 消息的发布 / 确认消息

5.1.6 连接管理模块

本项目的服务器是通过muduo库实现底层通信的,muduo库并不能提供我们所需的所有操作,我们需要连接管理模块实现对muduo库的二次封装(同时对信道管理模块的封装),以完成我们的需求。

  • 管理信息:连接关联的信道管理句柄、连接关联的muduo库Connection、信道管理所需的模块句柄。
  • 管理操作:新增、删除、获取连接,打开 / 关闭信道

5.1.7 Broker服务器管理模块

综合以上所有模块,搭建网络通信服务器,实现与客户端的网络通信,识别客户端的请求并提供请求处理服务。

  • 管理信息:虚拟机、消费者、连接管理句柄,工作线程池句柄,muduo库通信所需元素。
  • 提供服务:综合打开 / 关闭信道、消息订阅 / 取消订阅等请求处理接口。

5.2 客户端模块

5.2.1 消费者管理

消费者在客户端存在感比较薄弱,在用户使用角度中,只要创建一个信道,就可以通过信道完成所有操作。对于消费者的感官更多的是在订阅时传入了一个消费者标识。尤其是本项目的简单实现是一个信道只能订阅一个队列,也就是说一个信道只能创建一个消费者,一一对应更弱化了消费者的存在。

  • 消费者信息:消费者标识、订阅队列名称、自动应答标志、消息处理回调函数

5.2.2 信道请求模块

与服务端信道类似,客户端也有Channel的概念。

  1. 信道管理信息:
  • 信道ID
  • 信道关联的连接
  • 信道关联的消费者
  • 请求对应的消息响应队列(这里使用hash表,以快速查找指定相应)
  • 互斥锁&条件变量(大部分的请求都是阻塞操作,发送请求后需要等到响应后才能继续。但muduo库的通信是异步的,因此需要我们在收到响应后,通过判断是否是等待的指定响应来进行同步)

信道管理操作:

  • 信道的创建 / 删除
  • 交换机的声明 / 删除
  • 队列的声明 / 删除
  • 交换机 - 队列的绑定 / 解绑
  • 添加 / 取消订阅
  • 消息的发布 / 确认等

5.2.3 通信连接模块

向用户提供一个实现网络通信的Connection对象,内部可创建更细粒化的Channel对象与服务器通信。

  1. 管理信息:
  • 连接关联的实际用于通信的muduo::net::Connection连接对象
  • 连接关联的信道管理句柄(实现信道的增删查)
  • 连接关联的Event Loop异步循环工作线程
  • 异步工作线程池(对服务器发来的消息进行处理)
  1. 操作管理:
  • 管道的创建 / 删除

5.3 项目模块关系图

在这里插入图片描述

6. 项目效果简单演示

  1. 启动服务器
    在这里插入图片描述
  2. 发布客户端发布自定义消息(字符串或任务,这里简单起见使用字符串)
    在这里插入图片描述
  3. 消费者客户端进行消费(命令行指定要订阅的队列)
    在这里插入图片描述

7. 总结

本项目模拟RabbitMQ实现简化版的消息队列组件,内部实现了消息队列服务器以及客户端的搭建,并支持不同主机间消息的发布、订阅以及消息推送功能。
本篇博客,简单搭建了项目总体框架,框架难度不算高,难点在于要理清各数据结构间的关系以及回调函数较为复杂。
项目链接:项目源码

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

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

相关文章

UVa1389/LA3709 Hard Life

UVa1389/LA3709 Hard Life 题目链接题意输入格式输出格式 分析AC 代码 题目链接 本题是2006年icpc欧洲区域赛东北欧赛区的H题 题意 约翰是一家公司的CEO。公司的股东决定让他的儿子斯科特成为公司的经理。约翰十分担心,儿子会因为在经理岗位上表现优异而威胁到他CE…

IPv4地址学习

今天学习了IPv4,做下学习笔记: 什么是IPv4? IPv4地址是网络层地址,用于标识网络中的每个节点。 什么是子网?什么是主类子网划分? 我们将IP地址划分为网络位和主机位 一个地址为192.168.1.2/24&#xff…

【HTML】置换元素(替换元素)

● 它的内容不是由元素的标签内的内容决定的,而是由元素的属性决定的 ● 可以通过CSS设置宽度和高度。 常见的置换元素主要包括以下几种: <img> 元素:用于嵌入图像,通过 src 属性指定图像的路径。例如:<img src="example.jpg" alt="示例图片&quo…

场景感知技术带您重塑未来生活的新篇章

在科技日新月异的今天&#xff0c;场景感知技术正以前所未有的速度渗透到我们生活的方方面面&#xff0c;成为连接物理世界与数字世界的桥梁&#xff0c;重塑着人类的认知方式与生活体验。这项技术通过综合运用传感器、大数据分析、人工智能等前沿科技&#xff0c;实现对周围环…

C++多态 学习笔记(上)

本文涉及的指针都是 4bytes 。 如果要其他平台下&#xff0c;部分代码需要改动。比如&#xff1a;如果是 x64 程序&#xff0c;则需要考虑指针是 8bytes 问题 等等。 什么是多态&#xff1f; 举个例子&#xff1a;比如 买票这个行为 &#xff0c;当 普通人 买票时&#xff0c;…

短剧APP系统搭建,短剧市场的发展机遇

短剧作为近几年内发展快速的行业&#xff0c;一直深受大众的欢迎&#xff0c;各种让观众上头的短剧层出不穷&#xff0c;深深吸引着大众。短剧的巨大发展前景也吸引了大量资本涌入市场&#xff0c;目前&#xff0c;短剧入局者也都获得了不菲的经济收益&#xff01; 随着短剧行…

C++下标+【】、迭代器、范围for、迭代器对于其他容器都是通用的、迭代器可以更好的跟算法配合、rbegin和rend函数、const修饰的迭代器等的介绍

文章目录 前言一、 下标 【】二、 迭代器1.begin2. end3. 使用迭代器遍历string类对象 三、范围for(语法糖)五、迭代器对于其他容器都是通用的六、迭代器可以更好地跟算法配合七、 rbegin 和 rend函数八、 const 修饰的迭代器总结 前言 C下标【】、迭代器、范围for、迭代器对…

JavaEE---Spring MVC(5)

MVC学习小案例3 留言板案例 后端代码 测试 点击刷新的时候页面的这些记录仍在 一个小tips 我们在日常中写的时候会经常写到get和set方法,这会使整个代码看起来非常多不好看,这里我们引入一个新的依赖解决这个问题 引入LomBok依赖 那要是个别情况下我们不想获取他的ge…

高集成度双通道差分式电容型传感芯片-MC11

工采电子代理的MC11S、MC11T是一款高集成度双通道电容型传感芯片&#xff0c;芯片直接与被测物附近的差分电容极板相连&#xff0c;通过谐振激励并解算测量微小电容的变化。激励频率在0.1~20MHz范围内可配置&#xff0c;其频率测量输出为16bit数字信号&#xff0c;对应的电容感…

Ventoy启动盘制作

然后直接将系统的ISO镜像直接拷贝进去&#xff0c;就能直接使用

69页PPT全面预算管理体系的框架与落地

一、明确企业战略目标企业战略目标是预算指标体系确立的根本出发点。它为预算指标的设定提供了方向和指导。 深入分析企业长期发展规划 企业需要对自身的长期发展规划进行全面、深入的分析。这包括对市场趋势、行业竞争态势、技术发展方向等外部环境因素的研究&#xff0c;以…

AI技术颠覆游戏开发:谷歌DeepMind GameNGen实时生成《DOOM》探秘

引言 近年来&#xff0c;生成式人工智能&#xff08;AIGC&#xff09;在图像和视频生成领域取得了巨大突破。然而&#xff0c;谁能想到&#xff0c;这项技术正逐渐渗透进游戏开发领域&#xff0c;且潜力巨大。2023年8月29日&#xff0c;谷歌DeepMind发布了名为《扩散模型是实时…

【舍入,取整,取小数,取余数丨Excel 函数】

数学函数 1、Round函数 Roundup函数 Rounddown函数 取整&#xff1a;(Int /Trunc)其他舍入函数&#xff1a; 2、Mod函数用Mod函数提取小数用Mod函数 分奇偶通过身份证号码判断性别 1、Round函数 Roundup函数 Rounddown函数 Round(数字&#xff0c;保留几位小数)&#xff08;四…

解除网站禁用右键 解除禁用选择方法 并允许复制

限制我复制&#xff0c;太恶心了&#xff0c;别用技术作恶&#xff01;&#xff01;&#xff01; 一般HTML网站禁止右键选择的方法 <body ondragstart"return false" oncontextmenu"return false" onselectstart"return false">解除网站…

遇到“msvcp120.dll丢失”的错误提示?来看看msvcp120.dll丢失的解决方法都有哪些?

遇到“msvcp120.dll丢失”的错误提示可能会让人感到焦虑&#xff0c;尤其是当你尝试运行某个应用程序或游戏时突然接收到这样的消息。​msvcp120.dll​是Microsoft Visual C 2013 Redistributable Package 中的一个文件&#xff0c;主要负责C标准库中的功能&#xff0c;比如输入…

C++第四十六弹---解锁多线程编程的奥秘:<thread>库深入探索

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1 线程库 1.1 thread类的简单介绍 1.2 线程函数参数 1.3 原子性操作库(atomic) 1.4 lock_guard与unique_lock 1.4.1 mutex的种类 1.4.2 loc…

python-小理的三角形

题目描述 小理有一个数组长度大小为 n &#xff0c;数组中有 n 个正整数。 现在小理请你从其中选出三个元素&#xff08;注意选择元素的下标不能相同&#xff0c;但是其值可以相同&#xff09;组成一个三角形。 无法做到&#xff0c;请输出一行一个字符串"No solution&quo…

SQL进阶技巧:每年在校人数统计 | 区间重叠问题

目录 0 问题分析 1 数据准备 2 问题分析 3 小结 区间重叠问题 0 问题分析 有一个录取学生人数表 in_school_stu&#xff0c;记录的是每年录取学生的人数及录取学生的学制&#xff0c;计算每年在校学生人数。 1 数据准备 create table in_school_stu as ( select stack(5,…

【平渊网络】副业项目拆解:视频借鉴式搬运项目 | 搞笑视频跨平台 “借鉴式” 搬运项目思路 | 抖音防查重机制基础

目录 项目介绍 实操&#xff1a;账号准备 素材制作教学 防查重机制基础 项目介绍 只分享实操干货&#xff0c;不浪费时间。如果你没有2台手机&#xff0c;或者手机不支持分身就不用往下读了&#xff0c;这个项目大概率是做不出流量的。 项目是情感聊天对话的变种&#xff…

C++ DLL DEMO

头文件dlltest.h #pragma once #include "pch.h" #include <iostream> #include <fstream> #include <iomanip> #include <string> #include <bitset>extern "C" __declspec(dllexport) void debugService(uint32_t debugF…