Django:使用 Redis PubSub、Node.js 和 Socket.io 的 JSON 通知

Posted

技术标签:

【中文标题】Django:使用 Redis PubSub、Node.js 和 Socket.io 的 JSON 通知【英文标题】:Django: JSON Notifications using Redis PubSub, Node.js & Socket.io 【发布时间】:2014-01-31 01:29:00 【问题描述】:

我偶然发现了这篇文章:http://maxburstein.com/blog/realtime-django-using-nodejs-and-socketio/

这使我朝着正确的方向前进。

我目前有一个 ios 前端和一个 Django 后端。我使用 Gunicorn 为前端应用程序提供数据。我的 iOS 应用程序和我的支持之间的通信是基于 REST 的。我只是来回发送 JSON。我不提供任何网页。只是 JSON 响应。

我已经实现了一个简单的帖子和评论模型:

class Post(models.Model):
         user = models.ForeignKey(User)
         blog = models.CharField(max_length=5000)

class Comment(models.Model):
         comment = models.CharField(max_length=140)
         user = models.ForeignKey(User)
         post_id = models.ForeignKey(Post)
         created_at = models.DateTimeField(auto_now_add=True)

用户可以发表博客文章,其他用户可以对其发表评论。因此,如果 userX 上有一篇博客文章和 userY cmets。我想通知 userX userY 对他/她的帖子发表了评论。

我以前是靠pyAPNS通知用户的;一个使用 Twisted 向 APNS 发送通知的 python 包装器,但如果 userX 为我的应用程序关闭推送通知,那么 userX 将无法接收应用程序内通知。所以我运气不好。

我只关心应用内通知。我仍然希望 userX 在他使用应用程序时接收实时更新。

当用户发出 POST 请求时,Django 可以将消息发布到 Redis 上的通道。 Node.js 将订阅该频道,socket.io 会将其发送给该特定用户。

这是我的 views.py 的精简版本,其中创建了评论对象。我将 id 发送给发表评论的用户、帖子的 id 以及发表博客帖子的用户的 id。用户将使用 json 向此 url 发出 post 请求:http://example.com:8000/upload-comment/

def UploadComment(request):
         data  = json.loads(request.body)
         redis_server = redis.Redis(host='12.345.678.9', port=6379, db=0, password='mypassword')
         newComment = Comment()
         newComment.comment = data['comment']
         newComment.user_id = data['user_id']
         newComment.post_id = data['post_id']
         newComment.save() 
         PostOwner = data['post_owner_id'] #id of the blog post owner
         # Need to send a notification to PostOwner
         response_data = []
         response_data.append(
                 'send_notifcation_to': PostOwner
                  'action': 'comment'
                  'comment_by': newComment.user.username)
         redis_server.publish("notifications", json.dumps(response_data))
         return HttpResponse('Request Successful')

Node.js 实现(根据上面 Max Burstein 的文章)

var http = require('http');
var server = http.createServer().listen(4000);
var io = require('socket.io').listen(server);

这是据我所知 :( 我知道这很可悲,但我还有很多问题。如何将 node.js 订阅到我从 Django 发布到的远程 Redis 服务器?有多少客户端可以连接到这个套接字吗?有限制吗?是为每个客户端创建一个套接字吗?还是每个客户端都在同一个套接字上监听?我可以通过这个套接字将 json 数据发送到一个特定的客户端吗?我知道这是一个巨大的post ,但我急需帮助。如果我有什么不清楚的地方,请告诉我,以便我编辑问题。谢谢!

【问题讨论】:

这篇文章中的node.js 实现比您在此处显示的要多得多。其余的对你不起作用怎么办? 你被Node.js锁定了吗?我已经实现了与 python + celery + websockets 类似的东西。 你想坚持使用套接字和 node.js 吗?或者您更愿意接受redis(pub/sub) + gunicorn + SSE (Server Sent Events) @AamirAdnan SSE with Redis pub sub 是另一种解决方案。但问题是,我知道具体发送给哪个客户吗?只有特定的客户才能收到与他们相关的通知。有点像你的 Instagram 活动提要。每次有人点赞或点赞您的照片时,您都会在浏览器或移动设备中收到通知。 每个用户都有自己订阅的频道。当用户 A 对用户 B 活动执行操作时,您将在用户 B 频道上发送通知。识别渠道有什么困难? 【参考方案1】:

我绝对不会为此使用 node.js。您可以使用 Celery 或 gevent 或其他任何方式在 Python 中处理 websocket。

只需创建一个线程注册到 Redis 并监听新消息。

当用户连接时,将socket放入一个以用户名为索引的弱值哈希中;当给他们的消息到达时,在该哈希中查找目标用户名并将其发送出去。弱值,因为当用户断开连接时它会自行清理。真的很简单。

我还会使用 websocket 而不是 HTTP-PUT 向服务器发送新消息。

【讨论】:

嘿@matthias urliches。刚刚偶然发现您的答案,看起来与我非常相关-您能否详细说明如何使用 django/celery/other 创建这种线程?【参考方案2】:

看看这里的实现: http://blog.sneawo.com/blog/2013/02/08/real-time-messaging-in-django-using-node-dot-js-and-redis/

【讨论】:

【参考方案3】:

听起来你应该使用 RabbitMQ http://www.rabbitmq.com/devtools.html 之类的东西,还有一个 node.js 实现等等。

看看吧,它应该让你振作起来:)

