一文读懂Cache一致性原理

news2024/11/18 18:43:22

1. 为何需要cache一致性

访问memory数据的速度相比core的运行速度来说,要花费更多的时钟周期,为了减轻这个差异引进了存储器层次结构,如图1所示。在层次结构中,越往上,读写速度越快,价格更贵,存储容量也越小。

图1 存储器结构层次

随着摩尔定律的发展,人们试图增加CPU的core数以提升系统整体性能,这类系统称之为多core系统。一个包含多core的系统中,每个core可能都有自己的私有cache,并且有可能会对相同的memory地址进行修改。如果多个core同时对同一memory地址进行读写操作,则很可能会导致cache内容不一致,从而导致数据不一致,最终造成系统出错和崩溃。因此,在设计多core系统时,必须考虑cache一致性(cache coherency)问题来确保数据的正确性和系统的稳定性。举个例子,假设2个core的系统,每个core私有的L1 cache的cacheline大小是64Bytes。两个core都读取0x80地址数据,导致0x80开始的64Bytes数据都分别加载到core0和core1的私有L1 cache中。

图2 两core系统读取0x80地址的数据

core0对0x80执行写操作,写入值为0x01。Core0的私有L1 cache更新对应cacheline的值。然后,core1读取0x80的数据,core1发现命中自己私有L1 cache,然后返回0x00值,并不是core0写入的0x01值,这就造成了core0和core1的私有L1 cache数据不一致现象。

图3 core0更新0x80地址的数据

按照正确的处理流程,可以使用以下两种方法保证多core cache的一致性:

  • Core0修改0x80地址数据的时候,除了更新core0的私有cache之外,还应将修改的数据通知到core1的私有cache去更新0x80地址的数据。
  • Core0修改0x80地址数据的时候,除了更新core0的私有cache之外,还应通知core1的私有cache将0x80地址所在的cacheline置为无效。保证core1读取数据时不会命中自己的cache。这样core1就必须重新从core0或memory中去读取0x80地址最新的数据。

Cache一致性可以通过软件和硬件来解决,通常来说,软件维护cache一致性维护成本太高,会导致性能下降。现在的实现方案基本都是使用硬件来自动维护多核cache的一致性,并且对软件和程序员来说是透明的。因此,本文只介绍硬件维护cache一致性。硬件维护cache一致性需要制定一致性协议,一致性协议是由系统内的各个分布式硬件参与者组件共同实现和维护的一组规则。一致性协议有许多版本,Arm的ACE(AXI Coherency Extensions)和CHI(Coherent Hub Interface),AMD的Coherent HyperTransport(HT),Intel的QuickPath Interconnect(QPI)等等。

2. 一致性协议的本质

虽然一致性协议众多,但本质上讲,所有一致性协议都通过将写入的数据传播到所有一致性cache来使一个core的写入对其它core可见。具体来说有两点原则:

  • Single-Writer, Multiple-Read(SWMR):在任意时刻,对于任意的memory地址A,只有一个core可以写它(也可以读它),或者多个core读它。因此,永远不会有这样的情况:一个core可以写入memory地址A,而其它core可以同时读取或写入该memory地址A。如下图4所示,可以把时间会切割为无限小的切片,在每个切片中,对于memory地址A,要么单个core具有读写访问权限,要么若干core(可能为0)具有只读访问权限。比如T1和T4时刻,允许多个core对同一个memory进行读取,但在T2和T3种,只能存在1个core拥有读写的权限。
  • Data-Value:在时间切片中,上一个时间切片结束的数据值与下一个时间切片开始的数据值相同,即使同一个切片中,每个core读取到的值必须相同。也就是memory地址的数据值需要被正确传播。比如在T1时刻,如果core2和core5可以读取到不同的值,那么就不符合一致性系统。同样的,如果T3的core1没有读到T2的core3最后写的数据值,或者T4的core1/core2/core3没有读到T3的core1最后写的数据值,那么也不符合一致性系统。

图4 不同core读写的时间顺序

对于SWMR还有另一种有意思的方式来解释,对于每个memory地址,存在固定数量的令牌,其数量至少与core数量一样。如果一个core拥有所有的令牌,它就可以写入memory地址,而且core必须至少有1个令牌,才能对memory地址进行读取数据。因此,在任何给定的时刻,当有core正在读写memory地址的数据时,其它core正在写入memory地址是不可能的。

