项目完成 – 基于Django3.x版本 – 开发部署小结


前言

最近因为政企部门的工作失误,导致我们的项目差点挂掉,客户意见很大,然后我们只能被动进入007加班状态,忙得嗷嗷叫,直到今天才勉强把项目改完交付,是时候写一个小结。

技术

因为前期需求不明确,数据量不大,人手也不多,所以我直接用Django做了后端,Django自带的admin可以作为管理后台使用,可以很快完成这个需求。

我们的前端有两个,一个数据展示大屏,一个可视化地图。前者使用Vue+ElementUI+DataV实现,后者使用jQuery+百度MapV。

大概的效果如下所示,涉及到数据的部分只能打码,感谢理解~

这个是Django的admin界面,主页是我重新写的

项目完成 - 基于Django3.x版本 - 开发部署小结

数据展示大屏

项目完成 - 基于Django3.x版本 - 开发部署小结

可视化地图

项目完成 - 基于Django3.x版本 - 开发部署小结

技术含量其实不高,但项目在具体实施和落地的过程中,有一些问题和细节,还是有必要记录一下

切换MySQL数据库

开发的时候默认用的SQLite数据库,到了正式环境,需要切换到CS架构的服务器,比如MySQL

我的配置是这样

# 数据库配置 database_config = {     'sqlite3': {         'ENGINE': 'django.db.backends.sqlite3',         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),     },     'mysql': {         'ENGINE': 'django.db.backends.mysql',         'NAME': 'name',         'USER': 'root',         'PASSWORD': 'password',         'HOST': 'mysql' if DOCKER else 'localhost',         'PORT': '3306',     } } DATABASES = {'default': database_config['sqlite3']} 

这样方便切换不同的数据库,其实还可以把数据库切换用环境变量来控制,docker中使用MySQL,本地开发环境使用sqlite。

MySQL的HOST配置是:'mysql' if DOCKER else 'localhost' 也是为了适配本地环境和docker环境的切换,后续我再封装一下 DjangoStarter 的数据库配置部分,实现前面说的环境变量切换配置。

大量数据导入问题

本项目遇到的第一个麻烦的问题是大量的数据导入

客户提供的数据是Excel格式,大概几百万条吧,我首先使用Python对数据进行预处理,做了一些去重、数据清洗之类的操作,然后导出成JSON文件。

然后在导入Django数据库的时候就遇到了问题,DjangoORM的速度太慢了!

让他跑数据跑了一个晚上,才导入了80w条数据左右,这肯定不行啊,因为被业务部门捅了娄子,项目还有几天的时间就要上线了,要赶!总共数据有几百万呢……

没办法,只能直接上SQL了,掏出Navicat,连上服务器的MySQL数据库,然后把数据直接导入临时表,再用SQL一番折腾,导入到Django生成的表里,这样数据的问题就搞定了。

(当然后续还有一系列的数据问题,前期的数据清洗还是不够的,后面发现的一些数据缺失啥的问题,在赶进度的过程中边处理,做了一些补救措施,最后也还好勉强可以用)

下次数据清洗还是得试一下Pandas这种专业的工具,单靠Python本身不够。

接口缓存

由于数据量太大,有几个需要计算操作的接口是比较慢的,该优化的暂时都优化了(下面或许会写一下DjangoORM的优化),所以只能上缓存了

项目用了我的「DjangoStarter」项目模板,本身集成了Redis支持,所以缓存直接用Redis的就好了。

缓存有两种使用方式,用Django默认的cache_page装饰器,或者第三方库rest_framework_extensions

前者作用于 function view 上,当然 class view 也能用,但是得加 method_decorator,然后得自己封装一个缓存过期配置。

后者可以使用 rest_framework 的缓存配置,相对来说更方便,只是需要安装一个库。(另外提一点,rest_framework_extensions这个库还有其他的一些功能,有空再介绍,感兴趣的同学可以探索一下)

最终我选择了第二个,哈哈~

不过我都介绍一下吧,很简单

cache_page 的使用

from django.views.decorators.cache import cache_page from rest_framework.decorators import api_view  @cache_page(CACHE_TIMEOUT) @api_view() def overview(request: Request): 	... 

其中 CACHE_TIMEOUT 的单位是秒,也可以设置成 None,这样缓存就永不过期了。

rest_framework_extensions

rest_framework 的缓存配置

