1. RabbitMQ是什么?

  MQ 是什么?队列是什么,MQ 我们可以理解为消息队列,队列我们可以理解为管道。以管道的方式做消息传递。

生活场景:

1.其实我们在双11的时候,当我们凌晨大量的秒杀和抢购商品,然后去结算的时候,就会发现,界面会提醒我们,让我们稍等,以及一些友好的图片文字提醒。而不是像前几年的时代,动不动就页面卡死,报错等来呈现给用户。

在这业务场景中,我们就可以采用队列的机制来处理,因为同时结算就只能达到这么多。

2.在我们平时的超市中购物也是一样,当我们在结算的时候,并不会一窝蜂一样涌入收银台,而是排队结算。这也是队列机制。

2. RabbitMQ简介

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 下面将重点介绍RabbitMQ中的一些基础概念,了解了这些概念,是使用好RabbitMQ的基础。

  • 可靠性(Reliablity):使用了一些机制来保证可靠性,比如持久化、传输确认、发布确认。
  • 灵活的路由(Flexible Routing):在消息进入队列之前,通过Exchange来路由消息。对于典型的路由功能,Rabbit已经提供了一些内置的Exchange来实现。针对更复杂的路由功能,可以将多个Exchange绑定在一起,也通过插件机制实现自己的Exchange。
  • 消息集群(Clustering):多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
  • 高可用(Highly Avaliable Queues):队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
  • 多种协议(Multi-protocol):支持多种消息队列协议,如STOMP、MQTT等。
  • 多种语言客户端(Many Clients):几乎支持所有常用语言,比如Java、.NET、Ruby等。
  • 管理界面(Management UI):提供了易用的用户界面,使得用户可以监控和管理消息Broker的许多方面。
  • 跟踪机制(Tracing):如果消息异常,RabbitMQ提供了消息的跟踪机制,使用者可以找出发生了什么。
  • 插件机制(Plugin System):提供了许多插件,来从多方面进行扩展,也可以编辑自己的插件。

2.1 定义和特征

  1. RbbitMQ是面向消息的中间件,用于组件之间的解耦,主要体现在消息的发送者和消费者之间无强依赖关系

  2. RabbitMQ特点:高可用,可扩展,多语言客户端,管理界面等;

  3. 主要使用场景:流量削峰,异步处理,应用解耦等;

2.2 安装

安装erlang

# centos7
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.17/rabbitmq-server-3.7.17-1.el7.noarch.rpm
yum install epel-release
yum install unixODBC unixODBC-devel wxBase wxGTK SDL wxGTK-gl
rpm -ivh esl-erlang_22.0.7-1~centos~7_amd64.rpm

 安装rabbitmq

wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.0/rabbitmq-server-3.8.0-1.el7.noarch.rpm
yum -y install socat
rpm -ivh rabbitmq-server-3.8.0-1.el7.noarch.rpm

 启动

chkconfig rabbitmq-server on                     # 开机启动
systemctl start rabbitmq-server.service # 启动
systemctl stop rabbitmq-server.service # 停止
systemctl restart rabbitmq-server.service # 重启
rabbitmqctl status # 查看状态
rabbitmq-plugins enable rabbitmq_management # 启动Web管理器

 

修改配置

vi /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.0/ebin/rabbit.app

将:{loopback_users, [<<”guest”>>]}, 改为:{loopback_users, []}, 原因:rabbitmq从3.3.0开始禁止使用guest/guest权限通过   除localhost外的访问

systemctl restart rabbitmq-server.service    # 重启服务

3. RabbitMQ核心概念

  • Broker:标识消息队列服务器实体.
  • Virtual Host:虚拟主机。标识一批交换机、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在链接时指定,RabbitMQ默认的vhost是 /。
  • Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
  • Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
  • Banding:绑定,用于消息队列和交换机之间的关联。一个绑定就是基于路由键将交换机和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
  • Channel:信道,多路复用连接中的一条独立的双向数据流通道。新到是建立在真实的TCP连接内地虚拟链接,AMQP命令都是通过新到发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说,建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。
  • Connection:网络连接,比如一个TCP连接。
  • Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。
  • Consumer:消息的消费者,表示一个从一个消息队列中取得消息的客户端应用程序。
  • Message:消息,消息是不具名的,它是由消息头和消息体组成。消息体是不透明的,而消息头则是由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(优先级)、delivery-mode(消息可能需要持久性存储[消息的路由模式])等。

AMQP消息路由

