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


    前言

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

    技术

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

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

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

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

    image

    数据展示大屏

    image

    可视化地图

    image

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

    切换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社区的强大生态,不过性能和部署方便就要稍逊一点点。

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

  • 相关阅读:
    poj 3071 Football (概率dp)
    CF1408G Clusterization Counting
    2-sat
    线段树优化建图
    SP5971 LCMSUM
    [NOI2020]命运
    SP19149 INS14H
    Atcoder ARC-068
    CF908G New Year and Original Order
    (四)、Fiddler打断点
  • 原文地址:https://www.cnblogs.com/deali/p/16188050.html
Copyright © 2020-2023  润新知