【讨论】:

请至少提供在这种情况下使用 RabbitMQ 的示例的链接。只是提到它并提供指向 devtools 页面的链接只是打开了更多的可能性,而不是进一步帮助他......【参考方案4】:

我也在使用一个很棒的库,叫做 django-websocket-redis。

虽然它不使用 gunicorn,但它使用 uWSGI,在迁移到时应该不会有太多开销。只需按照文档进行操作即可,它们非常全面。

https://django-websocket-redis.readthedocs.org/en/latest/

获取您的一些代码并使用 django-websockets-redis 实现它:

## Make a json representation of your model available.

class Comment(models.Model):
     comment = models.CharField(max_length=140)
     user = models.ForeignKey(User)
     post_id = models.ForeignKey(Post)
     created_at = models.DateTimeField(auto_now_add=True)

    def as_json(self):
        return dict(
        id=self.id,
        comment=self.comment,
        created_at=self.created_at.strftime('%m/%d/%Y'),
        post_id=self.post_id
        user=self.user
        )

然后,在您的视图中,在发出请求时,将 json 表示保存到您订阅的 redis 工具中...您可以在任何地方执行此操作,无论是您的序列化程序还是视图,但我会保留它给你。

def UploadComment(request):
     data  = json.loads(request.body)

     ## In django-websockets-redis, this portion below is registered in the settings.py file, therefore i am commenting this out. 
     ## redis_server = redis.Redis(host='12.345.678.9', port=6379, db=0, password='mypassword')

     newComment = Comment()
     newComment.comment = data['comment']
     newComment.user_id = data['user_id']
     newComment.post_id = data['post_id']
     newComment.save() 
     PostOwner = data['post_owner_id'] #id of the blog post owner
     # Need to send a notification to PostOwner

     ## Need to turn this into a string before posting to redis
     json_comment = str(newComment.as_json())

     ## Create a facility based on the PostOwner ID. The PostOwner will always be subscribed to this channel. 
     RedisPublisher(facility=PostOwner, broadcast=True).publish_message(json_comment)

     return HttpResponse('Request Successful')

现在我们已经处理好了后端,在前端签证 vie javascript 上,您的用户根据他的 user.id 订阅设施:

jQuery(document).ready(function($) 
    var ws4redis = WS4Redis(
    uri: ' WEBSOCKET_URI  PostOwner.id ?subscribe-broadcast&publish-broadcast&echo',
    receive_message: receiveMessage,
    heartbeat_msg:  WS4REDIS_HEARTBEAT 
);

// receive a message though the Websocket from the server
function receiveMessage(msg) 
    //This is where you would retrieve the JSON string from the Redis server, and subsequently manipulate the DOM with appends, Toastr Notifications, Etc...

【讨论】:

以上是关于Django:使用 Redis PubSub、Node.js 和 Socket.io 的 JSON 通知的主要内容,如果未能解决你的问题,请参考以下文章

Scala Redis 中的 PubSub

Redis golang 客户端定期丢弃错误的 PubSub 连接 (EOF)

为啥使用 PUBSUB 订阅时无法 PING?

Redis Pubsub命令用法

ruby redis_pubsub_demo.rb

python 一个简短的脚本,用Python探索Redis pubsub函数