Maison développement back-end Tutoriel Python Compteur de notifications de messages dans le framework Django de Python

Compteur de notifications de messages dans le framework Django de Python

Mar 03, 2017 pm 01:14 PM
django 计数器

Le début de l'histoire : .count()

Supposons que vous ayez une classe de modèle de notification qui enregistre principalement toutes les notifications du site :

class Notification(models.Model):
  """一个简化过的Notification类,拥有三个字段:

  - `user_id`: 消息所有人的用户ID
  - `has_readed`: 表示消息是否已读
  """

  user_id = models.IntegerField(db_index=True)
  has_readed = models.BooleanField(default=False)
Copier après la connexion

Bien sûr, vous obtiendrez d'abord le nombre de messages non lus d'un certain utilisateur grâce à cette requête :

# 获取ID为3074的用户的未读消息数
Notification.objects.filter(user_id=3074, has_readed=False).count()
Copier après la connexion

Lorsque votre table de notification est relativement petite, cette méthode ne pose aucun problème, mais lentement, à mesure que le volume d'affaires augmente. Il y a des centaines de millions de données dans la table des messages. De nombreux utilisateurs paresseux ont des milliers de messages non lus.

À ce stade, vous devez implémenter un compteur pour compter le nombre de messages non lus pour chaque utilisateur. De cette façon, par rapport au count() précédent, il suffit d'exécuter une simple requête de clé primaire (. ou Mieux encore), vous pouvez obtenir le nombre de messages non lus en temps réel.

Meilleure solution : créer un compteur
Tout d'abord, créons un nouveau tableau pour stocker le nombre de messages non lus pour chaque utilisateur.

class UserNotificationsCount(models.Model):
  """这个Model保存着每一个用户的未读消息数目"""

  user_id = models.IntegerField(primary_key=True)
  unread_count = models.IntegerField(default=0)

  def __str__(self):
    return &#39;<UserNotificationsCount %s: %s>&#39; % (self.user_id, self.unread_count)
Copier après la connexion

Nous fournissons à chaque utilisateur enregistré un enregistrement UserNotificationsCount correspondant pour enregistrer son nombre de messages non lus. Chaque fois que vous obtenez le nombre de messages non lus, vous n'avez besoin que de UserNotificationsCount.objects.get(pk=user_id).unread_count.

Ensuite, voici le nœud de la question, comment savoir quand nous devons mettre à jour nos compteurs ? Django propose-t-il des raccourcis à cet égard ?

Défi : Mettre à jour votre compteur en temps réel

Pour que notre compteur fonctionne correctement, nous devons le mettre à jour en temps réel, ce qui comprend :

  • Lorsqu'un nouveau message non lu arrive, le compteur est à 1

  • Lorsque le message est supprimé anormalement, si le message associé est non lu, le compteur est à - 1

  • Lorsqu'un nouveau message est lu, le compteur est à -1

Résolvons ces situations une à une.

Avant de lancer la solution, nous devons introduire une fonction dans Django : Signaux est un mécanisme de notification d'événements fourni par Django, qui vous permet d'écouter certains événements personnalisés ou prédéfinis, lorsque ces événements se produisent. , les méthodes définies par l'implémentation sont appelées.

Par exemple, django.db.models.signals.pre_save & django.db.models.signals.post_save représentent des événements qui seront déclenchés avant et après qu'un modèle appelle la méthode de sauvegarde. C'est la même chose que le. déclencheur fourni par la base de données. Il existe une certaine similitude dans les fonctionnalités.

Pour plus d'informations sur les signaux, veuillez vous référer à la documentation officielle. Jetons un coup d'œil aux avantages que les signaux peuvent apporter à nos compteurs.

1. Lorsqu'un nouveau message arrive, réglez le compteur 1

Cette situation devrait être mieux gérée en utilisant les signaux de Django, cela ne prend que peu de temps avec quelques lignes. de code, nous pouvons implémenter la mise à jour du compteur dans ce cas :

from django.db.models.signals import post_save, post_delete