AMQP中消息的路由过程和JMS存在一些差别。AMQP中增加了Exchange和Binging的角色。生产者把消息发布到Exchange上,消息最终到达队列并被消费者接收,而Binding决定交换器的消息应该发送到哪个队列。

  • Exchange类型

Exchange分发消息时,根据类型的不同分发策略有区别。目前共四种类型:direct、fanout、topic、headers(headers匹配AMQP消息的header而不是路由键(Routing-key),此外headers交换器和direct交换器完全一致,但是性能差了很多,目前几乎用不到了。所以直接看另外三种类型。)。

direct

 

消息中的路由键(routing key)如果和Binding中的binding key一致,交换器就将消息发到对应的队列中。路由键与队列名完全匹配。

fanout

 

每个发到fanout类型交换器的消息都会分到所有绑定的队列上去。fanout交换器不处理该路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout类型转发消息是最快的。

topic

 
       topic交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键(routing-key)和绑定键(bingding-key)的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:"#"和"*"。#匹配0个或多个单词,匹配不多不少一个单词。

4. RabbitMQ的运行模式

  • 简单模式
       创建实例
package RabbitMQ

import (
"fmt"
"github.com/streadway/amqp"
"log"
) //url格式 amqp://账号:密码@rabbitmq服务器地址:端口号/vhost
const MQURL = "amqp://zhangyafei:zhangyafei@182.254.179.186:5672/imooc" type RabbitMQ struct {
conn *amqp.Connection
channel *amqp.Channel
// 队列名称
QueueName string
//交换机
Exchange string
//key
Key string
// 连接信息
Mqurl string
} //创建结构体实例
func NewRabbitMQ(queueName string, exchange string, key string) *RabbitMQ {
rabbitmq := &RabbitMQ{QueueName: queueName, Exchange: exchange, Key: key, Mqurl: MQURL}
var err error
// 创建rabbitmq连接
rabbitmq.conn, err = amqp.Dial(rabbitmq.Mqurl)
rabbitmq.failOnErr(err, "创建连接错误!")
rabbitmq.channel, err = rabbitmq.conn.Channel()
rabbitmq.failOnErr(err, "获取channel失败!")
return rabbitmq
} //断开channel和connection
func (r *RabbitMQ) Destory() {
r.channel.Close()
r.conn.Close()
} //错误处理函数
func (r *RabbitMQ) failOnErr(err error, message string) {
if err != nil {
log.Fatalf("%s:%s", message, err)
panic(fmt.Sprintf("%s:%s", message, err))
}
} // 简单模式step1: 1.创建简单模式下的rabbitmq实例
func NewRabbitMQSimple(queueName string) *RabbitMQ {
return NewRabbitMQ(queueName, "", "")
} // 简单模式step2: 2.简单模式下生产
func (r *RabbitMQ) PublishSimple(message string) {
// 1. 申请队列,如果队列不存在则自动创建,如果存在则跳过创建
// 保证队列存在,消息能发送到队列中
_, err := r.channel.QueueDeclare(
r.QueueName,
// 是否持久化
false,
// 是否为自动删除
false,
// 是否具有排他性
false,
// 是否阻塞
false,
// 额外属性
nil,
)
if err != nil {
fmt.Println(err)
}
// 2. 发送消息到队列中
r.channel.Publish(
r.Exchange,
r.QueueName,
// 如果为true,根据exchange类型和routekey规则,如果无法找到符合条件的队列,则会把发送的消息返回给发送者
false,
// 如果为true,当exchange发送消息到队列后发现队列上没有绑定消费者,则会把消息发还给发送者
false,
amqp.Publishing{ContentType: "text/plain", Body: []byte(message)},
)
} // 简单模式step3: 3.简单模式下消费
func (r *RabbitMQ) ConsumeSimple() {
// 1. 申请队列,如果队列不存在则自动创建,如果存在则跳过创建
// 保证队列存在,消息能发送到队列中
_, err := r.channel.QueueDeclare(
r.QueueName,
// 是否持久化
false,
// 是否为自动删除
false,
// 是否具有排他性
false,
// 是否阻塞
false,
// 额外属性
nil,
)
if err != nil {
fmt.Println(err)
}
// 2. 接收消息
msgs, err := r.channel.Consume(
r.QueueName,
// 用来区分多个消费者
"",
// 是否自动应答
true,
// 是否具有排他性
false,
// 如果为true,表示不能将同一个conn中的消息发送给这个conn中的消费者
false,
// 队列是否阻塞
false,
nil,
)
if err != nil {
fmt.Println(err)
}
forever := make(chan bool)
// 3. 启用协程处理消息
go func() {
for d := range msgs {
// 实现我们要处理的逻辑函数
log.Printf("Received a message: %s", d.Body)
}
}()
log.Printf("[*] waiting for messages, to exit process CTRL+C")
<-forever
}

