GO实现RabbitMQ【订阅/发布】

RabbitMQ 概述:

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而群集和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。

RabbitMQ中的几个主要概念:

  1. Producer (生产者) : 消息的生产者,投递方

  2. Consumer (消费者) : 消息的消费者

  3. RabbitMQ Broker (RabbitMQ 代理) : RabbitMQ 服务节点(单机情况中,就是代表RabbitMQ服务器)

  4. Queue (队列) : 在RabbitMQ中Queue是存储消息数据的唯一形式

  5. Binding (绑定) : RabbitMQ中绑定(Binding)是交换机(exchange)将消息(message)路由给队列(queue)所需遵循的规则。如果要指示交换机“E”将消息路由给队列“Q”,那么“Q”就需要与“E”进行绑定。绑定操作需要定义一个可选的路由键(routing key)属性给某些类型的交换机。路由键的意义在于从发送给交换机的众多消息中选择出某些消息,将其路由给绑定的队列。

  6. RoutingKey (路由键) : 消息投递给交换器,通常会指定一个 RoutingKey ,通过这个路由键来明确消息的路由规则

RabbitMQ大致的工作流程:

1.客户端连接到消息队列服务器,打开一个channel。
2.户端声明一个exchange,并设置相关属性。
3.客户端声明一个queue,并设置相关属性。
4.户端使用routing key,在exchange和queue之间建立好绑定关系。
5.客户端投递消息到exchange。

具体流程详见:Go语言与RabbitMQ - 简书 (jianshu.com)

扇型交换机:(订阅/发布)

扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列,而不理会绑定的路由键。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的拷贝分别发送给这所有的N个队列。扇型用来交换机处理消息的广播路由(broadcast routing)。

因为扇型交换机投递消息的拷贝到所有绑定到它的队列,所以他的应用案例都极其相似:

  • 大规模多用户在线(MMO)游戏可以使用它来处理排行榜更新等全局事件
  • 体育新闻网站可以用它来近乎实时地将比分更新分发给移动客户端
  • 分发系统使用它来广播各种状态和配置更新
  • 在群聊的时候,它被用来分发消息给参与群聊的用户。(AMQP没有内置presence的概念,因此XMPP可能会是个更好的选择)

扇形交换机(fanout )没有路由键,会直接把接收到的message发送给所有绑定到的所有队列中

 

 废话不说,上代码

我这里实现的是给多个用户,发送同一条消息,在数据库中记录消息信息以及用户id和消息id,数据可在postman中测试

1.首先封装连接RebbitMQ的方法

安装RabbitMQ扩展包

go get github.com/streadway/amqp

package lib

import (
	"github.com/streadway/amqp"
	"log"
)

func RabbitMQConn() (conn *amqp.Connection, err error) {
	//RabbitMq分配的用户名
	user := "guest"
	//RabbitMq分配的密码
	password := "guest"
	//RabbitMq Broker ip地址
	host := "127.0.0.1"
	//RabbitMq Broker 监听端口号
	port := "5672"
	//连接地址
	url := "amqp://" + user + ":" + password + "@" + host + ":" + port + "/"
	//新建一个连接
	conn, err = amqp.Dial(url)
	return conn, err
}

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

2.发布

// Fan 发布
func Fan(content string, uids string) error {
	//建立连接
	conn, err := RabbitMQConn()
	if err != nil {
		return err
	}
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	err = ch.ExchangeDeclare(
		"message", // name
		"fanout",  // type
		true,      // durable
		false,     // auto-deleted
		false,     // internal
		false,     // no-wait
		nil,       // arguments
	)
	failOnError(err, "Failed to declare an exchange")
    //拼接messageid + 用户ids      28;1,3,5,7;
	//bodyFrom(content) 得到返回消息id  但是这里用于拼接字符串所以strconv.Itoa()把int转string
	body := strconv.Itoa(bodyFrom(content)) + ";" + uids + ";"
	err = ch.Publish(
		"message", // exchange
		"",        // routing key
		false,     // mandatory
		false,     // immediate
		amqp.Publishing{
			ContentType: "text/plain",
			Body:        []byte(body),
		})
	failOnError(err, "Failed to publish a message")

	log.Printf(" [x] Sent %s", body)
	return err
}

func bodyFrom(args string) int {
    //拿到消息体入库,返回添加入库后的id
	id, _ := models.SendMessage(args)
	return id
}

ExchangeDeclare()参数讲解:

  • name 交换机名称

  • type交换机类型

  • durable 持久化标识

  • autoDelete 是否自动删除

  • internal 是否是内置交换机

  • noWait 是否等待服务器确认

  • args 其它配置

3.订阅

// Take 订阅
func Take() error {
	//建立连接
	conn, err := RabbitMQConn()
	if err != nil {
		return err
	}
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	err = ch.ExchangeDeclare(
		"message", // name
		"fanout",  // type
		true,      // durable
		false,     // auto-deleted
		false,     // internal
		false,     // no-wait
		nil,       // arguments
	)
	failOnError(err, "Failed to declare an exchange")

	q, err := ch.QueueDeclare(
		"MessageDo", // name
		false,       // durable
		false,       // delete when usused
		true,        // exclusive
		false,       // no-wait
		nil,         // arguments
	)
	failOnError(err, "Failed to declare a queue")

	err = ch.QueueBind(
		q.Name,    // queue name
		"",        // routing key
		"message", // exchange
		false,
		nil,
	)
	failOnError(err, "Failed to bind a queue")

	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		true,   // auto-ack
		false,  // exclusive
		false,  // no-local
		false,  // no-wait
		nil,    // args
	)

	forever := make(chan bool)

	go func() {
		for d := range msgs {
			log.Printf(" [x] %s", d.Body)
			//字节传字符串
			s := string(d.Body[:])
			MessageDo(s)
		}
	}()
	<-forever
	return err
}

func MessageDo(message string) {
	// 28;1,3,5,7;
	arr := strings.Split(message, ";")
	//[0:1]是messageId   arr[1:2]是userid
	s := strings.Join(arr[0:1], " ")
	messageId, _ := strconv.Atoi(s)
	uids := strings.Join(arr[1:2], ",")
	ids := strings.Split(uids, ",")
	for _, v := range ids {
		i, _ := strconv.Atoi(v)
		models.MessageRecordDo(i, messageId)
	}
}

 

生产者需要bee run 一次,消费者也需要bee run一次,需要分成两个main.go来写  

资源:

RabbitMQ官网:

RabbitMQ Tutorials — RabbitMQ

RabbitMQ中文文档:

RabbitMQ中文文档 · RabbitMQ in Chinese (mr-ping.com)

这篇文章部分转自:

Go语言与RabbitMQ - 简书 (jianshu.com)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值