最近,在我所有的项目中,消息队列以这样或那样的方式出现。通常我使用AWS,我使用SQS和SNS,但我也经常使用RabbitMQ和MQTT。在AWS中,有一些我经常使用的东西来隔离服务。发出消息的过程将消息发送到SNS,我将SNS绑定到SQS。通过这种技术,我可以将N个SQS附加到同一个SNS上。我已经在这里使用了。在AWS中,这样做是非常简单的。今天,我们将用RabbitMQ做同样的事情。事实上,在 RabbitMQ 中做到这一点是非常容易的。我们只需要遵循 RabbitMQ 官方文档中的教程。
该脚本监听一个交换,并将消息重新发送到一个队列主题。基本上与我们在 RabbitMQ 文档中看到的相同。
import settings
from lib.logger import logger
from lib.rabbit import get_channel
channel = get_channel()
channel.exchange_declare(
exchange=settings.EXCHANGE,
exchange_type='fanout')
result = channel.queue_declare(
queue='',
exclusive=True)
queue_name = result.method.queue
channel.queue_bind(
exchange=settings.EXCHANGE,
queue=queue_name)
logger.info(f' [*] Waiting for {settings.EXCHANGE}. To exit press CTRL+C')
channel.queue_declare(
durable=settings.QUEUE_DURABLE,
auto_delete=settings.QUEUE_AUTO_DELETE,
queue=settings.QUEUE_TOPIC
)
def callback(ch, method, properties, body):
channel.basic_publish(
exchange='',
routing_key=settings.QUEUE_TOPIC,
body=body)
logger.info(f'Message sent to topic {settings.QUEUE_TOPIC}. Message: {body}')
channel.basic_consume(
queue=queue_name,
on_message_callback=callback,
auto_ack=True)
channel.start_consuming()
我们使用rabbitmqadmin向交换机发送一条消息,并在队列中看到该消息。
rabbitmqadmin -u username -p password publish exchange=exchange routing_key= payload="hello, world"
我对这个项目的想法是将其部署到docker swarm集群中,并且只在堆栈中添加/删除监听器。
...
exchange2topic1:
image: ${ECR}/exchange2queue:${VERSION}
build:
context: .
dockerfile: Dockerfile
deploy:
restart_policy:
condition: on-failure
depends_on:
- rabbit
command: /bin/sh wait-for-it.sh rabbit:5672 -- python exchange2queue.py
environment:
RABBIT_HOST: rabbit
RABBIT_USER: ${RABBITMQ_USER}
RABBIT_PASS: ${RABBITMQ_PASS}
EXCHANGE: exchange
QUEUE_TOPIC: topic1
QUEUE_DURABLE: 1
QUEUE_AUTO_DELETE: 0
...
有了这种方法,我就可以非常简单地添加和删除新的监听器,而不需要接触发射器。
另外,作为补充,我喜欢添加一个简单的 http api,以允许我使用 post request 向交换器发送消息,而不是使用 RabbitMQ 客户端。这是因为有时我与遗留系统一起工作,使用AMQP客户端并不简单。这就是一个简单的 Flask API
from flask import Flask, request
from flask import jsonify
import settings
from lib.auth import authorize_bearer
from lib.logger import logger
from lib.rabbit import get_channel
import json
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify({"status": "ok"})
@app.route('/publish/<path:exchange>', methods=['POST'])
@authorize_bearer(bearer=settings.API_TOKEN)
def publish(exchange):
channel = get_channel()
try:
message = request.get_json()
channel.basic_publish(
exchange=exchange,
routing_key='',
body=json.dumps(message)
)
logger.info(f"message sent to exchange {exchange}")
return jsonify({"status": "OK", "exchange": exchange})
except:
return jsonify({"status": "NOK", "exchange": exchange})
现在我们可以使用 curl、postman 或任何其他 http 客户端向交易所发射消息。
POST http://localhost:5000/publish/exchange
Content-Type: application/json
Authorization: Bearer super_secret_key
{
"hello": "Gonzalo"
}
此外,在 docker 堆栈中,我想使用一个反向代理来为我的 Flask 应用程序(用 gunicorn 提供服务)和 RabbitMQ 管理控制台提供服务。我正在使用nginx来实现这一点。
upstream api {
server api:5000;
}
server {
listen 8000 default_server;
listen [::]:8000;
client_max_body_size 20M;
location / {
try_files $uri @proxy_to_api;
}
location ~* /rabbitmq/api/(.*?)/(.*) {
proxy_pass http://rabbit:15672/api/$1/%2F/$2?$query_string;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~* /rabbitmq/(.*) {
rewrite ^/rabbitmq/(.*)$ /$1 break;
proxy_pass http://rabbit:15672;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location @proxy_to_api {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://api;
}
}
这就是全部。这里是 docker-compose.yml
version: '3.6'
x-base: &base
image: ${ECR}/exchange2queue:${VERSION}
build:
context: .
dockerfile: Dockerfile
deploy:
restart_policy:
condition: on-failure
depends_on:
- rabbit
services:
rabbit:
image: rabbitmq:3-management
deploy:
restart_policy:
condition: on-failure
ports:
- 5672:5672
environment:
RABBITMQ_ERLANG_COOKIE:
RABBITMQ_DEFAULT_VHOST: /
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS}
nginx:
image: ${ECR}/exchange2queue_nginx:${VERSION}
deploy:
restart_policy:
condition: on-failure
build:
context: .docker/nginx
dockerfile: Dockerfile
ports:
- 8080:8000
depends_on:
- rabbit
- api
api:
<<: *base
container_name: front
command: /bin/sh wait-for-it.sh rabbit:5672 -- gunicorn -w 4 api:app -b 0.0.0.0:5000
deploy:
restart_policy:
condition: on-failure
environment:
RABBIT_HOST: rabbit
RABBIT_USER: ${RABBITMQ_USER}
RABBIT_PASS: ${RABBITMQ_PASS}
API_TOKEN: ${API_TOKEN}
exchange2topic1:
<<: *base
command: /bin/sh wait-for-it.sh rabbit:5672 -- python exchange2queue.py
environment:
RABBIT_HOST: rabbit
RABBIT_USER: ${RABBITMQ_USER}
RABBIT_PASS: ${RABBITMQ_PASS}
EXCHANGE: exchange
QUEUE_TOPIC: topic1
QUEUE_DURABLE: 1
QUEUE_AUTO_DELETE: 0
exchange2topic2:
<<: *base
command: /bin/sh wait-for-it.sh rabbit:5672 -- python exchange2queue.py
environment:
RABBIT_HOST: rabbit
RABBIT_USER: ${RABBITMQ_USER}
RABBIT_PASS: ${RABBITMQ_PASS}
EXCHANGE: exchange
QUEUE_TOPIC: topic2
QUEUE_DURABLE: 1
QUEUE_AUTO_DELETE: 0
完整的代码在我的github 中