一致性协议的两点原则在设计一致性协议时至关重要。举个比较常用的一致性无效协议(invalidate protocols)的设计方法:1.如果一个core想要读取memory地址A,它会向其它core发送消息,以获取memory地址A当前的最新值,并确保没有其它core缓存该memory地址A的cacheline拥有写权限的状态。2.如果一个core想要写入memory地址A,它会向其它core发送消息以获取memory地址A的当前值,并确保没有其它core缓存该memory地址A的cacheline拥有只读或读写权限的状态,也就是其它core都要无效掉自己缓存中memory地址A的cacheline。

3. 一致性协议的类别

有许多不同的方法来设计一致性协议,这些协议决定了在每个参与一致性的组件控制器上可能拥有哪些cache状态、发送或响应哪些一致性事务(transactions)操作、发生哪些事件(events)以及cache状态转换(transitions)等。

3.1 监听和目录

根据一个core的行为如何通知到其它core的方式可以分为监听协议(Snooping protocol)和目录协议(Directory protocol)。

1.监听协议

监听协议是第一类广泛使用的协议,在最初主导了商业市场,它们现在还有在各种小系统中使用。这种协议比较简单,当cache miss发生时,cache的控制器会发出仲裁请求给共享总线广播它的请求。共享总线确保所有cache控制器以相同的顺序观察到所有请求。监听协议依赖共享总线以一致的顺序向所有cache控制器发送广播消息,因此分布式cache控制器能够正确的更新自己的有限状态机,这些有限状态机共同代表了cache line的状态。

要求以一致的总顺序(total order)观察广播对于实现传统监听的共享总线具有重要意义。因为许多cache控制器可能同时尝试发出一致性请求,共享总线必须将这些请求序列化成某种总顺序(total order)。无论总线如何决定这个顺序,这个机制被称为监听协议的保序点(serialization point或ordering point)。在一般情况下,一个cache控制器发出一个一致性请求,总线在保序点将它排序并广播给所有cache控制器,包括发出请求的cache控制器,因此发出请求的cache控制器可以通过收到的监听请求流来判断它的请求在什么时候被处理了,在总顺序中排在哪个位置。

举个例子,监听协议总线使用仲裁逻辑来确保一次只在总线上广播一个请求,那么这个仲裁逻辑充当保序点,因为它有效地决定了各个一致性请求在总线上出现的顺序。有个微妙的点是,cache控制器的一致性请求是在仲裁逻辑顺序就排好序的,但是cache控制器只能通过监听来观察到这个顺序,进而判断自己请求的排序情况。因此,cache控制器的请求在保序点排好序后,可能会晚几个周期才能被cache控制器观察到请求顺序。

监听协议的latency比目录协议低,而且实现也比较简单,但是不易扩展。

图5 监听协议例子

2.目录协议

目录协议最初是为了解决监听协议缺乏可扩展性的问题而开发的。目录协议会创建一个目录来维护每一个cacheline的一致性状态的全局视图。该目录跟踪各cache保存了哪些cacheline以及处于什么状态。Cache控制器发出的一致性请求将直接单播给目录控制器(通常也称作home)。目录控制器要么直接响应请求,要么根据目录信息将请求转发给一个或多个其它cache控制器,然后由后者响应。目录协议使用间接层来避免有序广播网络和让每个cache控制器处理每个请求。一致性请求传输流程通常包括两个步骤(单播请求,然后是单播响应)或三个步骤(1个单播请求,K个转发请求和K个响应,其中K是共享者的数量)。有些协议甚至还有第四部,因为响应间接通过目录控制器,或者因为请求者在一致性流程完成时通知目录控制器。相比之下,监听协议将一个cacheline的状态分布在所有cache控制器上,由于这种分布式状态没有中央单元记录信息,一致性请求必须被广播到所有cache控制器上。因此监听协议的一致性请求流程总需要两个步骤(广播请求,然后是单播响应)。

