백엔드 개발 파이썬 튜토리얼 Python Django 프레임워크의 메시지 알림 카운터

Python Django 프레임워크의 메시지 알림 카운터

Mar 03, 2017 pm 01:14 PM
django 계수기

이야기의 시작: .count()

주로 모든 사이트 알림을 저장하는 알림 모델 클래스가 있다고 가정해 보겠습니다.

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

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

  user_id = models.IntegerField(db_index=True)
  has_readed = models.BooleanField(default=False)
로그인 후 복사

물론 처음에는 다음 쿼리를 통해 특정 사용자의 읽지 않은 메시지 수를 얻을 수 있습니다.

# 获取ID为3074的用户的未读消息数
Notification.objects.filter(user_id=3074, has_readed=False).count()
로그인 후 복사

알림 테이블이 상대적으로 작을 때는 이 방법을 사용해도 문제가 없지만 비즈니스 규모가 커질수록 속도가 느려집니다. 메시지 테이블에는 수억 개의 데이터 조각이 있습니다. 많은 게으른 사용자는 수천 개의 읽지 않은 메시지를 가지고 있습니다.

이때 각 사용자의 읽지 않은 메시지 수를 계산하는 카운터를 구현해야 합니다. 이런 방식으로 이전 count()와 비교하면 간단한 기본 키 쿼리만 실행하면 됩니다. 또는 더 나은 방법) 읽지 않은 메시지의 수를 실시간으로 얻을 수 있습니다.

더 나은 솔루션: 카운터 만들기
먼저 각 사용자의 읽지 않은 메시지 수를 저장하는 새 테이블을 만들어 보겠습니다.

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)
로그인 후 복사

우리는 등록된 각 사용자에게 해당 UserNotificationsCount 기록을 제공하여 읽지 않은 메시지 수를 저장합니다. 읽지 않은 메시지 수를 얻을 때마다 UserNotificationsCount.objects.get(pk=user_id).unread_count만 필요합니다.

다음으로 질문의 핵심은 언제 카운터를 업데이트해야 하는지 어떻게 알 수 있습니까? Django는 이와 관련하여 지름길을 제공합니까?

과제: 실시간 카운터 업데이트

카운터가 제대로 작동하려면 다음을 포함하여 실시간으로 업데이트해야 합니다.

  • 읽지 않은 새 메시지가 오면 카운터에 1을 추가

  • 메시지가 비정상적으로 삭제되었을 때 관련 메시지를 읽지 않은 경우에는 카운터에 1을 추가합니다. counter -1

  • 새 메시지를 읽었을 때 카운터는 -1

이러한 상황을 하나씩 해결해 보겠습니다.

솔루션을 버리기 전에 Django의 기능을 소개해야 합니다. Signals는 Django에서 제공하는 이벤트 알림 메커니즘으로, 이러한 이벤트가 발생할 때 특정 사용자 정의 또는 사전 설정 이벤트를 들을 수 있습니다. , 구현에서 정의된 메서드가 호출됩니다.

예를 들어 django.db.models.signals.pre_save & django.db.models.signals.post_save는 Model이 save 메소드를 호출하기 전후에 트리거되는 이벤트를 나타냅니다. 데이터베이스에서 제공하는 트리거 기능에는 일부 유사점이 있습니다.

Signal에 대한 자세한 내용은 공식 문서를 참조하세요. Signals가 카운터에 어떤 이점을 가져올 수 있는지 살펴보겠습니다.

1. 새 메시지가 오면 카운터에 1을 추가합니다

이 상황은 Django의 Signal을 사용하여 처리하는 것이 가장 좋습니다. 몇 줄의 코드만 있으면 이 경우 카운터 업데이트를 구현할 수 있습니다:

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)
로그인 후 복사

이러한 방식으로 알림.create 또는 .save()를 사용할 때마다 이와 같은 메서드를 사용하여 새 알림이 생성되면 우리의 NotificationController가 알림을 받고 카운터는 +1이 됩니다.

그러나 우리 카운터는 Django 신호를 기반으로 하기 때문에 코드 어딘가에서 원시 SQL을 사용하고 Django ORM 메서드를 통해 새 알림을 추가하지 않으면 우리 카운터는 알림을 받지 못합니다. 동일한 API를 사용하는 등 모든 새로운 알림 생성 방법을 표준화하는 것이 가장 좋습니다.