def incr_notifications_counter(sender, instance, created, **kwargs):
  # 只有当这个instance是新创建,而且has_readed是默认的false才更新
  if not (created and not instance.has_readed):
    return

  # 调用 update_unread_count 方法来更新计数器 +1
  NotificationController(instance.user_id).update_unread_count(1)

# 监听Notification Model的post_save信号
post_save.connect(incr_notifications_counter, sender=Notification)
Copier après la connexion

De cette façon, chaque fois que vous utilisez Notification.create ou .save( ), notre NotificationController sera notifié avec un compteur de 1.

Mais veuillez noter que, comme nos compteurs sont basés sur des signaux Django, si vous utilisez du SQL brut quelque part dans votre code et n'ajoutez pas de nouvelles notifications via la méthode Django ORM, nos compteurs ne recevront pas de notifications, c'est donc Il est préférable de standardiser toutes les nouvelles méthodes de création de notifications, par exemple en utilisant la même API.

2. Lorsqu'un message est supprimé anormalement, si le message associé n'est pas lu, le compteur est à -1

A la première expérience, cette situation est aussi relativement simple à gérer. Il vous suffit de surveiller le signal post_delete de Notification. Voici un exemple de code :

def decr_notifications_counter(sender, instance, **kwargs):
  # 当删除的消息还没有被读过时,计数器 -1
  if not instance.has_readed:
    NotificationController(instance.user_id).update_unread_count(-1)

post_delete.connect(decr_notifications_counter, sender=Notification)
Copier après la connexion


À ce stade, Notification L'événement de suppression peut également mettre à jour notre compteur normalement.

3. Lors de la lecture d'un nouveau message, le compteur est -1

Ensuite, lorsque l'utilisateur lit un message non lu, nous devons également mettre à jour notre compteur de messages non lus . Vous pourriez vous demander : qu’est-ce qu’il y a de si difficile là-dedans ? Puis-je simplement mettre à jour mon compteur manuellement dans ma méthode de lecture des messages ?

Par exemple :

class NotificationController(object):

  ... ...

  def mark_as_readed(self, notification_id):
    notification = Notification.objects.get(pk=notification_id)
    # 没有必要重复标记一个已经读过的通知
    if notication.has_readed:
      return

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

Grâce à quelques tests simples, vous pouvez sentir que votre compteur fonctionne très bien, mais, cette implémentation La méthode a un problème très fatal. Cette méthode ne peut pas gérer normalement les demandes simultanées.

Par exemple, vous avez un objet message non lu avec un ID de 100. À ce moment, deux demandes arrivent en même temps. Vous devez marquer cette notification comme lue :

<🎜. >

# 因为两个并发的请求,假设这两个方法几乎同时被调用
NotificationController(user_id).mark_as_readed(100)
NotificationController(user_id).mark_as_readed(100)
Copier après la connexion

Évidemment, les deux méthodes marqueront avec succès cette notification comme lue, car dans le cas de concurrence, vérifie comme si notification.has_readed ne peut pas fonctionner correctement, donc nous Le compteur sera incorrect - 1 deux fois, mais en fait nous ne lisons qu'une seule demande.

Alors, comment résoudre ce problème ?

Fondamentalement, il n'existe qu'une seule façon de résoudre les conflits de données provoqués par des requêtes concurrentes : le verrouillage. Deux solutions relativement simples sont introduites :

使用 select for update 数据库查询

select ... for update 是数据库层面上专门用来解决并发取数据后再修改的场景的,主流的关系数据库 比如mysql、postgresql都支持这个功能, 新版的Django ORM甚至直接提供了这个功能的shortcut 。 关于它的更多介绍,你可以搜索你使用的数据库的介绍文档。

使用 select for update 后,我们的代码可能会变成这样:

from django.db import transaction

class NotificationController(object):

  ... ...

  def mark_as_readed(self, notification_id):
    # 手动让select for update和update语句发生在一个完整的事务里面
    with transaction.commit_on_success():
      # 使用select_for_update来保证并发请求同时只有一个请求在处理,其他的请求
      # 等待锁释放
      notification = Notification.objects.select_for_update().get(pk=notification_id)
      # 没有必要重复标记一个已经读过的通知
      if notication.has_readed:
        return

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

除了使用``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)
Copier après la connexion

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