创建实例

        生产者
package main

import (
"RabbitMQ/RabbitMQ/RabbitMQ"
"fmt"
) func main() {
rabbitmq := RabbitMQ.NewRabbitMQSimple("imoocSimple")
rabbitmq.PublishSimple("hello imooc!")
fmt.Println("发送成功")
}

mainSimplePublish.go

  消费者

package main

import (
"RabbitMQ/RabbitMQ/RabbitMQ"
) func main() {
rabbitmq := RabbitMQ.NewRabbitMQSimple("imoocSimple")
rabbitmq.ConsumeSimple()
}

mainSimpleReceive.go

  • 工作模式
     生产者
package main

import (
"RabbitMQ/RabbitMQ/RabbitMQ"
"fmt"
"strconv"
"time"
) func main() {
rabbitmq := RabbitMQ.NewRabbitMQSimple("imoocSimple")
for i := 0; i<= 100; i++ {
rabbitmq.PublishSimple("hello imooc!" + strconv.Itoa(i))
time.Sleep(1 * time.Second)
fmt.Println(i)
}
}

mainWorkPublish.go

  消费者

package main

import (
"RabbitMQ/RabbitMQ/RabbitMQ"
) func main() {
rabbitmq := RabbitMQ.NewRabbitMQSimple("imoocSimple")
rabbitmq.ConsumeSimple()
}

mainWorkReceive.go

  • 订阅模式
      创建实例
// 订阅模式下创建RabbitMQ实例
func NewRabbitMQPubSub(exchangeName string) *RabbitMQ {
rabbitmq := NewRabbitMQ("", exchangeName, "")
var err error
// 获取connection
rabbitmq.conn, err = amqp.Dial(rabbitmq.Mqurl)
rabbitmq.failOnErr(err, "failed to connect rabbitmq!")
// 获取channel
rabbitmq.channel, err = rabbitmq.conn.Channel()
rabbitmq.failOnErr(err, "failed to open a channel")
return rabbitmq
} // 订阅模式下生产
func (r *RabbitMQ) PublishPub(message string) {
// 1. 尝试创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
"fanout", // 广播类型
true, // 持久化
false, // 是否删除
false, // true表示这个exchange不可以被client用来推送消息的,仅用来进行exchange和exchange之间的绑定
false,
nil,
)
r.failOnErr(err, "Failed to declare a exchange")
// 2. 发送消息
err = r.channel.Publish(
r.Exchange,
"",
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
} // 订阅模式消费端的代码
func (r *RabbitMQ) ReceiveSub() {
// 1. 尝试创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
"fanout", // 广播类型
true, // 持久化
false, // 是否删除
false, // true表示这个exchange不可以被client用来推送消息的,仅用来进行exchange和exchange之间的绑定
false,
nil,
)
r.failOnErr(err, "Failed to declare a exchange")
// 2. 试探性创建队列
q, err := r.channel.QueueDeclare(
"", // 随机生产队列名称
false,
false,
true,
false,
nil,
)
r.failOnErr(err, "failed to declare a queue")
// 绑定队列到 exchange中
err = r.channel.QueueBind(
q.Name,
"", // 在订阅模式下,这里的key为空
r.Exchange,
false,
nil)
// 消费消息
messages, err := r.channel.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
if err != nil {
fmt.Println(err)
}
forever := make(chan bool)
// 3. 启用协程处理消息
go func() {
for d := range messages {
// 实现我们要处理的逻辑函数
log.Printf("Received a message: %s", d.Body)
}
}()
log.Printf("[*] waiting for messages, to exit process CTRL+C")
<-forever
}

创建实例

      生产者
package main

import (
"RabbitMQ/RabbitMQ/RabbitMQ"
"fmt"
"strconv"
"time"
) func main() {
rabbitmq := RabbitMQ.NewRabbitMQPubSub("NewProduct")
for i := 0; i <= 100; i++ {
rabbitmq.PublishPub("订阅模式生产第" + strconv.Itoa(i) + "条数据")
fmt.Println("订阅模式生产第" + strconv.Itoa(i) + "条数据")
time.Sleep(1 * time.Second)
}
}

