//创建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
}
// PublishSimple simple模式队列生产 func (r *RabbitMQ) PublishSimple(message string) { //1.申请队列,如果队列不存在会自动创建,存在则跳过创建 _, err := r.channel.QueueDeclare( // queue:队列名称 r.QueueName, //durable:是否持久化,当mq重启之后,还在 true, //exclusive:是否独占即只能有一个消费者监听这个队列 false, //autoDelete:是否自动删除。当没有Consumer时,自动删除掉 false, //noWait:是否阻塞处理。true:不阻塞,false:阻塞 false, //arguments:其他属性 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), }) }
// ConsumeSimple simple模式下消费者 func (r *RabbitMQ) ConsumeSimple() { //1.申请队列,如果队列不存在会自动创建,存在则跳过创建 q, err := r.channel.QueueDeclare( //队列名称 r.QueueName, //是否持久化 true, //是否自动删除 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
}
##### 1.2.2. SimplePublish.go
package main
import ( "fmt" "qiutian.com/study/02.rabbitmq/Simple/mq" )
func main() { rabbitmq := mq.NewRabbitMQSimple("hello.queue1") rabbitmq.PublishSimple("Hello akita!") fmt.Println("发送成功!") }
##### 1.2.3. mainSimpleRecieve.go
package main
import ( "qiutian.com/study/02.rabbitmq/Simple/mq" )
// simple模式消费者 func main() { // queueName 替换成你自己的队列名 rabbitmq := mq.NewRabbitMQSimple("hello.queue1") rabbitmq.ConsumeSimple() }
### 2. 工作模式(Work)

* **特点**:
* + 多个消费者同时从同一个队列中获取消息并处理,每个消息只能被一个消费者处理。
+ 消息在被消费者处理后会从队列中移除。
* **真实应用场景**:
* + 任务分发系统:多个消费者同时从队列中获取任务,每个消费者处理一个任务,以实现任务的负载均衡。
* **优点**:
* + 可以通过增加消费者来提高消息处理的并发性。
* **缺点**:
* + 没有消息持久化,一旦 RabbitMQ 服务器重启,队列中的消息将会丢失。
#### 2.1. 目录结构
Work
-mq
–-rabbitmq.go //这个是RabbitMQ的封装
-WorkPublish
–-mainWorkPublish.go //生产者发送消息
-WorkRecieve1
–-mainWorkRecieve1.go // 消费者接受消息
-WorkRecieve2
–-mainWorkRecieve2.go // 消费者接受消息

