使用RabbitMQ在你的Django API之间进行通信
在使用Django REST框架创建你的API之后,你可能会发现你需要使用来自其他Django API的数据来改变或创建你的Django API的数据。
因此需要某种形式的通信来促进这些操作。
简介
我们将使用 RabbitMQ 来实现这种通信。RabbitMQ 是一种消息传递服务,允许微服务相互通信。
它具有以下优势。
- 订单和交付保证
- 冗余性
- 可扩展性
RabbitMQ 的工作方式是让发布者/生产者创建消息并将它们发送到一个带有routing-key 的交换器。消费者然后调用和接收消息并处理它们。
前提条件
对于您来说,重要的是要跟上进度。
- 您能够创建一个 Django 应用程序,并对如何使用[Django REST 框架]构建 API 有基本了解。
- 安装 RabbitMQ。
- 您已经安装了
pika库。您可以运行pip3 install pika。 - 一个合适的IDE,如Pycharm、VS Code等。
经验之谈
在本教程结束时,读者将。
- 对 RabbitMQ 的工作方式有更好的了解。
- 能够使用 RabbitMQ 作为一种消息传递服务在您的 API 之间进行通信。
开始学习
考虑一个场景,其中一个 API 包含有关不同报价及其相关喜欢数的详细信息。我们还有另一个 API,它处理对报价的喜欢,这样用户只能喜欢一个报价一次,当他/她喜欢时,报价的数量就会递增。
我们将创建一个名为Projects 的新目录来保存我们的项目。我们将首先创建一个名为Quotes 的项目,该项目将包含我们的引言。我们还将创建另一个项目Likes ,处理喜欢的问题。
创建一个名为Projects的新目录。Cd进入你的Projects 目录,运行以下命令。
django-admin startproject Quotes
django-admin startproject Likes
你的目录结构现在看起来应该如下。
.
├── Likes
│ ├── Likes
│ │ ├── asgi.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ └── manage.py
└── Quotes
├── manage.py
└── Quotes
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
报价项目
让我们创建一个名为quotes 的应用程序,我们将用它来构建我们的报价API。点击进入Quotes并运行python3 manage.py startapp qoutes 。
然后在你的settings.py 文件中添加以下内容:INSTALLED APPS 。
Quotes/settings.py
'quotes',
'rest_framework',
'corsheaders'
你需要安装Django REST框架和django-cors-headers。你可以运行pip3 install djangorestframework 和pip3 install django-cors-headers 来安装它们。
在MIDDLEWARE 中添加代码段'corsheaders.middleware.CorsMiddleware' ,并设置CORS_ORIGIN_ALLOW_ALL = True ,以便允许我们要使用的其他网站对我们的应用程序进行跨源请求。然而,这并不推荐,因为它让所有的网站都能访问你的网站。
我们可以继续创建我们的模型。
改变你的models.py 文件,使其看起来像这样。
quotes/models.py
from django.db import models
# Create your models here.
class Quote(models.Model):
title = models.CharField(max_length=50)
likes = models.PositiveIntegerField(default=0)
def __dir__(self):
return self.title
创建一个serializers.py 文件来序列化和反序列化报价实例。
在其中添加以下代码。
quotes/serializer.py
from rest_framework import serializers
from .models import *
from django.contrib.auth.models import User
class QuoteSerializer(serializers.ModelSerializer):
class Meta:
model = Quote
fields = '__all__'
class UserSerializer(serializerms.ModelSerializer):
class Meta:
model = User
exclude = ('password', )
让我们创建一些视图来渲染数据到网页上。恰好有几种创建视图的方法,我们的views.py 文件将有两种开始。
将你的views.py 文件编辑成这样。
qoutes/views.py
from django.http import Http404
from django.shortcuts import render
import random
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import *
from .serializers import *
# Create your views here.
class QuoteViewset(viewsets.ViewSet):
def list(self, request):
products = Quote.objects.all()
serializer = QuoteSerializer(products, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request):
serializer = QuoteSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk=None):
product = Quote.objects.get(pk=pk)
serializer = QuoteSerializer(product)
return Response(serializer.data, status=status.HTTP_200_OK)
def update(self, request, pk=None):
product = Quote.objects.get(pk=pk)
serializer = QuoteSerializer(instance=product, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, pk=None):
product = Quote.objects.get(pk=pk)
product.delete()
return Response('Quote deleted')
class UserAPIView(APIView):
def get(self, _):
users = User.objects.all()
return Response(UserSerializer(users).data)
class UserDetailAPIView(APIView):
def get_user(self, pk):
try:
User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_user(pk)
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
然后,我们创建一个url.py 文件来路由我们的视图。
编辑该文件,使之与此相似。
qoutes/url.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register('quotes', views.QuoteViewset, basename='quotes')
urlpatterns = [
path('', include(router.urls)),
path('users', views.UserAPIView.as_view(), name='users'),
path('users/<int:pk>/', views.UserDetailAPIView.as_view(),name='user-details')
]
然后我们将Quotes 文件夹中的urls.py 文件配置成这样。
Quotes/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('quotes.url'))
]
我们现在可以运行python3 manage.py makemigrations ,然后再运行python3 manage.py migrate 。让我们也通过运行python3 manage.py createsuperuser ,创建一个超级用户。
当我们运行python3 manage.py runserver ,我们应该有一个像这样的页面。
http://127.0.0.1:8000/quotes
http://127.0.0.1:8000/users
喜欢的项目
让我们创建一个名为likes 的应用程序。Cd进入Likes并运行python3 manage.py startapp likes ,在你的INSTALLED APPS ,在你的settings.py 文件中添加以下内容。
Likes/settings.py
'rest_framework',
'corsheaders',
'likes'
同样,在MIDDLEWARE 中添加代码段'corsheaders.middleware.CorsMiddleware' ,并设置CORS_ORIGIN_ALLOW_ALL = True 。
我们现在可以着手创建我们的模型,如下所示。
likes/models.py
from django.db import models
# Create your models here.
class Quote(models.Model):
id = models.IntegerField(unique=True, primary_key=True)
title = models.CharField(max_length=200)
def __str__(self):
return self.title
class QuoteUser(models.Model):
user_id = models.IntegerField( blank=True)
quote_id = models.IntegerField(unique=True, blank=True)
def __str__(self):
return f"User id {str(self.user_id)} Product id {str(self.quote_id)}"
我已决定创建一个类似的模型类Quote 以展示我们如何使用 RabbitMQ 传递数据并使用数据创建模型实例。
我们还将有一个额外的模型类QuoteUser 。我们现在将创建应用程序,并在短时间内看到通信和处理如何发生。
现在让我们创建一个serializer.py 文件,处理我们模型实例的序列化。
将该文件编辑成以下几行代码。
likes/serializer.py
from rest_framework import serializers
from .models import *
class QuoteSerializer(serializers.ModelSerializer):
class Meta:
model = Quote
fields = '__all__'
class QuoteUserSerializer(serializers.ModelSerializer):
class Meta:
model = QuoteUser
fields = '__all__'
然后我们在views.py 文件中创建一些视图。
likes/views.py
import requests
from django.shortcuts import render
# Create your views here.
from rest_framework import viewsets, status
from rest_framework import mixins
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import *
from .serializers import *
class QuoteViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
serializer_class = QuoteSerializer
queryset = Quote.objects.all()
class QuoteUserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
serializer_class = QuoteUserSerializer
queryset = QuoteUser.objects.all()
我们还需要创建一个url.py 文件来路由我们的视图。
likes/url.py
from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('quotes', views.QuoteViewSet, basename='quotes')
router.register('quoteusers', views.QuoteUserViewSet, basename='quoteusers')
urlpatterns = [
path('', include(router.urls))
]
我们现在可以运行python3 manage.py makemigrations ,然后再运行python3 manage.py migrate 。让我们也通过运行python3 manage.py createsuperuser ,创建一个具有与上面类似凭证的超级用户。
当我们运行python3 manage.py runserver 1234 ,我们应该有一个与此类似的页面。
http://127.0.0.1:1234/quotes
http://127.0.0.1:1234/quoteusers/
通信
现在我们已经创建了我们的 API,现在是时候对它们进行通信了。正如前面所指出的,RabbitMQ 通过让应用程序异步地发送和接收消息而工作。
我们首先希望在我们的 Likes 项目中创建一个Quote 实例,只要在 Quotes 项目中创建Quote 实例。
我们需要使我们的Quotes应用程序成为生产者,而Likes项目成为消费者。为了开始,让我们在Quotes 项目中的quotes 应用程序中创建一个producer.py 文件。
在其中添加以下几行代码。
quotes/producer.py
import json
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', heartbeat=600, blocked_connection_timeout=300))
channel = connection.channel()
def publish(method, body):
properties = pika.BasicProperties(method)
channel.basic_publish(exchange='', routing_key='likes', body=json.dumps(body), properties=properties)
当这段代码被执行时,会发生以下情况。
- 以下两行负责与 RabbitMQ 服务器建立连接。
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', heartbeat=600, blocked_connection_timeout=300))
channel = connection.channel()
- 然后我们创建一个函数
publish,处理消息的发送。method参数是有关消息的信息,body是要发送的消息。
我们还提供了一个属性作为该方法的内容,并将其作为channel.basic_publish 的参数之一进行传递。我们将使用由exchange='' 表示的默认交换,它让我们指定消息要去哪个队列。
我们已经将队列设置为likes ,并且需要在接收消息的应用程序中声明。
注意:我们设置了
heartbeat参数,因为 RabbitMQ 对空闲连接有超时。这意味着我们将在心跳参数所定义的时间内拥有一个连接。
为了发布消息,我们将在我们的views.py 文件中使用publish 函数。
编辑该文件的外观如下。
quotes/views.py
from django.http import Http404
from django.shortcuts import render
import random
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import *
from .serializers import *
from .producer import publish
# Create your views here.
class QuoteViewset(viewsets.ViewSet):
def list(self, request):
products = Quote.objects.all()
serializer = QuoteSerializer(products, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request):
serializer = QuoteSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
publish('quote_created', serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk=None):
product = Quote.objects.get(pk=pk)
serializer = QuoteSerializer(product)
return Response(serializer.data, status=status.HTTP_200_OK)
def update(self, request, pk=None):
product = Quote.objects.get(pk=pk)
serializer = QuoteSerializer(instance=product, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
publish('quote_updated', serializer.data)
return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, pk=None):
product = Quote.objects.get(pk=pk)
product.delete()
publish('quote_deleted', pk)
return Response('Quote deleted')
class UserAPIView(APIView):
def get(self, _):
users = User.objects.all()
return Response(UserSerializer(users, many=True).data)
class UserDetailAPIView(APIView):
def get_user(self, pk):
try:
User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_user(pk)
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
注意在视图文件中使用
publish函数。每当我们创建、编辑或删除一个Quote实例时,就会发布一条消息,其中包含每个操作的相关数据。
现在我们可以发布消息了,我们现在在我们的Likes项目中创建一个消费者。在Likes目录下创建一个consumer.py 文件。该文件应与manage.py 的结构层次相同。
编辑它以显示如下。
Likes/consumer.py
import json
import pika
import django
from sys import path
from os import environ
path.append('/home/john/Dev/SECTION/Likes/Likes/settings.py') #Your path to settings.py file
environ.setdefault('DJANGO_SETTINGS_MODULE', 'Likes.settings')
django.setup()
from likes.models import Quote
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', heartbeat=600, blocked_connection_timeout=300))
channel = connection.channel()
channel.queue_declare(queue='likes')
def callback(ch, method, properties, body):
print("Received in likes...")
print(body)
data = json.loads(body)
print(data)
if properties.content_type == 'quote_created':
quote = Quote.objects.create(id=data['id'], title=data['title'])
quote.save()
print("quote created")
elif properties.content_type == 'quote_updated':
quote = Quote.objects.get(id=data['id'])
quote.title = data['title']
quote.save()
print("quote updated")
elif properties.content_type == 'quote_deleted':
quote = Quote.objects.get(id=data)
quote.delete()
print("quote deleted")
channel.basic_consume(queue='likes', on_message_callback=callback, auto_ack=True)
print("Started Consuming...")
channel.start_consuming()
以下是我们代码中发生的情况。
1 像我们的生产者应用程序一样,我们设置了一个与 RabbitMQ 服务器的连接。
- 我们还需要设置
DJANGO_SETTINGS_MODULE,因为我们要在我们的likes应用程序之外访问一个模型。
path.append('/home/john/Dev/SECTION/Likes/Likes/settings.py')
environ.setdefault('DJANGO_SETTINGS_MODULE', 'Likes.settings')
django.setup()
from likes.models import Quote
-
我们还需要声明一个将接收消息的队列。它应该与
Quotes项目中producer.py文件中channels.basic_publish函数中的routing_key参数所声明的名称相同。 -
我们创建一个函数
callback,每当收到消息时就会被调用。ch是发生通信的通道。method是有关消息传递的信息。properties是用户定义的消息的属性。body是收到的消息。
在我们的例子中,回调函数处理Quote 实例的创建、更新和删除。property.content_type 是我们在producer.py 文件中声明的属性。
- 我们还指示 RabbitMQ 允许我们的
callback函数从likes队列接收消息。
channel.basic_consume(queue='likes', on_message_callback=callback, auto_ack=True)
- 告诉我们的通道开始接收消息。
channel.start_consuming()
为了开始发送消息,我们需要激活 RabbitMQ 服务器。我在 Ubuntu 上运行,将运行sudo service rabbitmq-server start 。我们还需要通过运行python3 consumer.py 来运行我们的消费者文件。
我将运行以下命令来开始。
当我们创建一个报价实例时,如下所示。
我们还在喜欢的应用程序中创建了一个报价实例,如下所示。
在更新和删除报价实例时,我们也应该能够做到这一点。
Editing a quote at Quotes project
Quote edited at Likes
Deleting a quote at Quotes project
Quote also deleted at Likes
我们现在可以处理对一个报价的喜欢了。我们将首先在我们的likes 应用程序中创建一个producer.py 文件。它将类似于quotes 应用程序中的文件,工作方式也相同。
使其看起来如下。
likes/producer.py
import json , pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', heartbeat=600, blocked_connection_timeout=300))
channel = connection.channel()
def publish(method, body):
properties = pika.BasicProperties(method)
channel.basic_publish(exchange='', routing_key='quotes', body=json.dumps(body), properties=properties)
这一次,我们将把我们的消息发布到一个名为quotes 的队列中。这个队列也需要在接收消息的应用程序中声明,即Quotes 。
我们将在views.py 文件中使用publish 函数。
编辑该文件,看起来如下。
likes/views.py
import requests
from django.shortcuts import render
# Create your views here.
from rest_framework import viewsets, status
from rest_framework import mixins
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import *
from .producer import publish
from .serializers import *
class QuoteViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
serializer_class = QuoteSerializer
queryset = Quote.objects.all()
class QuoteUserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
serializer_class = QuoteUserSerializer
queryset = QuoteUser.objects.all()
@api_view(['GET'])
def like(request, pk, format=None):
query = {'username': 'john'}
req = requests.get('http://127.0.0.1:8000/users', params=query)
data = req.json()
print(data)
try:
for s in range(len(data)):
if data[s]['id']:
quoteuser = QuoteUser.objects.create(user_id=data[s]['id'], quote_id=pk)
quoteuser.save()
publish('quote_liked', pk)
print('Quoteuser created')
return Response('Quote liked...', status=status.HTTP_201_CREATED)
except:
return Response("Quote liked...",status=status.HTTP_400_BAD_REQUEST)
我们首先获得一个符合我们查询的用户实例。现在,我们在每个应用程序中只有一个用户,即超级用户。我们希望一个用户只喜欢一次报价。
当有人喜欢一个报价时,我们就创建一个报价用户,而且这个报价用户只能创建一次。当一个用户喜欢一个报价时,在报价项目中,喜欢的数量会递增一个。
我们将在我们的Quotes 项目中创建一个consumer.py 文件,方法与之前一样。该文件应该如下所示。
Quotes/consumer.py
import json
import pika
from sys import path
from os import environ
import django
path.append('/Quotes/Quotes/settings.py') # Your path to settings.py
environ.setdefault('DJANGO_SETTINGS_MODULE', 'Quotes.settings')
django.setup()
from quotes.models import Quote
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', heartbeat=600, blocked_connection_timeout=300))
channel = connection.channel()
channel.queue_declare(queue='quotes', durable=True)
def callback(ch, method, properties, body):
print(body)
data = json.loads(body)
print(data)
if properties.content_type == 'quote_liked':
quote = Quote.objects.get(id=data)
quote.likes += 1
quote.save()
print("Quote likes increased.")
channel.basic_consume(queue='quotes', on_message_callback=callback)
print("Started Consuming...")
channel.start_consuming()
channel.close()
与之前的消费者一样,我们设置了一个连接,并声明了一个名为quotes 的队列。我们的callback 函数得到一个被喜欢的报价,并将其喜欢数增加1。
我还将运行python3 consumer.py ,以便在一个报价被喜欢时接收信息。
当人们访问一个页面时,例如,http://127.0.0.1:1234/quotes/2/like ,注意现在访问http://127.0.0.1:8000/quotes/ 的变化。ID为2的报价实例的喜欢数现在增加了1。
http://127.0.0.1:1234/quotes/2/like
http://127.0.0.1:8000/quotes/
请注意,当人们试图通过刷新页面再次喜欢某句话时,会返回一条消息说该句话已经被喜欢了。
http://127.0.0.1:1234/quotes/2/like
总结
在本教程中,我们已经了解了 RabbitMQ 工作的基本原理,并学会了如何使用它来处理 Django REST API 中的数据。