仿RabbitMQ实现消息队列———整体框架

news2024/11/26 14:25:37

目录

一、项目简介

需求分析

AMQP 特点:

AMQP 模型:

交换机类型

持久化

网络通信

二、服务端模块

1、交换机数据管理

2、队列数据管理

3、绑定数据管理

4、消息数据管理

5、虚拟机数据管理

6、路由匹配管理

7、消费者管理

8、信道管理

9、连接管理

10、服务器模块

三、客户端模块

1、消费者管理

2、信道管理

3、连接管理

4、异步线程池模块


一、项目简介

        在实际的后端开发中, 尤其是分布式系统⾥, 跨主机之间使⽤⽣产者消费者模型, 也是⾮常普遍的需求。因此, 我们通常会把阻塞队列封装成⼀个独⽴的服务器程序, 并且赋予其更丰富的功能。 这样的服务程 序我们就称为 消息队列 (Message Queue, MQ)。
        其中 RabbitMQ 是⼀个⾮常知名、功能强⼤且⼴泛使⽤的消息队列。本项目就是仿照RabbitMQ模拟实现一个简单的消息队列。

需求分析

⽣产者 (Producer)
消费者 (Consumer)
中间⼈ (Broker)
发布 (Publish)
订阅 (Subscribe)
我们需要实现的内容包括:
1.broker服务器:消息队列代理服务器
2.消息发布客户端:向服务器发布消息(生产者)
3.消息订阅客户端:从服务器订阅消息(消费者)
我们的消息队列是基于对AMQP协议的理解进行的整合,那么什么是AMQP协议呢:
         AMQP (Advanced Message Queuing Protocol) 是一种网络协议,主要用于消息中间件之间的异步通信。AMQP 提供了一种标准的方式来发送和接收消息,使得不同厂商的消息中间件可以互相操作。

AMQP 特点:

  • 开放标准:AMQP 是一个开放标准,这意味着它的规范是公开的,并且任何人都可以实现它。
  • 二进制协议:AMQP 使用二进制编码,这使得它比基于文本的协议更高效。
  • 可靠性:AMQP 设计为确保消息的可靠传输,包括确认机制、事务支持等。
  • 灵活性:AMQP 支持多种消息路由模式,如点对点 (point-to-point) 和发布/订阅 (publish/subscribe)。
  • 互操作性:AMQP 允许不同厂商的消息中间件互相通信,不受客户端或中间件使用的编程语言的影响。

AMQP 模型:

  • Broker (消息代理):这是消息中间件的核心组件,负责接收、存储和转发消息。
  • Exchange (交换器):Exchange 接收来自生产者的消息,并根据配置规则将消息发送到一个或多个队列。
  • Queue (队列):队列是用来存储消息的数据结构,直到消费者取走这些消息。
  • Binding (绑定):绑定定义了 Exchange 和 Queue 之间的关系,确定消息如何从 Exchange 到达 Queue。所谓的 Exchange 和 Queue 可以理解成 "多对多" 关系, 和数据库中的 "多对多" ⼀样. 意思是: ⼀个 Exchange 可以绑定多个 Queue (可以向多个 Queue 中转发消息) ⼀个 Queue 也可以被多个 Exchange 绑定 (⼀个 Queue 中的消息可以来⾃于多个 Exchange)
  • Producer (生产者):生产者是向消息中间件发送消息的应用程序。
  • Consumer (消费者):消费者是从消息中间件接收消息的应用程序。
总结下来就是这样一张图

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

1.创建交换机

2.销毁交换机

3.创建队列

4.销毁队列

5.创建绑定

6.解除绑定

7.发布消息

8.订阅消息

9.取消订阅

10.确认消息

生产者消费者则通过网络发送请求来调用这些API,实现生产者消费者模型

交换机类型

本项目实现三种交换机类型,也是最常见的:

Direct: ⽣产者发送消息时, 直接指定被该交换机绑定的队列名
Fanout: ⽣产者发送的消息会被复制到该交换机的所有队列中
Topic: 绑定队列到交换机上时, 指定⼀个字符串为 bindingKey。发送消息指定⼀个字符串 routingKey。当 routingKey 和 bindingKey 满⾜⼀定的匹配条件的时候, 则把消息投递到指定队列

持久化

