文章访问量的统计
涉及知识点:
- 自定义中间件
- 缓存读写
- F
实现方式:
- 基于当此访问后端实时处理
为防止竞争,使用F表达式来实现数据库层面的+1,但是每次访问都会产生一次写入操作,写入的操作会影响页面的响应速度,尤其是并发量比较高的时候 - 基于当此访问后端延时处理——celery
通过celery(分布式任务队列)使用异步化的方式处理访问统计 - 前端通过js埋点或img标签来统计
大规模系统常用的统计方式,需要一个独立的系统来完成统计业务 - 基于Nginx日志分析来统计
解决方法:对每一个用户生成唯一的id,保存在cookie中,用户访问时将访问数据存入缓存中,以便后续具体的统计操作
1.创建中间件生成唯一id
在app下创建middleware文件夹,新建__init__.py和user_id.py
import uuid
USER_KEY = 'uid'
TEN_YEARS = 60 * 60 * 25 * 365 * 10
class UserIDMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
uid = self.generate_uid(request)
# 给request对象添加uid属性,也就是说request对象是在访问后创建的
request.uid = uid
response = self.get_response(request)
# 给响应对象设置cookie,httponly:只能在服务端访问
response.set_cookie(USER_KEY, uid, max_age=TEN_YEARS, httponly=True)
return response
# 先从cookie中取uid,取不到则生成一个
def generate_uid(self, request):
try:
uid = request.COOKIES[USER_KEY]
except KeyError:
uid = uuid.uuid4().hex
return uid
django中的middleware在项目启动时会被初始化,等接收到请求后依次调用中间件,传递request作为参数
2.配置中间件
MIDDLEWARE = [
# 添加在第一行,则后面的流程中request对象就多了一个uid属性
'blog.middleware.user_id.UserIDMiddleware',
# 省略
]
3.完善视图层,进行访问统计:判断是否有缓存,没有则加1
from datetime import date
from django.db.models import Q, F
from django.core.cache import cache
class PostDetailView(CommonViewMixin, DetailView):
queryset = Post.latest_posts()
template_name = 'blog/detail.html'
context_object_name = 'post' # 设置模板中使用的变量名
pk_url_kwarg = 'post_id' # 设置url中的参数名,默认为pk
# 重写get方法,在其中处理访问统计
def get(self, request, *args, **kwargs):
# 在调用父类的get方法前进行访问统计,则可以获得实时的数据
self.handle_visited()
response = super().get(request, *args, **kwargs)
return response
def handle_visited(self):
increase_pv = False
increase_uv = False
uid = self.request.uid
# 在对象中获取url中的参数
post_id = self.kwargs.get('post_id')
# 生成针对每篇文章的key值
pv_key = 'pv:%s:%s' % (uid, self.request.path)
uv_key = 'uv:%s:%s:%s' % (uid, str(date.today()), self.request.path)
# 判断缓存中是否有pv_key,没有则创建,有效期1分钟,并将标志位改为True
if not cache.get(pv_key):
increase_pv = True
cache.set(pv_key, 1, 1*60)
if not cache.get(uv_key):
increase_uv = True
cache.set(uv_key, 1, 24*60*60)
# 根据标志位判断是否执行写入操作
# 此条件可避免出现两次写入操作
if increase_pv and increase_uv:
Post.objects.filter(pk=post_id).update(pv=F('pv')+1, uv=F('uv')+1)
elif increase_pv:
Post.objects.filter(pk=post_id).update(pv=F('pv') + 1)
elif increase_uv:
Post.objects.filter(pk=post_id).update(uv=F('uv') + 1)
django的缓存在未配置时使用的时内存缓存,内存缓存是进程间独立的
因此如果是多进程时就会出现问题
参考:《django企业开发实战》