#### 2.2. 代码实现
##### 2.2.1. rabbitmq.go
package mq
import ( "fmt" "log"
"github.com/streadway/amqp"
)
// MQURL amqp://user:password@host:port/vhost // amqp://是固定参数,这个信息是固定不变的。后面两个是用户名密码ip地址端口号Virtual Host // 如果vhost是“/”就输入/%2F,/%2F代表斜杠 const MQURL = "amqp://test:123123@127.0.0.1:5672/%2F" // RabbitMQ rabbitMQ结构体 type RabbitMQ struct { conn *amqp.Connection channel *amqp.Channel //队列名称 QueueName string //交换机名称 Exchange string //bind Key 名称 Key string //连接信息 Mqurl string }
// NewRabbitMQ 创建结构体实例 func NewRabbitMQ(queueName string, exchange string, key string) *RabbitMQ { return &RabbitMQ{QueueName: queueName, Exchange: exchange, Key: key, Mqurl: MQURL} }
// Destory 断开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)) } }
// NewRabbitMQWork 创建work模式下RabbitMQ实例 func NewRabbitMQWork(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 }
// PublishWork work模式队列生产 func (r *RabbitMQ) PublishWork(message string) { //1.申请队列,如果队列不存在会自动创建,存在则跳过创建 _, err := r.channel.QueueDeclare( // queue:队列名称 r.QueueName, //durable:是否持久化,当mq重启之后,还在 true, //exclusive:是否独占即只能有一个消费者监听这个队列 false, //autoDelete:是否自动删除。当没有Consumer时,自动删除掉 false, //noWait:是否阻塞处理。true:不阻塞,false:阻塞 false, //arguments:其他属性 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), }) }
// ConsumeWork work 模式下消费者 func (r *RabbitMQ) ConsumeWork() { //1.申请队列,如果队列不存在会自动创建,存在则跳过创建 q, err := r.channel.QueueDeclare( //队列名称 r.QueueName, //是否持久化 true, //是否自动删除 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
}
##### 2.2.2. mainWorkPublish.go
package main
import ( "fmt" "qiutian.com/study/02.rabbitmq/Work/mq" "strconv" "time" )
func main() { rabbitmq := mq.NewRabbitMQWork("hello.queue1") for i := 0; i <= 100; i++ { rabbitmq.PublishWork("Hello akita!" + strconv.Itoa(i)) time.Sleep(1 * time.Second) fmt.Println(i) } }
##### 2.2.3. mainWorkRecieve1.go
package main
import ( "qiutian.com/study/02.rabbitmq/Work/mq" )
func main() { // queueName 替换成你自己的队列名 rabbitmq := mq.NewRabbitMQWork("hello.queue1") rabbitmq.ConsumeWork() }
##### 2.2.4. mainWorkRecieve2.go
package main
import ( "qiutian.com/study/02.rabbitmq/Work/mq" )
func main() { // queueName 替换成你自己的队列名 rabbitmq := mq.NewRabbitMQWork("hello.queue1") rabbitmq.ConsumeWork() }
### 3. 订阅模式(Publish)
TIPS:
在订阅消费者模式下,消费者后监听到的数据取决于它开始监听的时间。如果消费者在生产者生产数据之前开始监听,那么它将不会消费到生产者在开始监听之前发布的数据。只有在消费者开始监听之后,生产者发布的数据才会被消费者接收和处理。因此,如果你想让消费者消费之前发布的数据,你需要确保消费者在生产者发布数据之后开始监听。

* **特点**:
* + 生产者将消息发送到一个交换机中,交换机将消息广播给所有绑定了该交换机的队列。
+ 每个消息都会被交换机广播给所有绑定了该交换机的队列。
* **真实应用场景**:
* + 新闻推送系统:生产者将最新的新闻发布到交换机中,每个订阅者(消费者)都会收到相同的新闻。
* **优点**:
* + 实现了消息的广播功能,适用于多个消费者同时接收相同消息的场景。
* **缺点**:
* + 无法根据消费者的兴趣或条件选择性地接收消息。
#### 3.1. 目录结构

#### 3.2. 代码实现
###### 3.2.1.1. rabbitmq.go
package mq
import ( "fmt" "log"
"github.com/streadway/amqp"
)
// MQURL amqp://user:password@host:port/vhost // amqp://是固定参数,这个信息是固定不变的。后面两个是用户名密码ip地址端口号Virtual Host // 如果vhost是“/”就输入/%2F,/%2F代表斜杠 const MQURL = "amqp://test:123123@127.0.0.1:5672/%2F"
// RabbitMQ rabbitMQ结构体 type RabbitMQ struct { conn *amqp.Connection channel *amqp.Channel //队列名称 QueueName string //交换机名称 Exchange string //bind Key 名称 Key string //连接信息 Mqurl string }
// NewRabbitMQ 创建结构体实例 func NewRabbitMQ(queueName string, exchange string, key string) *RabbitMQ { return &RabbitMQ{QueueName: queueName, Exchange: exchange, Key: key, Mqurl: MQURL} }
// Destory 断开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)) } }
// NewRabbitMQPub 创建Pub模式下RabbitMQ实例 func NewRabbitMQPub(exchangeName string) *RabbitMQ { //创建RabbitMQ实例 rabbitmq := NewRabbitMQ("", exchangeName, "") 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 }
// PublishPub Pub模式队列生产 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 exchange")
//2.发送消息
//发送消息
//参数:
//1.交换机
//2.队列,
//3.是否强制性路由到队列,如果为true,根据自身exchange类型和routekey规则无法找到符合条件的队列会把消息返还
//给发送者
//4.是否立即发送
//5.其他属性
//6.发送消息的内容
//调用channel 发送消息到队列中
r.channel.Publish(
r.Exchange,
"",
//如果为true,根据自身exchange类型和routekey规则无法找到符合条件的队列会把消息返还给发送者
false,
//如果为true,当exchange发送消息到队列后发现队列上没有消费者,则会把消息返还给发送者
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
}
// ConsumePub pub 模式下消费者 func (r *RabbitMQ) ConsumePub() { //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 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,
//在pub/sub模式下,这里的key要为空
"",
r.Exchange,
false,
nil)
//消费消息
messages, err := r.channel.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
forever := make(chan bool)
//启用协程处理消息
go func() {
for d := range messages {
log.Printf("Received a message: %s", d.Body)
}
}()
fmt.Println("退出请按 CTRL+C\n")
<-forever
}
###### 3.2.1.2. mainPubPublish.go
package main
import ( "fmt" "qiutian.com/study/02.rabbitmq/Pub/mq" "strconv" "time" )
func main() { rabbitmq := mq.NewRabbitMQPub("newExchange") for i := 0; i < 100; i++ { rabbitmq.PublishPub("订阅模式生产第" + strconv.Itoa(i) + "条" + "数据") fmt.Println("订阅模式生产第" + strconv.Itoa(i) + "条" + "数据") time.Sleep(1 * time.Second) } }
###### 3.2.1.3. mainPubRecieve1.go
package main
import ( "qiutian.com/study/02.rabbitmq/Pub/mq" )
func main() { rabbitmq := mq.NewRabbitMQPub("" + "newExchange") rabbitmq.ConsumePub() }
###### 3.2.1.4. mainPubRecieve2.go
package main
import ( "qiutian.com/study/02.rabbitmq/Pub/mq" )
func main() { rabbitmq := mq.NewRabbitMQPub("" + "newExchange") rabbitmq.ConsumePub() }
### 4. 路由模式(routing)

