前言
最近因為政企部門的工作失誤,導致我們的專案差點掛掉,客戶意見很大,然後我們只能被動進入007加班狀態,忙得嗷嗷叫,直到今天才勉強把專案改完交付,是時候寫一個小結。
技術
因為前期需求不明確,資料量不大,人手也不多,所以我直接用Django做了後端,Django自帶的admin可以作為管理後臺使用,可以很快完成這個需求。
我們的前端有兩個,一個資料展示大屏,一個視覺化地圖。前者使用Vue+ElementUI+DataV實現,後者使用jQuery+百度MapV。
大概的效果如下所示,涉及到資料的部分只能打碼,感謝理解~
這個是Django的admin介面,主頁是我重新寫的
資料展示大屏
視覺化地圖
技術含量其實不高,但專案在具體實施和落地的過程中,有一些問題和細節,還是有必要記錄一下
切換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):
...
參考資料
- drf實現常用資料快取:https://segmentfault.com/a/1190000018170069
響應資料量太大問題
前面那個視覺化地圖的頁面,需要獲取幾萬條人員資訊,這個介面一開始沒做優化,返回的資料大小有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 文件 | Django (djangoproject.com)
- 075: 【Django資料庫】ORM聚合函式詳解-Sum - zheng-weimin - 部落格園 (cnblogs.com)
效能優化
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社群的強大生態,不過效能和部署方便就要稍遜一點點。
總之,都好用,看場景使用~