mainPub.go

        消费者
package main

import (
"RabbitMQ/RabbitMQ/RabbitMQ"
) func main() {
rabbitmq := RabbitMQ.NewRabbitMQPubSub("NewProduct")
rabbitmq.ReceiveSub()
}

mainSub.go


  • 路由模式
 
   创建实例、
// 路由模式下创建RabbitMQ实例
func NewRabbitMQRouting(exchangeName string, routingkey string) *RabbitMQ {
// 创建RabbitMQ实例
rabbitmq := NewRabbitMQ("", exchangeName, routingkey)
var err error
// 获取connection
rabbitmq.conn, err = amqp.Dial(rabbitmq.Mqurl)
rabbitmq.failOnErr(err, "failed to connect rabbitmq!")
// 获取channel
rabbitmq.channel, err = rabbitmq.conn.Channel()
rabbitmq.failOnErr(err, "failed to open a channel")
return rabbitmq
} // 路由模式发送消息
func (r *RabbitMQ) PublishRouting(message string) {
// 1. 尝试创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
"direct", // 定向类型
true, // 持久化
false, // 是否删除
false, // true表示这个exchange不可以被client用来推送消息的,仅用来进行exchange和exchange之间的绑定
false,
nil,
)
r.failOnErr(err, "Failed to declare a exchange")
// 2. 发送消息
err = r.channel.Publish(
r.Exchange,
r.Key,
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
} // 路由模式消费端的代码
func (r *RabbitMQ) ReceiveRouting() {
// 1. 试探性的创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
"direct", // 广播类型
true, // 持久化
false, // 是否删除
false, // true表示这个exchange不可以被client用来推送消息的,仅用来进行exchange和exchange之间的绑定
false,
nil,
)
r.failOnErr(err, "Failed to declare a exchange")
// 2. 试探性创建队列
q, err := r.channel.QueueDeclare(
"", // 随机生产队列名称
false,
false,
true,
false,
nil,
)
r.failOnErr(err, "failed to declare a queue")
// 绑定队列到 exchange中
err = r.channel.QueueBind(
q.Name,
r.Key, // 在订阅模式下,这里的key为空
r.Exchange,
false,
nil)
// 消费消息
messages, err := r.channel.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
if err != nil {
fmt.Println(err)
}
forever := make(chan bool)
// 3. 启用协程处理消息
go func() {
for d := range messages {
// 实现我们要处理的逻辑函数
log.Printf("Received a message: %s", d.Body)
}
}()
log.Printf("[*] waiting for messages, to exit process CTRL+C")
<-forever
}

创建实例

     生产者
package main

import (
"RabbitMQ/RabbitMQ/RabbitMQ"
"fmt"
"strconv"
"time"
) func main() {
rabbit_imooc_one := RabbitMQ.NewRabbitMQRouting("exImooc", "imooc_one")
rabbit_imooc_two := RabbitMQ.NewRabbitMQRouting("exImooc", "imooc_two")
for i := 0; i <= 100; i++ {
rabbit_imooc_one.PublishRouting("Hello imooc one!" + strconv.Itoa(i))
rabbit_imooc_two.PublishRouting("Hello imooc two!" + strconv.Itoa(i))
time.Sleep(1 * time.Second)
fmt.Println(i)
}
}

PublishRouting.go

  消费者1

package main

import "RabbitMQ/RabbitMQ/RabbitMQ"

func main()  {
rabbitmq_imooc_one := RabbitMQ.NewRabbitMQRouting("exImooc", "imooc_one")
rabbitmq_imooc_one.ReceiveRouting()
}

ReceiveRouting1.go

  消费者2

package main

import "RabbitMQ/RabbitMQ/RabbitMQ"

func main()  {
rabbitmq_imooc_two := RabbitMQ.NewRabbitMQRouting("exImooc", "imooc_two")
rabbitmq_imooc_two.ReceiveRouting()
}

ReceiveRouting2.go