路由模式(Routing Mode)是消息队列中一种常见的消息路由模式,它允许消息的发送者(Producer)将消息发送到指定的交换机(Exchange),而交换机则根据消息的路由键(Routing Key)将消息路由到符合条件的队列(Queue)。路由模式提供了一种灵活的消息路由机制,使得消息的分发可以根据不同的路由规则进行定制。
**优点:**
1. **灵活性高:** 路由模式允许根据消息的属性或条件将消息路由到不同的队列或消费者,从而实现灵活的消息处理和路由策略。
2. **解耦性强:** 路由模式可以将消息的生产者和消费者解耦,使它们之间相互独立。生产者只需将消息发送到交换机,而不需要知道具体的消费者是谁,消费者也只需订阅感兴趣的消息,而不需要知道消息的生产者是谁,从而实现了系统的解耦。
3. **灵活的消息过滤和路由:** 路由模式允许根据消息的属性、标签或主题将消息路由到不同的队列或消费者,实现精确的消息过滤和路由,从而提高了系统的灵活性和可扩展性。
4. **支持多种路由策略:** 路由模式支持多种路由策略,如直连路由、主题路由、分发路由等,可以根据不同的业务需求选择合适的路由策略,从而满足不同的应用场景。
**缺点:**
1. **配置复杂:** 路由模式的配置相对复杂,需要定义交换机、队列和绑定关系,以及配置路由规则等,如果配置不当可能会导致消息路由错误或丢失。
2. **性能损耗:** 路由模式需要对消息进行额外的路由和过滤操作,可能会增加系统的消息处理延迟和性能消耗,特别是在消息量较大的情况下。
3. **消息堆积风险:** 如果路由模式配置不当或消息处理能力不足,可能会导致消息堆积的风险,从而影响系统的稳定性和可靠性。
4. **维护成本高:** 路由模式需要维护交换机、队列和绑定关系等配置信息,特别是在系统规模较大或消息路由策略较复杂的情况下,维护成本可能会较高。
**使用场景:**
1. **微服务架构下的消息路由**:在微服务架构中,不同的服务可能需要处理不同类型的消息。通过路由模式,可以将消息根据服务的需求进行路由,确保每个服务只接收到与其相关的消息,从而实现解耦和灵活性。
2. **实时监控与日志分析**:在监控系统或日志分析系统中,需要根据不同的指标或日志级别对数据进行分类和分析。路由模式可以根据数据的属性或标签将数据路由到不同的处理节点,以实现实时监控和日志分析。
3. **任务调度与负载均衡**:在任务调度系统中,任务可能具有不同的优先级或类型,需要根据任务的属性进行分发和调度。通过路由模式,可以将任务路由到不同的工作节点,实现任务的负载均衡和优先级调度。
4. **多级消息过滤与订阅**:在订阅发布系统中,订阅者可能对不同类型或主题的消息感兴趣。路由模式可以根据消息的属性或主题将消息路由到不同的订阅者,实现精确的消息过滤和订阅。
5. **数据同步与复制**:在分布式系统中,可能需要将数据复制到不同的节点或数据中心,以实现数据的备份和灾备。通过路由模式,可以将数据根据不同的条件进行路由和复制,确保数据的一致性和可靠性。
#### 4.1. 目录结构

