起因
Django 作為 Python著名的Web框架,相信很多人都在用,自己作業中也有專案專案在用,而在最近幾天的使用中發現,部署Django程式的服務器出現了記憶體問題,現象就是運行一段時間之后,記憶體占用非常高,最侄訓把服務器的記憶體耗盡,對于Python專案出現記憶體問題,自己之前處理過一次,所以并沒有第一次解決時的慌張,自己之前把解決方法也整理了博客:https://www.cnblogs.com/zhaof/p/10031945.html
但是事情似乎并沒有我想的那么簡單,自己嘗試用之前的的方法tracemalloc庫進行問題的排查,但是問題來了實際的專案中有快一百多個介面,怎么排查?難道一個一個介面進行測驗排查,但是時間又比較緊急,可能又來不及了,對比上次自己解決是因為上次的專案比較簡單,相對來說定位問題比較容易,那么這次怎么處理呢?
處理程序
一般Python專案其實是很少出現記憶體問題的,一般都是自己代碼寫的有問題導致的,而對于這次出現的問題,自己的排查思路(對于web 介面型別的專案):
- 先排查呼叫比較頻繁的介面
- 然后排查資料匯總介面(查詢比較復雜)
- 如果上述還沒有查出來,再排查剩余的介面
在這次的問題排查中,自己大致也是按照這個思路進行的,在對呼叫頻繁的介面進行排查時,并沒有發現記憶體的例外,而出現記憶體的問題則是在資料匯總的相關介面上,
其實這種介面對于初級開發可能是容易出問題的地方,首先這種介面查詢的資料相對其他介面會比較復雜,如果編碼基礎又不是特別好,可能就會在這些介面上出現bug.
而在這次的排查中,最終確定是在一個匯總資料的介面上,定位到問題處在了Django ORM 使用不當導致的,自己通過一個簡單代碼實體來說明:
class Student(models.Model):
name = models.CharField(max_length=20)
name2 = models.CharField(max_length=20)
name3 = models.CharField(max_length=20)
name4 = models.CharField(max_length=20)
name5 = models.CharField(max_length=20)
name6 = models.CharField(max_length=20)
name7 = models.CharField(max_length=20)
name8 = models.CharField(max_length=20)
name9 = models.CharField(max_length=20)
name10 = models.CharField(max_length=20)
name11 = models.CharField(max_length=20)
name12 = models.CharField(max_length=20)
name13 = models.CharField(max_length=20)
name14 = models.CharField(max_length=20)
name15 = models.CharField(max_length=20)
age = models.IntegerField(default=0)
正常情況,我們的表欄位會比較多,這里就通過多個name來模擬,出現題的代碼就出在關于這個表的介面上:
def index(request):
studets = Student.objects.filter(age__gt=20)
if studets:
pass
return HttpResponse("test memory")
為了讓記憶體問題容易復現,我通過腳本向Student中插入了20000條資料,當然這里資料越多,問題越明顯
通過一個測驗腳本并發請求這個介面,觀察記憶體情況,你會發現,記憶體會出現瞬間上漲的情況,并且如果你的資料越多,請求越多,你的記憶體可能會在一段時間居高不下,并且逐漸上漲,問題出在哪里了?
其實很簡單,問題出在了代碼中的if 判斷那里,我們通過filter 查詢回傳的是QuerySet 型別的資料,而我們過濾之后的資料可能會存在非常多的時候,這個時候我們通過if 直接判斷,自己的理解這個地方會將整個QuerySet加載到記憶體中,從而出現記憶體占用過高的問題,而如果并且這個時候這個介面的回應速度也是非常會變慢,而這個QuerySet 中的資料越多,記憶體占用越明顯,
在Django的檔案中其實做了說明
exists()?Returns
Trueif theQuerySetcontains any results, andFalseif not. This tries to perform the query in the simplest and fastest way possible, but it does execute nearly the same query as a normalQuerySetquery.
exists()is useful for searches relating to both object membership in aQuerySetand to the existence of any objects in aQuerySet, particularly in the context of a largeQuerySet.The most efficient method of finding whether a model with a unique field (e.g.
primary_key) is a member of aQuerySetis:entry = Entry.objects.get(pk=123) if some_queryset.filter(pk=entry.pk).exists(): print("Entry contained in queryset")Which will be faster than the following which requires evaluating and iterating through the entire queryset:
if entry in some_queryset: print("Entry contained in QuerySet")And to find whether a queryset contains any items:
if some_queryset.exists(): print("There is at least one object in some_queryset")Which will be faster than:
if some_queryset: print("There is at least one object in some_queryset")… but not by a large degree (hence needing a large queryset for efficiency gains).
Additionally, if a
some_querysethas not yet been evaluated, but you know that it will be at some point, then usingsome_queryset.exists()will do more overall work (one query for the existence check plus an extra one to later retrieve the results) than usingbool(some_queryset), which retrieves the results and then checks if any were returned.
所以對于我們的代碼我們只需要把if 判斷地方改成if not studets.exists() 就可以解決問題,
這是一個很小的知識點,但是如果使用不對,可能就會造成非常嚴重的記憶體問題,
總結
- 除了單元測驗,還需要做大資料量測驗,這次的問題如果在測驗的時候做過一定資料量的測驗,可能很早就能及時發現問題
- 對于基礎的庫的使用要更加熟悉
- 排查問題的思路要明確,不然可能會無從下手
延伸閱讀
- https://docs.djangoproject.com/en/3.0/ref/models/querysets/
- https://www.cnblogs.com/zhaof/p/10031945.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/73425.html
標籤:Python
上一篇:用selenium登錄豆瓣網
