Django中实现一个高性能计数器(Counter)实例

2019-10-06 12:09:25王振洲

            if notication.has_readed:
                return

            notification.has_readed = True
            notification.save()
            # 在这里更新我们的计数器,嗯,我感觉好极了
            self.update_unread_count(-1)

除了使用``select for update``这样的功能,还有一个比较简单的办法来解决这个问题。

使用update来实现原子性修改

其实,更简单的办法,只要把我们的数据库改成单条的update就可以解决并发情况下的问题了:

def mark_as_readed(self, notification_id):
        affected_rows = Notification.objects.filter(pk=notification_id, has_readed=False)
                                            .update(has_readed=True)
        # affected_rows将会返回update语句修改的条目数
        self.update_unread_count(affected_rows)

这样,并发的标记已读操作也可以正确的影响到我们的计数器了。

高性能?

我们在之前介绍了如何实现一个能够正确更新的未读消息计数器,我们可能会直接使用UPDATE 语句来修改我们的计数器,就像这样:

from django.db.models import F

def update_unread_count(self, count)
    # 使用Update语句来更新我们的计数器
    UserNotificationsCount.objects.filter(pk=self.user_id)
                                  .update(unread_count=F('unread_count') + count)
但是在生产环境中,这样的处理方式很有可能造成严重的性能问题,因为如果我们的计数器在频繁 更新的话,海量的Update会给数据库造成不小的压力。所以为了实现一个高性能的计数器,我们 需要把改动暂存起来,然后批量写入到数据库。

使用 redis 的 sorted set ,我们可以非常轻松的做到这一点。

使用sorted set来缓存计数器改动

redis是一个非常好用的内存数据库,其中的sorted set是它提供的一种数据类型:有序集合, 使用它,我们可以非常简单的缓存所有的计数器改动,然后批量回写到数据库。