#### 4.2. 代码实现
##### 4.2.1. rabbitmq.go
package mq
import ( "fmt" "log"
"github.com/streadway/amqp"
)
// MQURL amqp://user:password@host:port/vhost // amqp://是固定参数,这个信息是固定不变的。后面两个是用户名密码ip地址端口号Virtual Host // 如果vhost是“/”就输入/%2F,/%2F代表斜杠 const MQURL = "amqp://test:123123@127.0.0.1:5672/%2F"
// RabbitMQ rabbitMQ结构体 type RabbitMQ struct { conn *amqp.Connection channel *amqp.Channel //队列名称 QueueName string //交换机名称 Exchange string //bind Key 名称 Key string //连接信息 Mqurl string }
// NewRabbitMQ 创建结构体实例 func NewRabbitMQ(queueName string, exchange string, key string) *RabbitMQ { return &RabbitMQ{QueueName: queueName, Exchange: exchange, Key: key, Mqurl: MQURL} }
// Destory 断开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)) } }
// NewRabbitMQRouting 路由模式 创建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 }
// PublishRouting 路由模式发送消息 func (r *RabbitMQ) PublishRouting(message string) { //1.尝试创建交换机 err := r.channel.ExchangeDeclare( //交换机名称 r.Exchange, //交换机类型 "direct", //是否持久化 true, //是否自动删除 false, //true表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定 false, //是否阻塞处理 false, //其他属性 nil, ) r.failOnErr(err, "Failed to declare an exchange")
//2.发送消息
//发送消息
//参数:
//1.交换机
//2.队列,
//3.是否强制性路由到队列,如果为true,根据自身exchange类型和routekey规则无法找到符合条件的队列会把消息返还
//给发送者
//4.是否立即发送
//5.其他属性
//6.发送消息的内容
//调用channel 发送消息到队列中
r.channel.Publish(
r.Exchange,
r.Key,
//如果为true,根据自身exchange类型和routekey规则无法找到符合条件的队列会把消息返还给发送者
false,
//如果为true,当exchange发送消息到队列后发现队列上没有消费者,则会把消息返还给发送者
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
}
// ConsumeRouting routing 模式下消费者 func (r *RabbitMQ) ConsumeRouting() { //1.试探性创建交换机 err := r.channel.ExchangeDeclare( // 交换机名称 r.Exchange, //交换机类型 "direct", //是否持久化 true, // 是否自动删除 false, //YES表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定 false, // false, nil, ) r.failOnErr(err, "Failed to declare an 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,
// routing模式下要指定key
r.Key,
r.Exchange,
false,
nil)
//消费消息
messages, err := r.channel.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
forever := make(chan bool)
//启用协程处理消息
go func() {
for d := range messages {
log.Printf("Received a message: %s", d.Body)
}
}()
fmt.Println("退出请按 CTRL+C\n")
<-forever
}
##### 4.2.2. mainRoutingPublish.go
package main
import ( "fmt" "qiutian.com/study/02.rabbitmq/Routing/mq" "strconv" "time" )
func main() {
routingOne := mq.NewRabbitMQRouting("newRoutingExchange", "newRoutingKey_one")
routingTwo := mq.NewRabbitMQRouting("newRoutingExchange", "newRoutingKey_two")
for i := 0; i < 100; i++ {
if i%2 == 0 {
routingOne.PublishRouting("Hello World! " + strconv.Itoa(i))
} else {
routingTwo.PublishRouting("Hello World! " + strconv.Itoa(i))
}
time.Sleep(time.Second)
fmt.Println(i)
}
}
##### 4.2.3. mianRoutingRecieve1.go
package main
import ( "qiutian.com/study/02.rabbitmq/Routing/mq" )
func main() { routingOne := mq.NewRabbitMQRouting("newRoutingExchange", "newRoutingKey_one") routingOne.ConsumeRouting() }
##### 4.2.4. mianRoutingRecieve2.go
package main
import ( "qiutian.com/study/02.rabbitmq/Routing/mq" )
func main() { routingTwo := mq.NewRabbitMQRouting("newRoutingExchange", "newRoutingKey_two") routingTwo.ConsumeRouting() }
### 5. 话题模式(Topic)