交换机,队列,绑定,消息都是需要持久化的,我们需要根据持久化来保证在程序或主机重启时数据不会丢失。在项目中我们使用了Sqlite数据库来进行本地的轻量级存储

网络通信

⽣产者和消费者都是客⼾端程序, Broker 则是作为服务器,通过⽹络进⾏通信。
我们在broker的基础上,再加上建立连接和打开信道的操作,这样 可以更好地复用TCP连接,达到长连接的效果,避免频繁的创建关闭TCP连接。

二、服务端模块

1、交换机数据管理

        交换机数据管理就是描述了交换机应该有哪些数据

        我们可以设置交换机的类型以及消息的基本属性,基本结构如下:

syntax ='proto3';
package bitmq;
enum ExchangeType
{
    UNKNOWTYPE=0;
    DIRECT=1;
    FANOUT=2;
    TOPIC=3;
};

enum DeliverMode
{
    UNKNOWMODE=0;
    UNDURABLE=1;
    DURABLE=2;
};

message BasicProperties
{
    string id=1;
    DeliverMode delivery_mode=2;
    string routing_key=3;
};
message message
{
    message Payload
    {
        BasicProperties properties=1;
        string body=2;
        string valid=3;
    };
    Payload payload=1;
    uint32 offset=2;
    uint32 length=3;
};

1、交换机的名称:也是交换机的唯一标识

2、交换机的类型:决定了消息的转发方式(三种 )

每个队列与交换机绑定信息中有binding_key,每条消息中有routing_key

        1.直接交换:binding_key与routing_key相同时,将消息放入队列

        2.广播交换:交换机绑定的所有队列都放入消息

        3.主题交换:根据具体的匹配算法,将符合匹配条件的binding_key和routing_key对应的队列放入消息。

3、持久化标志:决定当前交换机的数据是否需要持久化存储

4、自动删除标志:如果关联该交换机的客户端都退出了,是否需要自动删除交换机。

5、交换机的其他参数,在本项目中未具体使用。

 对交换机的管理:

1、创建交换机:如果已存在就直接成功,不存在就创建(后续的一些结构也是如此操作)

//声明交换机
        bool declareExchange(const std::string &name,ExchangeType type,bool durable,bool auto_delete,
        const google::protobuf::Map<std::string,std::string> &args)
        // bool declareExchange(const std::string &name,ExchangeType type,bool durable,bool auto_delete,
        // const std::unordered_map<std::string,std::string> &args)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it=_exchanges.find(name);
            if(it!=_exchanges.end())
            {
                //如果交换机已经存在,那么直接返回,不需要新增
                return true;
            }
            auto exp=std::make_shared<Exchange>(name,type,durable,auto_delete,args);
            if(durable==true)
            {
                //若为持久化,则添加到数据库中
                bool ret=_mapper.insert(exp);
                if(!ret) return ret;
            }
            //添加到交换机表中
            _exchanges.insert(std::make_pair(name,exp));
            return true;
        }

2、删除交换机:每个交换机都会绑定一个或多个队列,所以在删除前也要删除掉所有相关的绑定信息,若交换机为持久化,也要在数据库中删除,后续结构也是类似操作。

//删除交换机
        void deleteExchange(const std::string &name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it=_exchanges.find(name);
            if(it==_exchanges.end())
            {
                return;
            }
            if(it->second->durable==true) _mapper.remove(name);
            _exchanges.erase(name);
        }

3、获取指定名称交换机

4、获取当前所有交换机数量

2、队列数据管理

队列数据管理需要管理的数据

1、队列名称

2、持久化存储标志:决定是否将队列持久化存储起来,重启后队列是否依旧存在

3、是否独占标志:如果独占,那么只有当前客户端可以订阅该队列消息

4、自动删除标志:当订阅了当前队列的客户端退出后,是否删除队列。本项目中未实现

5、其他参数

队列管理类(与交换机管理类似):

1、创建队列

2、删除队列

3、获取指定队列信息

4、获取队列数量

5、获取所有队列的名称:因为系统重启会重新加载数据,消息是以队列为单元存储在文件中的,所以加载消息需要知道队列的名称,因为在存储消息时,存储文件以队列名称进行取名

3、绑定数据管理

绑定数据管理:描述队列与交换机的绑定信息

管理的数据

1、交换机的名称

2、队列的名称