在大多数目录协议中,一致性请求在目录控制器处排序,多个cache控制器可以同时向目录控制器发送一致性请求,目录控制器会序列化这些请求并排好序。如果两个请求同时到达目录控制器,目录控制器会选择首先处理哪个请求,第二个处理的请求的命运取决于目录协议和请求的类型。第二个处理的请求可能:(a)在第一个请求之后立即得到处理;(b)暂时保存在目录中等待第一个请求完成;(c)直接发送否定响应。在后一种情况下,目录控制器向请求者发送否定确认响应,请求者必须重新发出其一致性请求。

目录协议在目录控制器上排序,因为大多数目录协议不使用总顺序的广播,没有序列化的全局概念。更确切说,一个一致性请求者相对于可能又副本的所有cache,必须单独序列化。需要其它cache显示的消息通知请求者的请求已被相关cache处理和序列化。比如说,对于想获取写权限的一致性请求,有共享副本的每个cache控制器一旦完成序列化了无效消息,都必须显示地发送确认消息。

目录协议需要更少的总线带宽,实现了更大的可伸缩性,但代价是总线某些一致性请求的latency。

图6 目录协议例子

3.2 无效和更新

根据一个core决定写cache line时,一致性协议中其它core的cache line副本如何处理,可以分为:无效协议(Invalidate protocol)和更新协议(Update protocol)。这个分类的选择与协议是监听还是目录类型无关,它们可以两两交叉,组成4种类型的一致性协议。

1. 无效协议

当一个core需要往某cacheline写入一个值时,cache控制器会启动一个一致性请求来使所有其它cache中对应的cacheline副本无效掉。一旦所有副本都无效掉后,请求者就可以将数据写入该cacheline中,这样就确保其它core不会读取到旧值。如果另一个core希望在其副本无效后读取该cacheline的值,则必须启动一个新的一致性请求来获取该cacheline的数据,并且它将从写该cacheline的core处获取副本,从而保证了数据一致性。

图7 无效协议例子 (1->2->3->4->5->6)

2.更新协议

当一个core需要往某cacheline写入一个值时,它会启动一个一致性请求来更新所有其它cache中的副本,这样所有cache用到的都是新值。

图8 更新协议例子  (1->2->3->4->5->6)

这两种协议的选择需要权衡,更新协议减少了core读取新值的latency,因为core不需要初始化并等待重读一致性请求的完成。更新协议通常要比无效协议消耗更大的带宽,因为更新协议除了传输地址,还要传输新值数据。此外,更新协议会使许多memory一致性模型的实现变得非常复杂。例如,当多个cache必须对一个cacheline的多个副本进行多个更新时,保持写的原子性非常困难。由于更新协议的复杂性,它们很少被实现。

监听和目录与无效和更新,这些可以检查混合搭配,组成四种类型的一致性协议:监听&&无效、监听&&更新、目录&&无效、目录&&更新。Arm的ACE和CHI使用就是目录&&无效组合。

4. 一致性协议的设计

4.1 状态(states)选择

4.1.1 状态的特征

只有一个core的系统中,cache line的状态要么有效要么无效。如果需要区分dirty状态,则cacheline可能有两种有效状态。Dirty的cache line具有比memory更新的数据。具有多个core的系统也可以只使用这两三种状态,但通常需要区分不同类型的有效状态。cache line状态有四个主要特征:validity、dirtiness、exclusivity和ownership。后两个特征是具有多core的系统所特有的。

  • Validity:一个有效的cache line具有该cache line的最新值。Cache line可以被读,但只有在它也是exclusivity的情况下才可以被写。
  • Dirtiness:如果一个cache line的值是最新的,且这个值与memory中的值不一致,那么这个cache line就是dirty的,缓存控制器负责最终用这个新值更新memory内容。Clean通常被用作dirty的反义词。
  • Exclusivity:如果一个cache line在系统中是唯一的cache line副本,则该cache line是exclusivity的,也就是该cache line除了可能在memory中,没有任何其它cache拥有这个cache line的值。
  • Ownership:如果cache控制器(或memory控制器)负责响应cache line的一致性请求,那么它就是这个cache line的owner。在大多数协议中,一个给定cache line始终只有一个owner。如果没有将cache line的ownership交接给另一个一致性控制器,就算是由于cache容量或冲突缺失,这个cache line可能也不会从cache中被踢出去以腾出空间给另一个cache line。在某些协议中,非owned的cache line可能会被悄悄无效掉,不发送任何消息通知其它组件。

