RabbitMQ
官网地址: https://www.rabbitmq.com/
一个遵循AMQP协议,开源面向消息的中间件,支持多种编程语言。
Rabbitmq 能做什么?
- 逻辑解耦,异步的消息任务
- 消息持久化,重启不影响
- 削峰,大规模的消息处理
主要的特点
可靠性:持久化,传输确认,发布确认
可扩展性:多个节点可以组成一个集群,可动态更改
多语言:支持多数编程语言
管理界面:有常见的用户界面,便于管理和监控
常见的应用场景:
并发请求的压力高可用设计(电商秒杀场景),
异步任务处理结果的回调设计(日志订单异步处理),
系统集成与分布式系统设计(各种子系统的消息同步)。
工作原理
简单介绍生产者和消费者会和服务器建立tcp链接,在tcp链接之上会建立多个信道channel,通过信道来发送消息,生产者生产消息后不直接直接发到队列中,而是发到一个交换空间:Exchange,
Exchange会根据Exchange类型和Routing Key来决定发到哪个队列中,消费者在从队列中拿到消息
具体工作模式
名词解释
ExChange :消息交换机,决定消息按照什么规则路由到那个对列中去
Queue :消息载体,每个消息都会被投到一个或多个队列
Binding:绑定,把exchange 和 queue按照路由规则绑定起来
Routing Key: 路由关键字,exchage根据这关键字来投递消息
Channel :消息通道,客户端的每个连接建立多个channel
Producer :消息生产者,用户投递消息的程序
Consumer :消息消费者,用于就是接收消息的程序
Exchage工作模式
Fanout: 类似广播,转发到所有绑定交换机的Queue
Direct: 类似单播,RoutingKey 和 BindingKey完全匹配
Topic : 类似组播,转发到符合通配符的Queue
headers:请求头与消息头匹配,才能接收到消息
环境配置
通过docker环境配置
# /www/rabbitmq目录可自定义,主要用于目录挂载
mkdir -p /www/rabbitmq
# 创建容器
docker run -d --hostname rabbit-node1 --name rabbit-node1 -p 5672:5672 -p15672:15672 -v /www/rabbitmq:/var/lib/rabbitmq rabbitmq:management
# 查看容器状态
docker ps | grep rabbit
浏览器打开登录rabbitmq, 入口:http://localhost:15672
默认用户名: guest 密码: guest
golang实战
简单基本玩法
//下载类库
go get "github.com/streadway/amqp"
前期准备代码
//连接信息
const MQURL = "amqp://imoocuser:[email protected]:5672/imooc"
//rabbitMQ结构体
type RabbitMQ struct {
conn *amqp.Connection
channel *amqp.Channel
//队列名称
QueueName string
//交换机名称
Exchange string
//bind Key 名称
Key string
//连接信息
Mqurl string
}
//创建结构体实例
func NewRabbitMQ(queueName string,exchange string ,key string) *RabbitMQ {
return &RabbitMQ{QueueName:queueName,Exchange:exchange,Key:key,Mqurl:MQURL}
}
//断开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))
}
}
简单模式
简单模式下 Exchange 和 key是为空的,不需要设置
//创建简单模式下RabbitMQ实例
func NewRabbitMQSimple(queueName string) *RabbitMQ {
//创建RabbitMQ实例
rabbitmq := NewRabbitMQ(queueName,"","")
var err error
//获取connection
rabbitmq.conn, err = amqp.Dial(rabbitmq.Mqurl)
rabbitmq.failOnErr(err, "failed to connect rabb"+
"itmq!")
//获取channel
rabbitmq.channel, err = rabbitmq.conn.Channel()
rabbitmq.failOnErr(err, "failed to open a channel")
return rabbitmq
}
//简单模式队列生产
func (r *RabbitMQ) PublishSimple(message string) {
//1.申请队列,如果队列不存在会自动创建,存在则跳过创建
_, err := r.channel.QueueDeclare(
r.QueueName,
//是否持久化
false,
//是否自动删除
false,
//是否具有排他性
false,
//是否阻塞处理
false,
//额外的属性
nil,
)
if err != nil {
fmt.Println(err)
}
//调用channel 发送消息到队列中
r.channel.Publish(
r.Exchange,
r.QueueName,
//如果为true,根据自身exchange类型和routekey规则无法找到符合条件的队列会把消息返还给发送者
false,
//如果为true,当exchange发送消息到队列后发现队列上没有消费者,则会把消息返还给发送者
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
}
//simple 模式下消费者
func (r *RabbitMQ) ConsumeSimple() {
//1.申请队列,如果队列不存在会自动创建,存在则跳过创建
q, err := r.channel.QueueDeclare(
r.QueueName,
//是否持久化
false,
//是否自动删除
false,
//是否具有排他性
false,
//是否阻塞处理
false,
//额外的属性
nil,
)
if err != nil {
fmt.Println(err)
}
//接收消息
msgs, err :=r.channel.Consume(
q.Name, // queue
//用来区分多个消费者
"", // consumer
//是否自动应答
true, // auto-ack
//是否独有
false, // exclusive
//设置为true,表示 不能将同一个Conenction中生产者发送的消息传递给这个Connection中 的消费者
false, // no-local
//列是否阻塞
false, // no-wait
nil, // args
)
if err != nil {
fmt.Println(err)
}
forever := make(chan bool)
//启用协程处理消息
go func() {
for d := range msgs {
//消息逻辑处理,可以自行设计逻辑
log.Printf("Received a message: %s", d.Body)
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}
工作模式
一个消息只能被一个消费者获取(场景:生产消息大于消费消息的时候),更简单模式代码一样,只是同事开启了多个消费端,起到负载均衡的作用
订阅模式
该模式下,队列为空,key为空;只需设置交换空间即可;消息被投递到多个队列中,一个消息被多个消费者消费
//订阅模式创建RabbitMQ实例
func NewRabbitMQPubSub(exchangeName string) *RabbitMQ {
//创建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,
//true表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定
false,
false,
nil,
)
r.failOnErr(err, "Failed to declare an excha"+
"nge")
//2.发送消息
err = r.channel.Publish(
r.Exchange,
"",
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
}
//订阅模式消费端代码
func (r *RabbitMQ) RecieveSub() {
//1.试探性创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
//交换机类型
"fanout",
true,
false,
//YES表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定
false,
false,
nil,
)
r.failOnErr(err, "Failed to declare an exch"+
"ange")
//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,
//在pub/sub模式下,这里的key要为空
"",
r.Exchange,
false,
nil)
//消费消息
messges, err := r.channel.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
forever := make(chan bool)
go func() {
for d := range messges {
log.Printf("Received a message: %s", d.Body)
}
}()
fmt.Println("退出请按 CTRL+C\n")
<-forever
}
路由模式
在路由模式下,一个消息可以被多个消费者获取,该模式生产端可以指定消费端;交换机的类型需要设置为direct,并且需要设置bind key。
/路由模式
//创建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
"direct",
true,
false,
false,
false,
nil,
)
r.failOnErr(err, "Failed to declare an excha"+
"nge")
//2.发送消息
err = r.channel.Publish(
r.Exchange,
//要设置
r.Key,
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
}
//路由模式接受消息
func (r *RabbitMQ) RecieveRouting() {
//1.试探性创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
//交换机类型
"direct",
true,
false,
false,
false,
nil,
)
r.failOnErr(err, "Failed to declare an exch"+
"ange")
//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.Key,
r.Exchange,
false,
nil)
//消费消息
messges, err := r.channel.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
forever := make(chan bool)
go func() {
for d := range messges {
log.Printf("Received a message: %s", d.Body)
}
}()
fmt.Println("退出请按 CTRL+C\n")
<-forever
}
Topic模式,话题模式
一个消息可以被多个消费者获取,消息的目标queue可用BindingKey以通配符,的方式指定。
交换的类型设置为 topic,在接受端通过匹配规则匹配(例如:hello.*.world)
//话题模式
//创建RabbitMQ实例
func NewRabbitMQTopic(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) PublishTopic(message string ) {
//1.尝试创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
//要改成topic
"topic",
true,
false,
false,
false,
nil,
)
r.failOnErr(err, "Failed to declare an excha"+
"nge")
//2.发送消息
err = r.channel.Publish(
r.Exchange,
//要设置
r.Key,
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
}
//话题模式接受消息
//要注意key,规则
//其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
//匹配 imooc.* 表示匹配 imooc.hello, 但是imooc.hello.one需要用imooc.#才能匹配到
func (r *RabbitMQ) RecieveTopic() {
//1.试探性创建交换机
err := r.channel.ExchangeDeclare(
r.Exchange,
//交换机类型
"topic",
true,
false,
false,
false,
nil,
)
r.failOnErr(err, "Failed to declare an exch"+
"ange")
//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,
//在pub/sub模式下,这里的key要为空
r.Key,
r.Exchange,
false,
nil)
//消费消息
messges, err := r.channel.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
forever := make(chan bool)
go func() {
for d := range messges {
log.Printf("Received a message: %s", d.Body)
}
}()
fmt.Println("退出请按 CTRL+C\n")
<-forever
}
参考: https://www.cnblogs.com/luotianshuai/p/7469365.html#4199652