2. 메시지가 비정상적으로 삭제된 경우 관련 메시지를 읽지 않은 경우 카운터는 -1입니다

첫 번째 경험으로도 이 상황은 상대적입니다. 다음은 알림의 post_delete 신호만 모니터링하면 됩니다.

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)
로그인 후 복사


이 시점에서 삭제가 수행됩니다. 알림 이벤트의 카운터도 정상적으로 업데이트될 수 있습니다.

3. 새 메시지를 읽을 때 카운터는 -1입니다.

다음으로 사용자가 읽지 않은 메시지를 읽으면 읽지 않은 메시지 카운터도 업데이트해야 합니다. . 이게 뭐가 그렇게 어려운데요? 메시지를 읽는 방법에서 카운터를 수동으로 업데이트할 수 있나요?

예:

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)
로그인 후 복사

몇 가지 간단한 테스트를 통해 카운터가 매우 잘 작동하고 있다고 느낄 수 있지만, 이렇습니다. 구현 방법에 매우 치명적인 문제가 있습니다. 이 방법은 동시 요청을 정상적으로 처리할 수 없습니다.

예를 들어 ID가 100인 읽지 않은 메시지 개체가 있습니다. 이때 두 개의 요청이 동시에 들어옵니다. 이 알림을 읽음으로 표시해야 합니다.

# 因为两个并发的请求,假设这两个方法几乎同时被调用
NotificationController(user_id).mark_as_readed(100)
NotificationController(user_id).mark_as_readed(100)
로그인 후 복사

분명히 두 가지 방법 모두 성공적으로 이 알림을 읽음으로 표시합니다. 왜냐하면 동시성의 경우 통지.has_readed가 제대로 작동할 수 없는지 확인하므로 카운터가 두 번 잘못 -1이 되기 때문입니다. 하지만 실제로는 요청을 하나만 읽습니다.

그렇다면 이 문제를 어떻게 해결해야 할까요?

기본적으로 동시 요청으로 인한 데이터 충돌을 해결하는 방법은 하나뿐입니다. 바로 잠금입니다.

使用 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)
로그인 후 복사

除了使用``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(&#39;unread_count&#39;) + count)
로그인 후 복사

但是在生产环境中,这样的处理方式很有可能造成严重的性能问题,因为如果我们的计数器在频繁 更新的话,海量的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中那些没有被回写
# 到数据库的缓冲区数据。在这里代码就省略了
로그인 후 복사

通过以上的代码,我们把计数器的更新缓冲在了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)
로그인 후 복사

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

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

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

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

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

django 버전 확인하는 방법 django 버전 확인하는 방법 Dec 01, 2023 pm 02:25 PM

Django 버전 확인 단계: 1. 터미널 또는 명령 프롬프트 창을 엽니다. 2. Django가 설치되어 있는지 확인합니다. Django가 설치되지 않은 경우 패키지 관리 도구를 사용하여 설치하고 pip install django 명령을 입력합니다. 설치가 완료된 후 python -m django --version을 사용하여 Django 버전을 확인할 수 있습니다.

Django와 Flask: Python 웹 프레임워크 비교 분석 Django와 Flask: Python 웹 프레임워크 비교 분석 Jan 19, 2024 am 08:36 AM

Django와 Flask는 모두 Python 웹 프레임워크의 리더이며 둘 다 고유한 장점과 적용 가능한 시나리오를 가지고 있습니다. 이 기사에서는 이 두 프레임워크를 비교 분석하고 구체적인 코드 예제를 제공합니다. 개발 소개 Django는 모든 기능을 갖춘 웹 프레임워크이며, 주요 목적은 복잡한 웹 애플리케이션을 신속하게 개발하는 것입니다. Django는 ORM(Object Relational Mapping), 양식, 인증, 관리 백엔드 등과 같은 다양한 내장 기능을 제공합니다. 이러한 기능을 통해 Django는 대규모 처리를 수행할 수 있습니다.

Django 프레임워크의 장점과 단점: 알아야 할 모든 것 Django 프레임워크의 장점과 단점: 알아야 할 모든 것 Jan 19, 2024 am 09:09 AM