3、对应的binding_key:绑定密钥,在交换机主题交换和直接交换时要用到。

由数字,字符,_,#, . ,  *  组成,比如news.sport.# 就可以与news.sport.football匹配成功

管理的操作:

1、添加绑定信息

2、解除绑定信息

3、获取交换机所有的相关绑定信息:

        1、在删除交换机的时候要删除相关的绑定信息    

        2、交换机也要通过这些信息来发布到指定的队列

4、获取队列所有的绑定信息:

        1、删除队列的时候,也要删除相关的绑定信息

5、获取绑定信息的数量

4、消息数据管理

消息的基本属性

message BasicProperties
{
    string id=1;
    DeliverMode delivery_mode=2;
    string routing_key=3;
};
message message
{
    message Payload
    {
        BasicProperties properties=1;
        string body=2;
        string valid=3;
    };
    Payload payload=1;
    uint32 offset=2;
    uint32 length=3;
};

消息属性:

1、消息ID:唯一标识

2、持久化标志(同队列与交换机)

3、 routing_key:决定了要发布的队列,交换机根据交换类型与binding来匹配

4、消息主体

以下是服务端在管理消息时添加的信息:

存储偏移量:消息以队列为单元存储在文件中,这个偏移量是相对于文件起始位置的偏移量

消息长度:从偏移量位置取出指定长度的消息。解决粘包问题

