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

19

我看到了这篇文章:http://maxburstein.com/blog/realtime-django-using-nodejs-and-socketio/

这篇文章让我有了一些方向感。

我目前拥有一个iOS前端和一个Django后端。我使用Gunicorn将数据提供给前端应用程序。我的iOS应用程序与后端之间的通信基于REST。我只是简单地发送JSON数据。我没有提供任何网页,只有JSON响应。

我已经实现了一个简单的Post和Comment模型:

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)

用户可以发布博客文章,其他用户可以对它们进行评论。因此,如果用户X有一篇博客文章,用户Y对其发表评论。我希望通知用户X,用户Y对他/她的文章发表了评论。

我过去依赖 pyAPNS 来通知用户;这是一个使用Twisted向APNS发送通知的Python包装器,但是如果用户X关闭了我的应用程序的推送通知,那么用户X将无法接收到应用内通知。所以我就没办法了。

我只关心应用内通知。我仍然希望用户X在使用应用程序时能够收到实时更新。

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

这是我的views.py的简化版本,其中创建评论对象。我发送制作者ID、帖子ID和博客文章作者ID。用户将使用json将POST请求发送到此URL: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数据?我知道这是一个很大的帖子,但我非常需要帮助。如果我有什么表述不清的地方,请告诉我,这样我就可以编辑问题。谢谢!


这篇文章中关于node.js的实现比你在这里展示的要多得多。除此之外,还有什么部分不能正常工作呢? - Aaron Dufour
你是否局限于Node.js?我已经使用Python + Celery + Websockets实现了类似的功能。 - Dwight Gunning
你想继续使用sockets和node.js吗?还是你更愿意接受redis(pub/sub) + gunicorn + SSE (Server Sent Events) - Aamir Rind
@AamirAdnan 使用Redis pub sub的SSE是另一种解决方案。但问题是,我该如何知道要将其发送给哪个特定客户端?只有特定的客户端应该接收与他们相关的通知。就像你的Instagram活动提要一样。每当有人评论或喜欢你的照片时,你会在浏览器或移动设备上收到通知。 - deadlock
每个用户都有自己的频道,他们订阅了该频道。当用户A在用户B的活动上执行操作时,您将在用户B的频道上发送通知。在识别这些频道方面有什么困难吗? - Aamir Rind
只是为了给你一点推动,请阅读我在这里的评论:http://dpaste.com/hold/1560307/ - Aamir Rind
4个回答

3
我肯定不会使用Node.js来完成这个任务。您可以使用Python处理WebSockets,例如Celery或gevent等库。
只需创建一个线程来注册Redis并监听新消息即可。
当用户连接时,将套接字放入由用户名索引的弱值哈希表中;当他们的消息到达时,在该哈希表中查找目标用户名并发送相应的消息。使用弱值哈希表是因为它会在用户断开连接后自动清理本身。真的很简单。
我还建议使用WebSocket而非HTTP-PUT将新消息发送到服务器。

1
嘿,@matthias urliches。我刚刚偶然看到了你的回答,看起来对我非常相关 - 你能否详细说明如何使用django/celery/其他创建这种类型的线程? - alonisser


1

2
请至少提供RabbitMQ在这种情况下使用的示例链接。仅仅提到它并提供开发工具页面的链接只会打开更多的可能性,而不是帮助他进一步... - Tino

0

我也在使用一个很棒的库,叫做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')

现在我们已经处理好了后端,通过JavaScript处理前端,您的用户根据他的用户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...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接