4.1.2 MOESI状态模型

许多一致性协议使用经典的五态MOESI模型的子集。MOESI是cache中line的状态,最基本的三种状态时MSI,也可以使用O和E态,但是它们不是最基本的。O和E态通常是协议中优化某些场景使用的。这些状态中的每一个都可以由上一小节描述的状态特征组合而成。

  • M(odified):cache line是valid、exclusive、owned、并且可能是dirty的。这条line可以被读或写,且是cache中唯一的有效副本,拥有这条line的cache必须响应其它caches对这条line的请求,并且这条line在memory中的副本可能已经过期了。
  • S(hared):cache line是valid,但不是exclusive、dirty和owned的。这条line只能被读,其它caches也可能具有这条line的有效只读副本。
  • I(nvalid):cache 里呢是invalid,cache要么不包含这条line,要么包含可能无法读取或写入的陈旧副本。
  • O(wned):cache line是valid,owned、且可能是dirty的,但不是exclusive的。这条line只能被读,并且拥有这条line的cache必须响应其它caches对这条line的请求。其它cache也可能有这条line的只读副本,但它们不是owners。这条line在memory中的副本可能已经过期了。
  • E(xclusive):cache line是valid、owned、exclusive和clean的。这条line可以被读或写,没有其它cache拥有这条line的有效副本,并且这条line和memory中的数据是一致的,memory中的副本是最新的。(需要注意的是,某些协议中,Exclusive不被视为owned的)

图9用Venn图展示了MOESI状态。Venn图可以展示哪些状态共有哪些特征。除了I之外的所有状态都有效。M、O和E是ownership状态。M和E都是exclusivity,因为没有其它cache拥有该line的有效副本。M和O都是表示该cache line可能是dirty的。M、O、E和S组成了V(alid)态。

图9 MOESI状态的Venn图

MOESI状态模型虽然很常见,但不是所有的状态集合。例如,有些协议有F(orward)状态,它类似与O状态,除了它是clean的,即memory中的副本是最新的。Intel Core i7-9xx处理器中的QuickPath Interconnect(QPI)就使用了F状态,QPI支持MESIF的5个状态模型。

表1 MOESI的状态特征

4.2 事务(transaction)选择

一致性控制器组件的基本目标都是相似的,所以大多数一致性协议都有类似的transaction集合。例如,几乎所有协议都有一个获取Shared(只读) cache line的transaction。表2列出了一组常见的transactions,并且对于每个transaction,描述发起transaction的请求者目标。这些transactions都是由cache控制器发起的,这些cache控制器响应来自其对应core的读写请求。表3列出了一个core可以像其cache控制器发出的读写请求,以及这些core请求如何引导cache控制器发起一致性transactions。

表2 常见的transactions

尽管大多数协议使用类似的transactions集合,但它们在一致性控制器如何交互以执行transaction方面存在相当大的差异。正如我们之前提到,在监视协议中,cache控制器通过向系统中所有一致性控制器广播GetS请求来获取目标的cache line,并且当前line的owner的控制器需要提供所需数据去响应请求者。相反,在目录协议中,cache控制器通过向特定的目录控制器发送单播GetS请求,目录控制器可能直接响应,也可能将请求转发给其它一致性控制器去响应请求者的line数据。

表3 core对cache控制器的常见请求

5. 总结

为了弥补访问memory速度太慢的原因引进了cache。在多core系统中,每个core可能都有自己的私有cache,多个core会对自己的私有cache进行读写操作,如果不使用软件或硬件来维护cache数据,必然会造成cache一致性问题。为了硬件层面解决这个问题,大多数现在多core系统中都定义了一致性协议,让各个一致性控制器遵照” Single-Writer, Multiple-Read”和” Data-Value”这两条原则去维护存储内容。在一致性协议设计中,根据core的行为如何通知其它core分为监听协议和目录协议,根据core决定写cache line时,如何处理其它core的cache line副本,分为无效协议和更新协议。” 监听协议和目录协议”与” 无效协议和更新协议”可以两两交叉组成4种一致性协议,但目前最常用的是目录协议和无效协议的组合。随后抽象出cache line状态的四个主要特征:validity、dirtiness、exclusivity和ownership,以及这四个特征如何对应到经典的MOESI状态模型。最后介绍了一致性协议中常用的一致性transactions,以及core请求如何引导cache控制器发起一致性transactions。

