消息队列是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是/用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。
1.解耦
场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。
传统模式的缺点:
采用消息队列后
2.异步
场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种 1.串行的方式;2.并行方式
(1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端
(2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
(3)引入消息队列,将不是必须的业务逻辑,异步处理。
3.流量削峰
一般应用于秒杀活动中,因为秒杀活动一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
作用: 1.可以控制活动人数,超过此一定阀值的订单直接丢弃 2.可以缓解短时间的高流量压垮应用
4.缺点
系统的可用性降低
例如系统只需要调用abc三个接口,现在引入了mq之后虽然之前调用都没有出错,系统可以正常运行,但是mq出现问题的话,整个系统也同样会受影响
系统的复杂性提高
需要考虑消息是否丢失,还需要考虑消息传递的顺序
一致性问题
系统发送完消息返回成功,但是abc中若有系统写入失败,就会产生数据不一致的问题
1.先将rabbitmq和erlang的软件包上传至阿里云服务器,然后解压安装
rpm -Uvh esl-erlang_23.0-1_centos_7_amd64.rpm
yum install erlang
yum install -y socat
rpm -Uvh rabbitmq-server-3.8.14-1.el7.noarch.rpm
yum install rabbit-server -y
2.启动rabbitmq-server
systemctl start rabbitmq-server
systemctl status rabbitmq-server # 查看状态,
3.添加超级用户(web管理界面)
rabbitmqctl add_user admin admin # 添加用户 用户名 密码
rabbitmqctl set_user_tags admin administrator # 修改用户权限 用户名 权限
rabbitmqctl change_password admin admin # 修改密码
4.运行rabbitmq
docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 8030:15672 -p 8031:5672 -p 8032:25672 -p 8033:61613 -p 8034:1883 rabbitmq:management
(一).simple模式(即最简单的收发模式)
1.消息产生消息,将消息放入队列
2.消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。
//send.go
package main
import (
"log"
"github.com/streadway/amqp"
)
func FailOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
//链接rabbitmq server
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
FailOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
//创建一个通道,用于完成任务
ch,err := conn.Channel()
FailOnError(err,"Failed to open a channel")
defer ch.Close()
//声明一个队列,用于消息发送
q, err := ch.QueueDeclare(
"hello world", // 队列名
false, // 持久化
false, // 是否自动删除
false, // 排他性
false, // no-wait
nil, // 附属参数
)
FailOnError(err, "Failed to declare a queue")
body := "Hello World!"
err = ch.Publish( // 发送消息(生产者)
"", // exchange
q.Name, // 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)
}
//receive.go
package main
import (
"log"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
//连接rabbitmq server
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
//创建一个通道
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
//声明队列
q, err := ch.QueueDeclare(
"hello world", // name
false, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
msgs, err := ch.Consume( // 注册一个消费者(接收消息)
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
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
}
(二).work工作模式(资源的竞争)
1.消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。
2.消息确认
完成一个任务需要消耗时间,如果一个消费者开始了一个任务并在完成期间关闭或者死亡,我们就会丢失他刚刚处理的消息,同时还将丢失所有已经派发给特定worker但未处理的信息。我们可以使用消息确认来避免这样的事情发生,消费者发回一个 ack(nowledgement) 来告诉 RabbitMQ 一个特定的消息已经被接收、处理并且 RabbitMQ 可以自由地删除它。如果消费者在没有发送 ack 的情况下死亡(其通道关闭、连接关闭或 TCP 连接丢失),RabbitMQ 将理解消息未完全处理并将重新排队。如果有其他消费者同时在线,它会迅速将其重新交付给另一个消费者。这样您就可以确保不会丢失任何消息,即使工作人员偶尔会死亡。
3.消息持久性
RabbitMQ服务器停止,如果没有持久化,任务就会丢失,我们只需要在声明队列时将durable参数设置为true,同时将amqp.Publishing中加入DeliveryMode: amqp.Persistent,即可实现持久化。这样即使我们rabbit重启,我们的队列也不会丢失
//new_task.go
package main
import (
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError