技术控

    今日:55| 主题:49157
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Django Channels: Using Custom Channels

[复制链接]
心痛heartache 发表于 7 天前
30 0

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
In my earlier blog post -    Introduction to Django Channels, I mentioned that we can create our own channels for various purposes. In this blog post, we would discuss where custom channels can be useful, what could be the challenges and of course we would see some code examples. But before we begin, please make sure you are familiar with the concepts of Django Channels. I would recommend going through the above mentioned post and the official docs to familiarize yourself with the basics.  
  Our Use Case

  Channels is just a queue which has consumers (workers) listenning to it. With that concept in mind, we might be able to think of many innovative use cases a queue could have. But in our example, we will keep the idea simple. We are going to use Channels as a means of background task processing.
  We will create our own channels for different tasks. There will be consumers waiting for messages on these channels. When we want to do something in the background, we would pass it on the appropriate channels & the workers will take care of the tasks. For example, we want to create a thumbnail of an user uploaded photo? We pass it to the    thumbnailschannel. We want to send a confirmation email, we send it to the    welcome_emailchannel. Like that. If you are familiar with Celery or Python RQ, this would sound pretty familiar to you.  
  Now here’s my use case - in one of the projects I am working on, we’re building APIs for mobile applications. We use BrainTree for payment integration. The mobile application sends a    nonce- it’s like a token that we can use to initiate the actual transaction. The transaction has two steps - first we initiate it using the nonce and I get back a transaction id. Then I query whether the transaction succeeded or failed. I felt it would be a good idea to process this in the background. We already have a websocket end point implemented using Channels. So I thought it would be great to leverage the existing setup instead of introducing something new in the stack.  
  Challenges

  It has so far worked pretty well. But we have to remember that Channels does not gurantee delivery of the messages and there is no retrying if a message fails. So we wrote a custom management command that checks the orders for any records that have the nonce set but no transaction id or there is transaction id but there is no final result stored. We then scheduled this command to run at a certain interval and queue up the unfinished/incomplete orders again. In our case, it doesn’t hurt if the orders need some 5 to 10 minutes to process.
  But if we were working on a product where the message delivery was time critical for our business, we probably would have considered Celery for the background processing part.
  Let’s see the codes!

  First we needed to write a handler. The hadler would receive the messages on the subscribed channel and process them. Here’s the handler:
  1. def braintree_process(message):
  2.     order_data = message.content.get('order')
  3.     order_id = message.content.get('order_id')
  4.     order_instance = Order.objects.get(pk=order_id)
  5.     if order_data:
  6.         nonce = order_data.get("braintree_nonce")
  7.         if nonce:
  8.             # [snipped]
  9.             TRANSACTION_SUCCESS_STATUSES = [
  10.                 braintree.Transaction.Status.Authorized,
  11.                 braintree.Transaction.Status.Authorizing,
  12.                 braintree.Transaction.Status.Settled,
  13.                 braintree.Transaction.Status.SettlementConfirmed,
  14.                 braintree.Transaction.Status.SettlementPending,
  15.                 braintree.Transaction.Status.Settling,
  16.                 braintree.Transaction.Status.SubmittedForSettlement
  17.             ]
  18.             result = braintree.Transaction.sale({
  19.                 'amount': str(order_data.get('total')),
  20.                 'payment_method_nonce': nonce,
  21.                 'options': {
  22.                     "submit_for_settlement": True
  23.                 }
  24.             })
  25.             if result.is_success or result.transaction:
  26.                 transaction = braintree.Transaction.find(result.transaction.id)
  27.                 if transaction.status in TRANSACTION_SUCCESS_STATUSES:
  28.                     # [snipped]
  29.                 else:
  30.                     # [snipped]
  31.             else:
  32.                 errors = []
  33.                 for x in result.errors.deep_errors:
  34.                     errors.append(str(x.code))
  35.                 # [snipped]
复制代码
Then we needed to define a routing so the messages on a certain channel is passed on to this handler. So in our channel routing, we added this:
  1. from channels.routing import route
  2. from .channel_handlers import braintree_process
  3. channel_routing = [
  4.     route("braintree_process", braintree_process),
  5.     # [snipped] ...
  6. ]
复制代码
We now have a routing set and a handler ready to accept messages. So we’re ready! All we need to do is to start passing the data to this channel.
  When the API receives a    nonce, it just passes the order details to this channel:  
  1. Channel("braintree_process").send({
  2.     "order": data,
  3.     "order_id": order.id
  4. })
复制代码
And then the workers start working. They accept the message and then starts processing the payment request.
  In our case, we already had the workers running (since they were serving our websocket requests). If you don’t have any workers running, don’t forget to run them.
  1. python manage.py runworker
复制代码
If you are wondering about how to deploy channels, I have you covered -    Deploying Django Channels using Daphne  
  Prioritizing / Scaling Channels

  In our project, Django Channels do two things - handling websocket connections for realtime communication, process delayed jobs in background. As you can probably guess, the realtime part is more important. In our current setup, the running workers handle both types of requests as they come. But we want to dedicate more workers to the websocket and perhaps just one worker should keep processing the payments.
  Luckily, we can limit our workers to certain channels using the    --only-channelsflag. Or alternatively we can exclude certain channels by using the    --exclude-channelsflags.  
  Concluding Thoughts

  I personally find the design of channels very straightforward, simple and easy to reason about. When Channels get merged into Django, it’s going to be quite useful, not just for implementing http/2 or websockets, but also as a way to process background tasks with ease and without introducing third party libraries.
友荐云推荐




上一篇:Inverted Index Project
下一篇:[原]CUBIC拥塞控制算法是天生干坏事的吗
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表