为了使整篇文章内容不显得太冗长,本文没有对cache状态转换(transitions)过程过多讲述,也没有讲述异构系统和多芯片多处理器的一致性,现在计算机系统主要是异构的,不仅包含多core,还包含GPU和其它加速器(例如,神经网络硬件)。为了追求可编程性,这些异构系统也开始支持共享内存。

希望读者不管是从事软件还是硬件,本文能帮助大家了解更多的cache一致性知识。今天就写到这里了,有空继续写点memory consistency model。

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

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

相关文章

Prometheus(六):Blackbox监控安装配置

目录 1 Blackbox Exporter安装配置1.1 Blackbox Exporter简介1.2 安装1、安装-使用源码包安装下载安装blackbox.yml文件配置快速启动文件 2、安装-使用docker 1.3 Prometheus配置1、http监控2、ping探测-ip3、https probe-DNS解析4、metrics配置5、TCP监控-探测端口 总结 1 Bla…

Element UI中日期选择日(date-picker)等其他选择器下拉显示错位、位置错误解决

省流版 给选择器加上唯一key(下面的想看就看) 问题复现 需求是用一个下拉切换时间维度的选择,分别为年度、季度、月度,但是开发的时候发现,当切换的时候,视图可正常切换,但点击选择时却发现选…

InfoNCE loss

InfoNCE loss是一种用于自监督学习的损失函数,通常用于训练对比学习模型,如自编码器或神经网络。全称是"InfoNCE: Contrastive Estimation of Neural Entropy",基于对比学习的思想,旨在最大化正样本的相似性&#xff0c…

关于 C/C++ 1Z(17)开源项目 openppp2 协同程式切换工作流