Go语言系列之RabbitMQ消息队列的更多相关文章

  1. RabbitMQ消息队列系列教程(二)Windows下安装和部署RabbitMQ

    摘要 本篇经验将和大家介绍Windows下安装和部署RabbitMQ消息队列服务器,希望对大家的工作和学习有所帮助! 目录 一.Erlang语言环境的搭建 二.RabbitMQ服务环境的搭建 三.Ra ...

  2. RabbitMQ学习系列二:.net 环境下 C#代码使用 RabbitMQ 消息队列

    一.理论: .net环境下,C#代码调用RabbitMQ消息队列,本文用easynetq开源的.net Rabbitmq api来实现. EasyNetQ 是一个易于使用的RabbitMQ的.Net客 ...

  3. RabbitMQ消息队列应用

    RabbitMQ消息队列应用 消息通信组件Net分布式系统的核心中间件之一,应用与系统高并发,各个组件之间解耦的依赖的场景.本框架采用消息队列中间件主要应用于两方面:一是解决部分高并发的业务处理:二是 ...

  4. 使用EasyNetQ组件操作RabbitMQ消息队列服务

    RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue)的开源实现,是实现消息队列应用的一个中间件,消息队列中间件是分布式系统中重要的组件,主要解决应用耦合, ...

  5. Python RabbitMQ消息队列

    python内的队列queue 线程 queue:不同线程交互,不能夸进程 进程 queue:只能用于父进程与子进程,或者同一父进程下的多个子进程,进行交互 注:不同的两个独立进程是不能交互的.   ...

  6. RabbitMQ 消息队列 应用

    安装参考    详细介绍   学习参考 RabbitMQ 消息队列 RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. M ...

  7. (三)RabbitMQ消息队列-Centos7下安装RabbitMQ3.6.1

    原文:(三)RabbitMQ消息队列-Centos7下安装RabbitMQ3.6.1 如果你看过前两章对RabbitMQ已经有了一定了解,现在已经摩拳擦掌,来吧动手吧! 用什么系统 本文使用的是Cen ...

  8. (一)RabbitMQ消息队列-RabbitMQ的优劣势及产生背景

    原文:(一)RabbitMQ消息队列-RabbitMQ的优劣势及产生背景 本篇并没有直接讲到技术,例如没有先写个Helloword.我想在选择了解或者学习一门技术之前先要明白为什么要现在这个技术而不是 ...

  9. RabbitMQ消息队列(一): Detailed Introduction 详细介绍

     http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...

随机推荐

  1. .NET6使用DOCFX自动生成开发文档

    本文内容来自我写的开源电子书<WoW C#>,现在正在编写中,可以去WOW-Csharp/学习路径总结.md at master · sogeisetsu/WOW-Csharp (gith ...

  2. react18 来了,我 get 到...

    大家好! 本文主要是关于即将发布的 react 18 的新特性.那么 react18 带来了什么呢? 详情可以关注 github React 18 工作组仓库 1. automatic batchin ...

  3. 【划重点】Python matplotlib绘图设置坐标轴的刻度

    一.语法简介 plt.xticks(ticks,labels,rotation=30,fontsize=10,color='red',fontweight='bold',backgroundcolor ...

  4. 转:Sed使用

    awk于1977年出生,今年36岁本命年,sed比awk大2-3岁,awk就像林妹妹,sed就是宝玉哥哥了.所以 林妹妹跳了个Topless,他的哥哥sed坐不住了,也一定要出来抖一抖. sed全名叫 ...

  5. 日历优先级(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 好了,这下我们一共有三个日历了:"项目日历"(默认的日历)."任务日历"(与任务关 ...

  6. CF1492B Card Deck 题解

    Content 有 \(n\) 张纸牌组成的一个牌堆,每张纸牌都有一个价值 \(p_1,p_2,\dots,p_n\).每次选出最顶上的几个牌放到另外一个一开始为空的牌堆里面.定义一个牌堆的总值为 \ ...

  7. CF670A Holidays 题解

    Content 假设 \(1\) 年有 \(n\) 天,而每周同样会有 \(5\) 天工作日和 \(2\) 天休假.求一年最小的休假天数和最大休假天数. 数据范围:\(1\leqslant n\leq ...

  8. atexit模块介绍

    atexit 模块介绍 python atexit 模块定义了一个 register 函数,用于在 python 解释器中注册一个退出函数,这个函数在解释器正常终止时自动执行,一般用来做一些资源清理的 ...

  9. Vim使用简介

    Vim操作 Vim真的很酷:D 编辑模式 正常模式:在文件中四处移动光标进行修改 插入模式:插入文本 替换模式:替换文本 可视化(一般,行,块)模式:选中文本块 命令模式:用于执行命令 在不同的操作模 ...

  10. c++参数入栈顺序和参数计算顺序

    关于 本文涉及到代码,演示环境为:win10 + VS2017 ,ubuntu+clang clang版本: 参数入栈顺序 顺序 几种常见的函数参数入栈顺序,还有两种就不介绍了(__clrcall._ ...