红色Queue:绑定的是usa.#,因此凡是以usa.开头的routing key都会被匹配到
黄色Queue:绑定的是#.news,因此凡是以.news结尾的routing key都会被匹配
话题模式(Topic Mode)是消息队列中一种高级的消息路由模式,它基于消息的主题(Topic)进行消息的订阅和分发。在话题模式中,消息的发送者(Producer)将消息发送到特定的主题,而消息的接收者(Consumer)则根据自己感兴趣的主题进行订阅,从而接收相关的消息。
**工作原理:**
1. **主题定义:** 在话题模式中,主题由一个或多个单词组成,每个单词之间用点号(.)分隔,例如:**stock.usd.nyse**。
2. **通配符匹配:** 话题模式支持两种通配符,分别是**\***(星号)和**#**(井号)。
* + **\***:表示匹配一个单词,例如:**stock.\*.nyse**可以匹配**stock.usd.nyse**和**stock.eur.nyse**等。
+ **#**:表示匹配零个或多个单词,例如:**stock.usd.#**可以匹配**stock.usd.nyse**、**stock.usd.nasdaq**以及**stock.usd**等。
1. **消息路由:** 消息的发送者在发送消息时指定一个主题,而消息的接收者则可以使用通配符来订阅感兴趣的主题。消息队列根据主题的匹配规则将消息路由到符合条件的订阅者。
**应用场景:**
1. **多级消息过滤:** 话题模式可以根据消息的主题进行多级的消息过滤和匹配,从而实现精确的消息路由和分发。
2. **发布/订阅系统:** 话题模式常用于发布/订阅系统中,其中消息的发送者作为发布者,向不同的主题发送消息,而消息的接收者作为订阅者,根据自己的需求订阅感兴趣的主题。
3. **事件驱动架构:** 话题模式可以用于构建事件驱动的架构,其中各个服务之间通过消息队列进行事件的发布和订阅,从而实现服务之间的解耦和灵活的消息传递。
**优点:**
* **灵活性高:** 话题模式支持灵活的主题定义和通配符匹配,可以根据不同的业务需求实现精确的消息路由和分发。
* **解耦性强:** 话题模式可以将消息的生产者和消费者解耦,使它们之间相互独立,从而提高系统的可维护性和可扩展性。
**缺点:**
* **配置复杂:** 话题模式的配置相对复杂,需要定义主题和通配符规则,并确保发送者和接收者之间的匹配规则一致,否则可能导致消息路由错误或丢失。
* **性能损耗:** 话题模式需要对消息进行额外的匹配和路由操作,可能会增加系统的消息处理延迟和性能消耗,特别是在消息量较大的情况下
#### 5.1. 目录结构

#### 5.2. 代码实现
##### 5.2.1. rabbitmq.go
package mq
import ( "fmt" "log"
"github.com/streadway/amqp"
)
// MQURL amqp://user:password@host:port/vhost // amqp://是固定参数,这个信息是固定不变的。后面两个是用户名密码ip地址端口号Virtual Host // 如果vhost是“/”就输入/%2F,/%2F代表斜杠 const MQURL = "amqp://test:123123@127.0.0.1:5672/%2F"
// RabbitMQ rabbitMQ结构体 type RabbitMQ struct { conn *amqp.Connection channel *amqp.Channel //队列名称 QueueName string //交换机名称 Exchange string //bind Key 名称 Key string //连接信息 Mqurl string }
// NewRabbitMQ 创建结构体实例 func NewRabbitMQ(queueName string, exchange string, key string) *RabbitMQ { return &RabbitMQ{QueueName: queueName, Exchange: exchange, Key: key, Mqurl: MQURL} }
// Destory 断开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)) } }
// NewRabbitMQTopic 话题模式 创建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 }
// PublishTopic 话题模式发送消息 func (r *RabbitMQ) PublishTopic(message string) { //1.尝试创建交换机 err := r.channel.ExchangeDeclare( //交换机名称 r.Exchange, //交换机类型 "topic", //是否持久化 true, //是否自动删除 false, //true表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定 false, //是否阻塞处理 false, //其他属性 nil, ) r.failOnErr(err, "Failed to declare an exchange")
//2.发送消息
//发送消息
//参数:
//1.交换机
//2.队列,
//3.是否强制性路由到队列,如果为true,根据自身exchange类型和routekey规则无法找到符合条件的队列会把消息返还
//给发送者
//4.是否立即发送
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!