高性能?
我们在之前介绍了如何实现一个能够正确更新的未读消息计数器,我们可能会直接使用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(&#39;unread_count&#39;) + count)
Copier après la connexion

但是在生产环境中,这样的处理方式很有可能造成严重的性能问题,因为如果我们的计数器在频繁 更新的话,海量的Update会给数据库造成不小的压力。所以为了实现一个高性能的计数器,我们 需要把改动暂存起来,然后批量写入到数据库。

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

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

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

RK_NOTIFICATIONS_COUNTER = &#39;ss_pending_counter_changes&#39;

def update_unread_count(self, count):
  """修改过的update_unread_count方法"""
  redisdb.zincrby(RK_NOTIFICATIONS_COUNTER, str(self.user_id), count)

# 同时我们也需要修改获取用户未读消息数方法,使其获取redis中那些没有被回写
# 到数据库的缓冲区数据。在这里代码就省略了
Copier après la connexion

通过以上的代码,我们把计数器的更新缓冲在了redis里面,我们还需要一个脚本来把这个缓冲区 里面的数据定时回写到数据库中。

通过自定义django的command,我们可以非常轻松的做到这一点:

# File: management/commands/notification_update_counter.py

# -*- coding: utf-8 -*-
from django.core.management.base import BaseCommand
from django.db.models import F

# Fix import prob
from notification.models import UserNotificationsCount
from notification.utils import RK_NOTIFICATIONS_COUNTER
from base_redis import redisdb

import logging
logger = logging.getLogger(&#39;stdout&#39;)


class Command(BaseCommand):
  help = &#39;Update UserNotificationsCounter objects, Write changes from redis to database&#39;

  def handle(self, *args, **options):
    # 首先,通过 zrange 命令来获取缓冲区所有修改过的用户ID
    for user_id in redisdb.zrange(RK_NOTIFICATIONS_COUNTER, 0, -1):
      # 这里值得注意,为了保证操作的原子性,我们使用了redisdb的pipeline
      pipe = redisdb.pipeline()
      pipe.zscore(RK_NOTIFICATIONS_COUNTER, user_id)
      pipe.zrem(RK_NOTIFICATIONS_COUNTER, user_id)
      count, _ = pipe.execute()
      count = int(count)
      if not count:
        continue

      logger.info(&#39;Updating unread count user %s: count %s&#39; % (user_id, count))
      UserNotificationsCount.objects.filter(pk=obj.pk)\
                     .update(unread_count=F(&#39;unread_count&#39;) + count)
Copier après la connexion

之后,通过 python manage.py notification_update_counter 这样的命令就可以把缓冲区 里面的改动批量回写到数据库了。我们还可以把这个命令配置到crontab中来定义执行。

总结
文章到了这里,一个简单的“高性能”未读消息计数器算是实现完了。说了这么多,其实主要的知识点就是这么些:

使用Django的signals来获取Model的新建/删除操作更新
使用数据库的select for update来正确处理并发的数据库操作
使用redis的sorted set来缓存计数器的修改操作
希望能对您有所帮助。 :)

更多Python的Django框架中消息通知的计数器相关文章请关注PHP中文网!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Article chaud

R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Meilleurs paramètres graphiques
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Comment réparer l'audio si vous n'entendez personne
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Comment déverrouiller tout dans Myrise
4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Comment vérifier la version de Django Comment vérifier la version de Django Dec 01, 2023 pm 02:25 PM

Étapes pour vérifier la version de Django : 1. Ouvrez un terminal ou une fenêtre d'invite de commande ; 2. Assurez-vous que Django n'est pas installé, vous pouvez utiliser l'outil de gestion de packages pour l'installer et entrer la commande pip install django 3 ; . Une fois l'installation terminée, vous pouvez utiliser python -m django --version pour vérifier la version de Django.

Django vs Flask : une analyse comparative des frameworks Web Python Django vs Flask : une analyse comparative des frameworks Web Python Jan 19, 2024 am 08:36 AM

Django et Flask sont tous deux leaders dans les frameworks Web Python, et ils ont tous deux leurs propres avantages et scénarios applicables. Cet article procédera à une analyse comparative de ces deux frameworks et fournira des exemples de code spécifiques. Introduction au développement Django est un framework Web complet, son objectif principal est de développer rapidement des applications Web complexes. Django fournit de nombreuses fonctions intégrées, telles que ORM (Object Relational Mapping), formulaires, authentification, backend de gestion, etc. Ces fonctionnalités permettent à Django de gérer de grandes

Avantages et inconvénients du framework Django : tout ce que vous devez savoir Avantages et inconvénients du framework Django : tout ce que vous devez savoir Jan 19, 2024 am 09:09 AM

Django est un framework de développement complet qui couvre tous les aspects du cycle de vie du développement Web. Actuellement, ce framework est l’un des frameworks Web les plus populaires au monde. Si vous envisagez d'utiliser Django pour créer vos propres applications Web, vous devez alors comprendre les avantages et les inconvénients du framework Django. Voici tout ce que vous devez savoir, y compris des exemples de code spécifiques. Avantages de Django : 1. Développement rapide : Djang peut développer rapidement des applications Web. Il fournit une bibliothèque riche et interne

Comment vérifier la version de Django Comment vérifier la version de Django Nov 30, 2023 pm 03:08 PM

Comment vérifier la version de Django : 1. Pour vérifier via la ligne de commande, entrez la commande "python -m django --version" dans la fenêtre du terminal ou de la ligne de commande. 2. Pour vérifier dans l'environnement interactif Python, entrez "import django ; print(django. get_version())" code ; 3. Vérifiez le fichier de paramètres du projet Django et recherchez une liste nommée INSTALLED_APPS, qui contient des informations sur les applications installées.

Quelle est la différence entre les versions de Django ? Quelle est la différence entre les versions de Django ? Nov 20, 2023 pm 04:33 PM

Les différences sont : 1. Série Django 1.x : Il s'agit d'une première version de Django, comprenant les versions 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8 et 1.9. Ces versions fournissent principalement des fonctions de développement Web de base ; 2. Série Django 2.x : Il s'agit de la version intermédiaire de Django, comprenant les versions 2.0, 2.1, 2.2 et autres. 3. Série Django 3.x : Il s'agit de la dernière version ; série de Django, y compris les versions 3.0, 3, etc.

Comment mettre à niveau la version de Django : étapes et considérations Comment mettre à niveau la version de Django : étapes et considérations Jan 19, 2024 am 10:16 AM

Comment mettre à niveau la version de Django : étapes et considérations, exemples de code spécifiques requis Introduction : Django est un puissant framework Web Python qui est continuellement mis à jour et mis à niveau pour offrir de meilleures performances et plus de fonctionnalités. Cependant, pour les développeurs utilisant des versions plus anciennes de Django, la mise à niveau de Django peut rencontrer certains défis. Cet article présentera les étapes et les précautions à suivre pour mettre à niveau la version de Django et fournira des exemples de code spécifiques. 1. Sauvegardez les fichiers du projet avant de mettre à niveau Djan

Django est-il front-end ou back-end ? Django est-il front-end ou back-end ? Nov 21, 2023 pm 02:36 PM

Django est le back-end. Détails : Bien que Django soit avant tout un framework backend, il est étroitement lié au développement front-end. Grâce à des fonctionnalités telles que le moteur de modèles de Django, la gestion de fichiers statiques et l'API RESTful, les développeurs front-end peuvent collaborer avec les développeurs back-end pour créer des applications Web puissantes et évolutives.

Django, Flask et FastAPI : quel framework convient aux débutants ? Django, Flask et FastAPI : quel framework convient aux débutants ? Sep 27, 2023 pm 09:06 PM

Django, Flask et FastAPI : quel framework convient aux débutants ? Introduction : Dans le domaine du développement d'applications Web, il existe de nombreux excellents frameworks Python parmi lesquels choisir. Cet article se concentrera sur les trois frameworks les plus populaires, Django, Flask et FastAPI. Nous évaluerons leurs fonctionnalités et discuterons du framework le mieux adapté aux débutants. Parallèlement, nous fournirons également quelques exemples de code spécifiques pour aider les débutants à mieux comprendre ces frameworks. 1. Django : Django

See all articles