下述为开源项目 openppp2(github)构建工作在 C/C 17 的 stackful 有栈协同程式的工作流切换示意图: 在 openppp2 之中采用人工手动方式管理协同程式之间的切换,每个中断过程只是保存线程栈信息(如寄存器、当前#PC EIP&…

利用idea创建一个maven web工程

1.创建Maven项目 2.选择不使用Web项目骨架&#xff08;即普通maven项目&#xff09; 3.创建成功后在pom.xml设置打包方式为war&#xff0c;并重构maven项目 <packaging>war</packaging> 4.补齐Maven Web项目缺失webapp的目录结构 右键项目名打开模块设置&#xf…

【STM32+HAL】I2C+DMA读取AS5600编码器

一、DMA的应用 有关更多DMA的应用&#xff0c;详见【STM32HAL】DMA应用 二、HAL库配置 1、开启I2C 开启对应DMA及中断 2、开启串口通信 至此&#xff0c;HAL库配置完毕 三、DMA版&#xff08;高效但不稳定&#xff09; 1、as5600.c #include "AS5600.h" #includ…

Javascript本地存储的方式,区别及应用场景

文章目录 一、方式cookielocalStorage特点sessionStorage扩展的前端存储方式优点&#xff1a;缺点&#xff1a; 二、区别三、应用场景相关连接 一、方式 javaScript本地缓存的方法我们主要讲述以下四种&#xff1a; cookiesessionStoragelocalStorageindexedDB cookie Cook…

【Python】python编程初探2---字符编码,输入和输出,初识数据类型

欢迎来CILMY23的博客 本篇主题为【Python】python编程初探2---字符编码&#xff0c;输入和输出&#xff0c;初识数据类型 个人主页&#xff1a;CILMY23-CSDN博客 Python系列专栏&#xff1a;​​​​​​​http://t.csdnimg.cn/rAsEH 上一篇博客&#xff1a;http://t.csdni…

网络层协议之IP协议

网络层主要做两方面事情&#xff1a; 1.地址管理&#xff1a;制定一系列规则&#xff0c;通过地址描述出网络上的一个设备的位置 2.路由选择&#xff1a;网络环境复杂&#xff0c;从一个节点到另一个节点之间有很多条路径&#xff0c;这就需要通过路由选择来筛选/规划出更合适…

Day18:LeedCode 513.找树左下角的值 112. 路径总和 106.从中序与后序遍历序列构造二叉树

513. 找树左下角的值 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 思路:出该二叉树的 最底层 最左边 节点的值找出深度最大的第一个结点(左结点先遍历) 方法一…

数据库与缓存一致性如何保证

最近建了一个技术交流群&#xff0c;欢迎志同道合的同学加入&#xff0c;群里主要讨论&#xff1a;分享业务解决方案、深度分析面试题并解答工作中遇到的问题&#xff0c;同时也能为我提供写作的素材。 欢迎加Q&#xff1a;312519302&#xff0c;进群讨论 前言 在工作中&#…

LangChain使用实例——RAG

Q&A with RAG Overview LLMs支持的最强大的应用程序之一是复杂的问答 (Q&A) 聊天机器人&#xff0c;这些应用程序可以回答有关特定源信息的问题&#xff0c;使用一种称为检索增强生成&#xff08;RAG&#xff09;的技术。 RAG RAG 是一种利用额外数据增强 LLM 知识…

linux 网卡配置 vlan/bond/bridge/macvlan/ipvlan 模式

linux 网卡模式 linux网卡支持非vlan模式、vlan模式、bond模式、bridge模式&#xff0c;macvlan模式、ipvlan模式等&#xff0c;下面介绍交换机端及服务器端配置示例。 前置要求&#xff1a; 准备一台物理交换机&#xff0c;以 H3C S5130 三层交换机为例准备一台物理服务器&…

如何用智能AI绘一幅世界地图?

今天我们分享一下&#xff0c;用智能AI绘一幅世界地图的方法&#xff01; 为了方便你极速体验&#xff0c;特意在文末为你准备了登录帐号&#xff0c;省去你注册的烦恼。 认准AI绘画官网 如果你在百度搜索“AI绘画”或“Midjourney”&#xff0c;找出来的基本全是广告&#…

matplotlib画图:子图中坐标轴与标题重合...

如下图 只要在代码最后加入 plt.tight_layout() 就可以自动调节

Java练习题目6:水仙花数是指其个位,十位和百位三个数字的立方和等于其自身的三位数,求出所有的水仙花数。(Daddodil6)

每日小语 要相信卷首以卷尾为前提&#xff0c;几乎同卷尾以卷首为前提是一样的。——叔本华 思考 //水仙花数是指其个位&#xff0c;十位和百位三个数字的立方和等于其自身的三位数&#xff0c;求出所有的水仙花数。 import java.util.Scanner; public class Daddodil6 {publ…

服务运营 | 印第安纳大学翟成成:改变生活的水井选址

编者按&#xff1a; 作者于2023年4月在“Production and Operations Management”上发表的“Improving drinking water access and equity in rural Sub-Saharan Africa”探讨了欠发达地区水资源供应中的可达性和公平性问题。作者于2020年1月去往非洲埃塞俄比亚提格雷地区进行…

【Python进阶】:面向对象编程的力量:解锁封装、继承与多态的秘密武器

引言 在Python编程世界中&#xff0c;面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;如同一把锋利的剑&#xff0c;它将现实世界的实体抽象为类&#xff0c;赋予程序更强的结构化、模块化特征&#xff0c;极大地提升了代码的可读性、可维护性和复用性…

抖音视频关键词批量采集工具|无水印视频爬虫提取软件

抖音视频关键词批量采集工具&#xff1a; 我们很高兴地介绍最新推出的抖音视频关键词批量采集工具&#xff0c;该工具集成了多项强大功能&#xff0c;让您轻松实现视频内容的批量提取和下载。以下是详细的功能解析和操作说明&#xff1a; 主要功能&#xff1a; 关键词批量提取…

如何使用PHP和RabbitMQ实现延迟队列(方式一)?

前言 今天我们来做个小试验&#xff0c;用PHP和RabbitMQ实现消息队列的延迟功能。 前期准备&#xff0c;需要安装好docker、docker-compose的运行环境。 需要安装RabbitMQ的可以看下面这篇文章。 如何使用PHP和RabbitMQ实现消息队列&#xff1f;-CSDN博客 一、安装RabbitM…