引用的是rabbitMQ官方示例的库:github.com/rabbitmq/amqp091-go
在网络编程中我们知道tcp连接的创建、交互、销毁等相关操作的"代价"都是很高的,所以就要去实现如何复用这些连接,并要做到高效并可靠。
预期效果:
项目初始化构建时可以自定义选择生产者开启多个connection,每个connection可以启动多少个channel【都是全局复用的】,因为rabbitMQ所有的命令都是基本都是通过channel去操作完成的,所以这个channel很重要,也是我们想要复用的重点。
初始化创建完connection和channel后,当生产者需要发送一条消息的时候,我们可以通过一些策略去选择它发送到哪个connection和channel,我这里采用的就是随机选择,也可以采用哈希取模、轮询权重算法等,这个可以根据自身业务来做。
我简单画了一个效果图:
定义RabbitMQ的Config、Connection、Channel结构体
-
type Config
struct {
-
Host
string
-
Port
int
-
User
string
-
Password
string
-
}
-
-
type Channel
struct {
-
m *sync.Mutex
-
ch *amqp.Channel
-
}
-
-
type Connection
struct {
-
ctx context.Context
-
n
int
-
conn *amqp.Connection
-
ch []*Channel
-
}
实例化RabbitMQ结构体
-
func (mq *Connection) New(config Config) (rabbitmq *Connection) {
-
-
configString := fmt.Sprintf(
"amqp://%s:%s@%s:%d/", config.User, config.Password, config.Host, config.Port)
-
-
conn, err := amqp.Dial(configString)
-
if err !=
nil {
-
log.Panicf(
"amqp connect error: %v \n", err)
-
}
-
-
rabbitmq = &Connection{
-
ctx: context.Background(),
-
conn: conn,
-
}
-
-
return
-
}
一、创建消费者
-
// ConsumeWithWork rabbitmq消费消息[work模式 channelNums可以设置当前连接开启多少个channel]
-
func (mq *Connection) ConsumeWithWork(queueName
string, channelNums
int) {
-
for i :=
0; i < channelNums; i++ {
-
go
func(i int) {
-
-
ch, err := mq.conn.Channel()
-
if err !=
nil {
-
log.Panicf(
"amqp open a channel error: %v \n", err)
-
}
-
-
q, err := ch.QueueDeclare(
-
queueName,
// name
-
true,
// durable
-
false,
// delete when unused
-
false,
// exclusive
-
false,
// no-wait
-
nil,
// arguments
-
)
-
if err !=
nil {
-
log.Panicf(
"amqp declare a queue error: %v \n", err)
-
}
-
-
err = ch.Qos(
-
1,
// prefetch count
-
0,
// prefetch size
-
false,
// global
-
)
-
if err !=
nil {
-
log.Panicf(
"amqp set QoS error: %v \n", err)
-
}
-
-
msg, err := ch.Consume(
-
q.Name,
// queue
-
"",
// consumer
-
false,
// auto-ack
-
false,
// exclusive
-
false,
// no-local
-
false,
// no-wait
-
nil,
// args
-
)
-
if err !=
nil {
-
log.Panicf(
"amqp register a consumer error: %v \n", err)
-
}
-
-
log.Printf(
" [work-%d] Waiting for messages. To exit press CTRL+C", i)
-
-
for d :=
range msg {
-
time.Sleep(time.Second)
-
fmt.Printf(
"[work-%d] Received a message: %s \n", i, d.Body)
-
err = d.Ack(
false)
-
if err !=
nil {
-
log.Printf(
"work_one Ack Err: %v", err)
-
}
-
}
-
}(i)
-
}
-
-
var forever
chan
struct{}
-
<-forever
-
}
二、创建生产者组
-
// NewPlusherGroups 创建生产者组
-
func NewPlusherGroups(config Config, connNums, channelNums int) (plusherGroups
map[
int]*Connection) {
-
-
plusherGroups =
make(
map[
int]*Connection, connNums)
-
-
for i :=
0; i < connNums; i++ {
-
-
var rabbitmq *Connection
-
rabbitmq = rabbitmq.New(config)
-
rabbitmq.n = i
-
-
for cN :=
0; cN < channelNums; cN++ {
-
ch, err := rabbitmq.conn.Channel()
-
if err !=
nil {
-
log.Panicf(
"amqp open a channel error: %v \n", err)
-
}
-
rabbitmq.ch =
append(rabbitmq.ch, &Channel{ch: ch, m: &sync.Mutex{}})
-
}
-
-
plusherGroups[i] = rabbitmq
-
}
-
return
-
}
三、将消息随机分发给不同的connection、channel
-
// SendMessageWithWork 生产者发送消息[work模式+(many conn and many channel)]
-
func SendMessageWithWork(plusherGroups map[int]*Connection, queueName, body string)
bool {
-
-
if plusherGroups ==
nil {
-
log.Panicln(
"SendMessageWithWork plusherGroups params is nil!")
-
}
-
-
rand.Seed(time.Now().UnixNano())
-
-
//获取连接个数
-
connNums :=
len(plusherGroups)
-
-
//随机分配一个连接对象
-
randConnIndex := rand.Intn(connNums)
-
-
//选择随机分配的连接对象
-
conn := plusherGroups[randConnIndex]
-
-
//获取当前对象的channel个数
-
channelNums :=
len(conn.ch)
-
-
//随机分配一个channel对象
-
randChannelIndex := rand.Intn(channelNums)
-
-
//选择随机分配的channel
-
ch := conn.ch[randChannelIndex]
-
-
//既然采用了发布者复用conn、channel的形式那么一定要加锁处理
-
//这里为每个对象的操作进行加锁(非线程安全,不加锁会报错的)
-
//至于在存在并发竞争的情况下会存在一定性能损耗,但是我们配置好适量的conn和channel这个基本可以忽略
-
ch.m.Lock()
-
defer ch.m.Unlock()
-
-
q, err := ch.ch.QueueDeclare(
-
queueName,
// name
-
true,
// durable
-
false,
// delete when unused
-
false,
// exclusive
-
false,
// no-wait
-
nil,
// arguments
-
)
-
if err !=
nil {
-
log.Panicf(
"amqp declare a queue error: %v \n", err)
-
}
-
-
body = fmt.Sprintf(
"conn[%d] channel[%d] send message : %s", randConnIndex, randChannelIndex, body)
-
err = ch.ch.PublishWithContext(conn.ctx,
-
"",
// exchange
-
q.Name,
// routing key
-
false,
// mandatory
-
false,
-
amqp.Publishing{
-
DeliveryMode: amqp.Persistent,
-
ContentType:
"text/plain",
-
Body: []
byte(body),
-
})
-
if err !=
nil {
-
log.Panicf(
"amqp publish a message error: %v \n", err)
-
}
-
-
return
true
-
}
四、main函数调用消费者
-
package main
-
-
import (
-
rabbitmq
"go-test/rabbitmq/package"
-
)
-
-
func main() {
-
-
queueName :=
"task_queue"
-
-
config := rabbitmq.Config{
-
Host:
"192.168.6.103",
-
Port:
5672,
-
User:
"root",
-
Password:
"root",
-
}
-
-
var mq *rabbitmq.Connection
-
mq = mq.New(config)
-
-
//开启N个消费者
-
mq.ConsumeWithWork(queueName,
3)
-
}
五、main函数调用生产者组发送消息
-
package main
-
-
import (
-
"fmt"
-
"github.com/gin-gonic/gin"
-
rabbitmq
"go-test/rabbitmq/package"
-
"net/http"
-
"time"
-
)
-
-
func main() {
-
-
var messageNo
int
-
-
queueName :=
"task_queue"
-
-
config := rabbitmq.Config{
-
Host:
"192.168.6.103",
-
Port:
5672,
-
User:
"root",
-
Password:
"root",
-
}
-
-
//conn连接数
-
connNums :=
2
-
//channel连接数
-
channelNums :=
3
-
-
//启动N个不同conn的连接,并且每个连接对应的channel为N个的rabbitmq实例
-
plusherGroup := rabbitmq.NewPlusherGroups(config, connNums, channelNums)
-
-
-
e := gin.Default()
-
e.GET(
"/",
func(c *gin.Context) {
-
-
body := fmt.Sprintf(
"这是第%d条消息...", messageNo)
-
-
if rabbitmq.SendMessageWithWork(plusherGroup, queueName, body) ==
true {
-
messageNo++
-
c.JSON(
200, gin.H{
-
"code":
200,
-
"msg":
"success",
-
})
-
}
else {
-
c.JSON(
200, gin.H{
-
"code":
500,
-
"msg":
"error",
-
})
-
}
-
})
-
-
server := &http.Server{
-
Addr:
":18776",
-
Handler: e,
-
ReadTimeout: time.Minute,
-
WriteTimeout: time.Minute,
-
}
-
if err := server.ListenAndServe(); err !=
nil {
-
panic(any(
"HttpServer启动失败"))
-
}
-
}
执行流程:
启动消费者进程
可以看到我们用3个协程开启了3个work,也就是对应了3个channel
启动生产者组进程
这里用的gin框架,正常启动
我们可以看到rabbitMQ的控制台中,一共3个连接,1个是消费者进程,另外2个是生产者组进程,这2个正好和我们上面配置的connNums参数匹配
我们可以看到rabbitMQ的控制台中,一共9个channel,3个是消费者进程,另外6个是生产者组进程,这6个正好和我们上面配置的channelNums参数匹配
调用发送消息
ab.exe -n 1000 -c 1000 http://127.0.0.1:18776/
我们来看消费者日志打印情况,标红的可以证明我们在发送消息时让生产者根据我们的随机分配策略选择connection和channel
转载:https://blog.csdn.net/Enjun97/article/details/128974918