Django는 웹 개발 라이프사이클의 모든 측면을 포괄하는 완전한 개발 프레임워크입니다. 현재 이 프레임워크는 전 세계적으로 가장 인기 있는 웹 프레임워크 중 하나입니다. Django를 사용하여 자신만의 웹 애플리케이션을 구축하려는 경우 Django 프레임워크의 장점과 단점을 이해해야 합니다. 특정 코드 예제를 포함하여 알아야 할 모든 것이 여기에 있습니다. Django의 장점: 1. 빠른 개발 - Djang은 웹 애플리케이션을 빠르게 개발할 수 있습니다. 풍부한 라이브러리와 내부 기능을 제공합니다.

django 버전 확인하는 방법 django 버전 확인하는 방법 Nov 30, 2023 pm 03:08 PM

django 버전 확인 방법: 1. 명령줄을 통해 확인하려면 터미널이나 명령줄 창에서 "python -m django --version" 명령을 입력하세요. 2. Python 대화형 환경에서 확인하려면 "import django"를 입력하세요. print(django.get_version())" code; 3. Django 프로젝트의 설정 파일을 확인하여 설치된 애플리케이션 정보가 포함된 INSTALLED_APPS 목록을 찾습니다.

django 버전의 차이점은 무엇입니까? django 버전의 차이점은 무엇입니까? Nov 20, 2023 pm 04:33 PM

차이점은 다음과 같습니다. 1. Django 1.x 시리즈: 이는 버전 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8 및 1.9를 포함하는 Django의 초기 버전입니다. 이 버전은 주로 기본적인 웹 개발 기능을 제공합니다. 2. Django 2.x 시리즈: 2.0, 2.1, 2.2 및 기타 버전을 포함한 Django의 중기 버전입니다. 3. Django 3.x 시리즈: 최신 버전입니다. 버전 3.0, 3 등을 포함하는 Django 시리즈.

Django 버전을 업그레이드하는 방법: 단계 및 고려 사항 Django 버전을 업그레이드하는 방법: 단계 및 고려 사항 Jan 19, 2024 am 10:16 AM

Django 버전 업그레이드 방법: 단계 및 고려 사항, 필요한 특정 코드 예제 소개: Django는 더 나은 성능과 더 많은 기능을 제공하기 위해 지속적으로 업데이트되고 업그레이드되는 강력한 Python 웹 프레임워크입니다. 그러나 이전 버전의 Django를 사용하는 개발자의 경우 Django를 업그레이드하는 데 몇 가지 어려움이 있을 수 있습니다. 이 글에서는 Django 버전을 업그레이드하는 방법에 대한 단계와 주의 사항을 소개하고 구체적인 코드 예제를 제공합니다. 1. Djan을 업그레이드하기 전에 프로젝트 파일을 백업하세요.

django는 프론트엔드인가요, 백엔드인가요? django는 프론트엔드인가요, 백엔드인가요? Nov 21, 2023 pm 02:36 PM

django는 백엔드입니다. 세부사항: Django는 기본적으로 백엔드 프레임워크이지만 프런트엔드 개발과 밀접한 관련이 있습니다. Django의 템플릿 엔진, 정적 파일 관리, RESTful API와 같은 기능을 통해 프런트엔드 개발자는 백엔드 개발자와 협력하여 강력하고 확장 가능한 웹 애플리케이션을 구축할 수 있습니다.

Django, Flask, FastAPI: 초보자에게 적합한 프레임워크는 무엇인가요? Django, Flask, FastAPI: 초보자에게 적합한 프레임워크는 무엇인가요? Sep 27, 2023 pm 09:06 PM

Django, Flask, FastAPI: 초보자에게 적합한 프레임워크는 무엇인가요? 소개: 웹 애플리케이션 개발 분야에는 선택할 수 있는 우수한 Python 프레임워크가 많이 있습니다. 이 기사에서는 가장 널리 사용되는 세 가지 프레임워크인 Django, Flask 및 FastAPI에 중점을 둘 것입니다. 우리는 그들의 기능을 평가하고 초보자가 사용하기에 가장 적합한 프레임워크에 대해 논의할 것입니다. 동시에 초보자가 이러한 프레임워크를 더 잘 이해할 수 있도록 몇 가지 구체적인 코드 예제도 제공할 것입니다. 1. 장고: 장고

See all articles