是否有效标志:标识当前消息是否被删除,因为如果删除消息就进行一次文件读写比较耗费资源,那么我们的策略是(删除一条消息不会将后面的数据拷贝到前边,而是重置了标志位valid,每次删除消息后判断:如果有效消息的数量占消息总数比例不到50%,且数据量超过2000,则进行垃圾回收,重新整理文件系统,当系统重启,也只需要加载有效消息。

消息的管理

以队列为单元进行管理,因为对于消息的所有操作是以队列为单元的

管理数据:

1、消息链表:保存所有待推送的消息

2、待确认hash:消息推送给客户后,会等待客户端确认,收到确认后,才会删除消息。

3、持久化hash:假设消息都需要持久化,操作过程中会垃圾回收,但是垃圾回收会改变存储位置,内存中消息的存储位置也需要改变,要用新位置去更新持久化数据

垃圾回收:将有效消息读出来,然后重新截断文件,将消息写入文件中

4、持久化的有效消息总量

5、持久化的总的消息数量:决定了什么时候进行消息回收

管理操作

1、向队列新增消息

2、获取队首消息:获取消息后,将消息从待推送链表中删除,加入到待确认消息中

3、对消息进行确认:从待确认消息中移除,并进行持久话数据的删除

4、恢复队列的历史消息:主要在构造函数中进行

5、垃圾回收(队列持久化子模块完成):持久化文件中有效消息比例小于50%,总消息数超过2000进行垃圾回收

6、删除队列相关消息文件:当一个队列被删除了,他的消息也没有存在的意义了。

队列消息管理 

1、初始化队列消息结构

2、移除队列消息结构:在队列被删除时调用

3、向队列中新增消息

4、对队列消息进行确认

5、恢复队列历史消息

5、虚拟机数据管理

虚拟机数据管理

对于交换机+队列+绑定信息+消息数据管理的整合

要管理的数据

1、交换机的管理句柄

2、队列数据的管理句柄

3、绑定信息的数据管理句柄

4、消息数据管理句柄

要管理的操作:

1、声明/删除交换机:在删除交换机的时候要删除相关的绑定信息

2、声明/删除队列:在删除队列的时候要删除相关的绑定信息以及数据

3、队列的绑定/解除绑定:绑定的时候,交换机和队列必须存在

4、获取指定队列的消息

5、对指定队列的指定消息进行确认

6、获取交换机相关的所有绑定信息:一条消息要发布给指定交换机的时候,交换机获取所有的绑定信息,来确认消息要发送到哪个队列

6、路由匹配管理

决定了一个消息能否发布到指定的队列中

在交换机与队列的绑定信息中有一个banding_key,这是队列发布的匹配规则

在每条要发布的消息中,都有一个routing_key,这是消息的发布规则

根据交换机的类型进行相关的匹配操作

路由匹配模块本质上来说没有管理的数据,只有向外提供的路由匹配操作:

1、判断routing_key是否符合规定:

格式判定:只能由数字,字母,_, . 构成

2、判断binding_key是否符合规定:

格式判断:只能由数字,字母,_ , # , * 构成

3、判断routing_key与binding_key能否匹配成功的接口

7、消费者管理

客户端有两种:发布消息,订阅消息

只有订阅了消息的客户端才是一个消费者

消费者数据存在的意义:当指定的客户端有了消息后,需要将消息推送给这个消费者客户端

推送的时候就要找到这个客户端的相关信息———连接

消费者信息

1、消费者标识

2、订阅队列名称:当前队列有消息就会推送给这个客户端,当客户端收到消息,需要对指定队列的消息进行确认

3、自动确认标志:自动确认--推送消息后,直接删除消息不需要额外确认,手动确认——推送消息后需要等到确认回复再去删除消息

4、消费处理回调函数指针:队列有一条消息后,通过哪个函数进行处理(向指定客户端推送消息)

消费者管理

以队列为单元进行管理

        每个消费者订阅的都是指定队列的消息,消费者对这个消息进行确认也是以队列进行确认

当队列中有消息了,必然是获取订阅了这个队列的消费者进行推送

队列消费者管理结构

数据信息:消费者链表——保存当前队列的所有消费者信息(RR轮转每次取出下一个消费者 进行推送——一条消息只需要被一个客户端进行处理即可

管理操作:

1、新增消费者

2、RR轮转获取一个消费者

3、删除消费者

4、队列消费者数量

5、队列的消费者列表是否为空

消费者管理操作(以队列为单元进行管理)

1、初始化队列消费者结构

2、删除队列消费者结构

3、向指定队列中添加消费者

4、删除指定队列的指定消费者

5、获取指定队列的消费者

8、信道管理

信道是网络通信的一个概念,叫做通信信道

网络通信的时候,必然是通过网络连接来完成的,为了充分利用资源,细化出了信道的概念,对于用户来说,一个通信信道就是进行网络通信的载体,而一个真正的通信连接,可以创建出多个信道

每一个信道对于用户来说是独立的,但是本质的底层使用的是一个通信连接。

因此,信道是用户眼中的一个通道,所以所有的网络通信服务都由信道提供

1、声明/删除交换机

2、声明/删除队列

3、绑定信息的绑定/删除

4、消息的发布/订阅队列消息/取消订阅/队列消息的Ack

信道要管理的数据

1、信道ID

2、信道关联的消费者句柄:当信道关闭时,所有关联的消费者订阅都要取消,相当于删除所有的消费者。

3、信道关联的虚拟机句柄

4、工作线程池句柄:信道进行消息发布到指定队列后,要从指定队列的消费者链表中获取一个消费者,对这条消息进行消费,也就是将这条消息推送给客户端的操作要交给线程池来进行。并非每个信道都有一个线程池,而是整个服务器有一个线程池,大家所有的信道都是同一个线程池进行异步操作而已。

信道的管理

1、打开一个信道

2、关闭一个信道

3、获取指定信道句柄

9、连接管理

概念:网络通信连接

在网络通信模块,我们使用muduo库来实现底层通信,muduo库中本身就有Connection连接的概念和对象类。但是在我们的连接中还有一个上层的信道概念,这个概念在muduo库中是没有的。

因此我们需要在用户层面上对muduo库中的Connection连接进行二次封装,形成我们所需的连接管理

管理数据

1、muduo库的通信连接

2、当前连接关联的信道管理句柄

连接提供的操作

        1、创建信道

        2、关闭信道 

连接管理的操作

        1、新增连接

        2、关闭连接

        3、获取指定的连接信息

10、服务器模块

Broker服务器模块是一个功能的整合,本质上这个模块并不提供实质的功能性操作

这个模块最重要的是资源的整合,是一个资源的载体

1、一个服务器有一个工作线程池,其他所有的信道操作都是这一个线程池的。

2、一个服务器有一个虚拟机,其他所有的交换机,队列,绑定,消息的操作都是针对这个虚拟机进行的

3、一个服务器有一个消费者管理

4、通信相关的连接管理,协议处理模块句柄,也是一整个服务器有一套

三、客户端模块

1、消费者管理

消费者信息

1、消费者标识

2、订阅的队列名称

3、自动确认标志

4、消息的回调处理函数

当消费者订阅了一个队列的消息,这个队列有了消息后,就会将消息推送给这个客户端,这时候收到了消息就会通过回调函数处理,处理完毕后根据确认标志判断是否进行消息确认。

消费者管理:增 删 查 操作

2、信道管理

所有提供的操作与服务端基本对应,因为客户端需要给用户提供什么服务,服务器就要给客户端提供什么服务。

管理信息

1、信道ID

2、消费者管理句柄:每个信道都有自己相关的消费者

3、线程池句柄:对推送的消息进行回调处理,处理过程通过工作线程来进行

4、信道关联的连接

信道提供的服务

1、声明/删除交换机

2、声明/删除队列

3、绑定信息的绑定/删除

4、消息的发布/订阅队列消息/取消订阅/队列消息的Ack

5、创建/关闭信道

信道的管理:信道的 增 删 查 操作 

3、连接管理

客户端连接的管理,本质是对客户端TcpClient的二次封装和管理

对于用户,不需要有客户端的概念,连接对于用户来说就是客户端,通过连接创建信道来完成所需的服务,客户端这边的连接对用户来说就是一个资源的载体

管理操作:

1、连接服务器

2、创建信道

3、关闭信道

4、关闭连接

管理的资源:

异步线程池     连接关联的信道管理句柄

4、异步线程池模块

TcpClient需要一个EventLoopThread模块进行IO事件监控

收到推送的消息,需要对推送过来的消息进行处理,因此需要一个线程池来帮助我们完成消息处理的过程。

将异步工作线程模块单独出来,是因为多个连接用一个EventLoopThread进行IO监控就够了,所有推送的消息处理也只需要有一个线程池就够了。

以上就是本项目的整体框架,完整代码已上传仓库,有兴趣的话可以查看:

https://gitee.com/Faiz--555/MQ

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

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

相关文章

智源发布三款BGE新模型,再次刷新向量检索最佳水平

近期&#xff0c;以大语言模型&#xff08;LLM&#xff09;为基础的向量模型&#xff08;embedding model&#xff09;变得愈发流行。得益于大语言模型强大的语义理解能力&#xff0c;相关模型在下游任务中的检索精度得到了显著的提升。然而&#xff0c;当前基于大模型的向量模…

leetcode 1555 银行账号概要(postgresql)

需求 用户表&#xff1a; Users --------------------- | Column Name | Type | --------------------- | user_id | int | | user_name | varchar | | credit | int | --------------------- user_id 是这个表的主键。 表中的每一列包含每一个用户当前的额度信息。 交易表&…

Nginx反向代理实战

使用反向代理代理服务 假设我们有三台服务器提供不同的服务 nginx作为代理服务器 代理服务器&#xff1a; 192.168.101.23 其余三台服务器 服务器1 192.168.101.18 服务器2 192.168.101.87 服务器3 192.168.101.20 代理服务器的nginix配置 server {listen 8085;ser…

【机器学习基础】机器学习概述与实践基础

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;…

Docker中使用自定义网络方式实现Redis集群部署与测试流程

场景 Docker中Docker网络-理解Docker0与自定义网络的使用示例&#xff1a; Docker中Docker网络-理解Docker0与自定义网络的使用示例-CSDN博客 参考上面的流程实现自定义网络的实现。 下面记录其应用实例&#xff0c;使用Docker的自定义网络实现redis集群部署。 注&#xf…

IP地址https证书的优势与申请途径

一、IP地址SSL证书的优势 无需域名&#xff1a;对于一些内部系统或者专用设备而言&#xff0c;它们可能不具有域名&#xff0c;但仍需保障通信安全。IP地址SSL证书正好满足这一需求。简化管理&#xff1a;对于拥有大量设备的企业来说&#xff0c;维护每个设备的域名可能是一个…

tomato-靶机渗透

tomato-靶机 一、安装靶机环境 下载双击.ova文件&#xff0c;写文件名路径导入 打开虚拟机用NAT模式 编辑–>虚拟网络编辑器查看IP段 二、信息收集 1.御剑端口扫描查找该虚拟机的IP 访问网站 扫目录 dirb http://192.168.30.130 收集到目录 /server-status /antibot_im…

成为git砖家(9): git checkout <commit> <file> 的含义

文章目录 1. 目的2. 官方文档解释3. Tower 的解释4. References 1. 目的 git checkout 命令承载了非常多的功能&#xff0c; 想要一次全弄懂&#xff0c;不太现实&#xff1b; 这次白鱼带领大家学习 git checkout <file> 的用法。 老规矩&#xff0c;先查看 git checko…

鸿蒙 HarmonyOS NEXT端云一体化开发-云数据库篇

一、概述 云数据库是一款基于对象模型的数据库&#xff0c;采用存储区、对象类型和对象三级结构。 数据模型 存储区 存储区是一个独立的数据存储区域&#xff0c;多个数据存储区之间相互独立&#xff0c;每个存储区拥有完全相同的对象类型定义 --类似于关系型数据库中的da…

如何有效管理众多账号密码:选择适合你的密码管理工具

在如今的数字化时代&#xff0c;我们的生活几乎离不开各种互联网应用和服务。从社交媒体到在线银行&#xff0c;从购物网站到工作平台&#xff0c;每个应用都要求我们注册账号并设置密码。 随着账号数量的不断增加&#xff0c;管理这些密码成为了一个令人头疼的问题。幸运的是…

AMEYA360:上海雷卯电子CAN BUS芯片静电浪涌击穿整改方案

在现代电子系统中&#xff0c;CAN Bus(Controller Area Network Bus&#xff0c;控制器局域网络总线)作为一种常用的通信协议&#xff0c;标准CAN通常指的是CAN 2.0A和CAN 2.0B协议&#xff0c;其最大通讯速率为1Mbps。而高速CAN通常指的是CAN FD(CAN Flexible Data-rate)协议&…

如何磁盘覆写

使用命令提示符写0 命令提示符是Windows系统内置的一个非常实用的工具&#xff0c;可以通过几行短短的命令来完成各种各样的电脑相关操作而无需开启应用程序&#xff0c;所以我们可以通过命令提示符中的format命令来完成硬盘写0任务。 步骤1. 在搜索框中输入cmd并以管理员身份…

每日一题系列-两个数组的交集

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” class Solution { public:int hash[1010] {0};vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {vector<int> ret;for(a…

漏洞复现-路由器TOTOLINK-A6000R-RCE

本文来自无问社区&#xff0c;更多漏洞信息可前往查看http://wwlib.cn/index.php/artread/artid/14996.html 0x01 产品简介 TOTOLINK A6000R是一款性能卓越的无线路由器&#xff0c;采用先进的技术和设计&#xff0c;为用户提供出色的网络体验。其支持最新的Wi-Fi标准&#x…

Java学习----类和对象与封装

目录 一、面向对象和面向过程二、类类的定义类的实例化this引用&#xff08;这一块一定要看懂哦&#xff09;类的构造方法成员变量的初始化 三、封装包常见的包 四、关于static初始化 五、代码块六、对象的打印--toString 一、面向对象和面向过程 众所周知&#xff0c;Java语言…

HomeServer平台选择,介绍常用功能

​​ 平台选择 HomeServer 的性能要求不高&#xff0c;以下是我的硬件参数&#xff0c;可供参考&#xff1a; ‍ 硬件&#xff1a; 平台&#xff1a;旧笔记本CPU&#xff1a;i5 4210u内存 8G硬盘&#xff1a;128G 固态做系统盘&#xff0c;1T1T 机械盘组 RAID1 做存储。硬…

计算机网络-七层协议栈介绍

之前介绍了网络世界的构成&#xff0c;从宏观角度介绍了网络设备和网络架构&#xff0c;链接: link&#xff0c;但是这种认识过于粗糙&#xff0c;过于肤浅。网络本质上是用于主机之间的通信&#xff0c;是端对端的连接通信&#xff0c;两台计算机可能距离很远&#xff0c;主机…

新书推荐:《码农职场:IT 人求职就业手册》——照亮你的职业道路

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

PHP中关于排名和显示的问题

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

【LLM】五、open-webui+ollama搭建自己的RAG服务

系列文章目录 往期文章回顾&#xff1a; 【LLM】四、RAG简介 【LLM】三、open-webuiollama搭建自己的聊天机器人 【LLM】二、python调用本地的ollama部署的大模型 【LLM】一、利用ollama本地部署大模型 文章目录 ​​​​​​​ 目录 系列文章目录 文章目录 前言 一、RAG是…