REST_FRAMEWORK = {     # 缓存过期时间     'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60, } 

安装

pip install drf-extensions 

使用

常用的两种方式,装饰器和Mixin

装饰器用法

from rest_framework_extensions.cache.decorators import cache_response  class ViewSet(viewsets.ModelViewSet):     ...      @cache_response()     def list(self, request, *args, **kwargs):         ... 

Mixin用法,注意 CacheResponseMixin 要放在 ModelViewSet 的前面

from rest_framework_extensions.cache.mixins import CacheResponseMixin  class ViewSet(CacheResponseMixin, viewsets.ModelViewSet):     ... 

参考资料

响应数据量太大问题

前面那个可视化地图的页面,需要获取几万条人员信息,这个接口一开始没做优化,返回的数据大小有50MB,就很离谱,单纯网络传输就用了40秒,卡的一批。

前端小伙伴反映这个问题后,我查看一下服务器的日志,发现响应时间就达到了5秒,这忍不了啊。一开始是加了缓存,效果显著,响应时间直接压缩到了0.1秒!不过没用,传输时间还是很长。

继续分析,因为是用DjangoStarter的自动代码生成功能实现的接口,所以请求之后会默认返回人员信息的所有字段,但很明显,地图上只需要三个字段:ID、经纬度。

所以我重新写了个 serializer

class BasicPersonSerializer(serializers.ModelSerializer):     class Meta:         model = BasicPerson         fields = ['id', 'address_lng', 'address_lat'] 

然后在 viewsets 里也重写了 list 方法,用上新定义的这个 serializer

class BasicPersonViewSet(viewsets.ModelViewSet):     ...      @cache_response()     def list(self, request, *args, **kwargs):         queryset = self.filter_queryset(self.get_queryset())          page = self.paginate_queryset(queryset)         if page is not None:             serializer = BasicPersonSerializer(page, many=True)             return self.get_paginated_response(serializer.data)          serializer = BasicPersonSerializer(queryset, many=True)         return Response(serializer.data) 

然后再来测试一下,响应时间69毫秒,数据量变成4MB+,效果很显著!

整个传输时间只需要1.2秒左右~

妙啊,但是我还不满足,既然还有优化空间,那就继续优化。

给接口加个gzip压缩吧~

为图省事,直接上全站压缩

MIDDLEWARE = [     'django.middleware.gzip.GZipMiddleware', ] 

搞定

再次请求看看

数据大小被压缩到480kb,传输时间只需要356毫秒!妙啊

这个问题就搞定了。

参考资料:Django使用gzip实现压缩请求 - https://www.pythonf.cn/read/116970

聚合查询

既然用Django了,有了这么方便好用的ORM,就别老想着用什么SQL语句了。

我在这个项目里比较常用的是这几个

  • aggregate
  • annotate
  • values (虽然这个可能不算,但用的很多)
  • Count
  • Sum

每个函数的具体用法我就不复制粘贴了,看下面的参考资料吧~

参考资料:

性能优化

Django性能确实有点一言难尽,性能优化也老生常谈了,不过就实际运用而已,我们这也还是在探索之中,因为大部分场景是够用的,没有多高的并发。

不过有个比较慢的地方是展示大屏的数据接口,因为要汇集好几个表的数据进行统计,几十万上百万的数据,有点慢,一开始响应时间需要40秒,这也太离谱了。

肯定是得优化的,优化思路从减少数据库访问次数、合并运算、增加缓存入手,优化完成之后冷启动速度5秒,命中缓存60毫秒内,效果还是可以的。

关于性能优化这块以后还是得继续看看,Django有太多可以优化的地方了……

(或者不行的话直接用.Net Core这种高性能的平台重写?)

部署

部署方面依然是 uwsgi / docker / docker-compose 这套组合,之前用了好多次了,比较稳定,配置文件都是现成的,直接把代码上传服务器 up一下就启动了,非常方便。

对了还需要配置一下nginx,uwsgi是专有协议,需要做个转发,才能使用http访问到。

部署之后关闭debug模式,还需要进入docker容器里,在bash里执行 collectstatics 收集静态文件。

之后要更新的话,只需要在pycharm里配置 commit 的时候顺便deploy,把修改的代码文件提交到服务器,然后修改一下 readme.md 文件(我配置了监听这个文件)即可重启服务。

项目监控

对了,还有一个关键的,项目上线之后,需要监控项目的运行状态

对于Django项目,我用的是sentry来做监控,很好用,集成也很方便,这个sentry我准备后面写篇文章来介绍。

PS:对于.Net Core项目,我用的是.Net专用的ExceptionLess,这个界面很简洁直观,但文档没有sentry详细,不过docker搭建还是很方便的。

可以看我之前的这篇文章:[Vue2.x项目整合ExceptionLess监控](

小结

OK,大概就是这样了,项目也不是到这里就结束了,只是暂告一个段落,接下来看看客户那边有什么新的需求再来继续开发。

Django框架陆陆续续也用了两三年的时间了,虽然应用场景都比较简单,但属于是基本摸清了开发流程和定制的上限,像django-admin这种内置的管理后台,尽管有大量的自定义配置功能,还有simpleUI这种优秀的第三方界面,但他的上限还是摆在那,遇到稍微需要定制化的管理后台需求,还是得自己搞一套,好在用RestFramework写接口实在是方便,接口导出来,套个vue+elementUI的前端,一套后台就搞定了。

如果还需要数据大屏这类可视化功能,我们现在也积累了一些这方面的技术和经验,可以比较快的出产品。

接下来很多新的项目还是优先使用.Net Core技术,从稳定性和性能的方面考虑,我还是更信赖.Net Core。

Django的优势主要还是在于开发效率和背靠Python社区的强大生态,不过性能和部署方便就要稍逊一点点。

总之,都好用,看场景使用~

发表评论

相关文章