Celery Best Practice 筆記

邊看這篇 Celery – Best Practices 邊做的簡單摘錄與筆記。

  1. 不要用資料庫當作 AMQP Broker。Celery 會建立數個 process 去 poll 資料庫來檢查是否有新的工作,這會導致資料庫的 disk I/O 增加,也會增加對資料庫的連接數目。
  2. 使用更多佇列 (不要只用一個)。
    並不是所有的 task 執行時間、次數跟權重都一樣,例如不重要的 task A 可能會執行很多次,但比較重要的 task B 只有零星幾個。一個佇列會導致 Celery 依序執行佇列裡的工作,所以前面可能會排了很多 task A 工作,就多花了許多時間執行,反而重要的 task B 工作延後了。依照 task 分佇列擺放,可以避免這樣的情況。
  3. 使用權重。Celery 可以針對佇列設定權重與分流,可以使用專門的 worker 來服務指定的佇列,讓 task 得到更好的服務。
    
       CELERY_QUEUES = (
        Queue('default', Exchange('default'), routing_key='default'),
        Queue('for_task_A', Exchange('for_task_A'), routing_key='for_task_A'),
        Queue('for_task_B', Exchange('for_task_B'), routing_key='for_task_B'),
       )
       CELERY_ROUTES = {
        'my_taskA': {'queue': 'for_task_A', 'routing_key': 'for_task_A'},
        'my_taskB': {'queue': 'for_task_B', 'routing_key': 'for_task_B'},
       }
    
    
       celery worker -E -l INFO -n workerA -Q for_task_A
       celery worker -E -l INFO -n workerB -Q for_task_B
    
  4. 使用 Celery 的錯誤處理機制。task 可以指定這些參數 default_retry_delay=300, max_retries=5 來指定重試間隔與重試次數。task 裏面只要使用 try…except 跟 self.retry 就可以了。
    
       @app.task(bind=True, default_retry_delay=300, max_retries=5)
       def my_task_A():
         try:
           print("doing stuff here...")
         except SomeNetworkException as e:
           print("maybe do some clenup here....")
           self.retry(e)     # Retry!
    
  5. 使用 Flower。這是一個只要裝上就能使用的 Module,可以用來觀察 Task/Queue 的狀況。
  6. 只有在真的需要時,才保留執行結果。不需要的話,就加上 CELERY_IGNORE_RESULT = True,Celery 會自動丟棄結果。
  7. 不要傳遞 ORM 物件給 task。這是因為 Celery 是用 serialization 方式來傳遞參數到別的 Process (Task 是在其他的 Process 上執行),預設可以使用 pickle, cPickle, JSON, YAML ,但是 serialization/deserialization 是有負擔的,而且不保證所有狀態都能保存,建議最好是 pure 的物件或是用整數、字串等比較不容易出狀況的型態。

Celery 的 autodiscover_tasks

跟 Django 的整合可以參考 First steps with Django

裏面會要求你在 django app 的目錄下新增一個 celery.py,這裡有一行 app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) ,這行就是找到所有 tasks 的關鍵。找不到 task 的話,執行 python manage.py celeryd 時,不會有錯誤訊息,只在程式要執行這些 task 時印出錯務訊息,說找不到。

原始碼是在 celery/app/base.py 裡,大致就是依照 INSTALLED_APPS 列出的 package 去找 tasks,有的話就 import 進來。如果你的 celery task 沒有列在 INSTALLED_APPS 裡,或是函式不在 tasks 裡的話,可以再多加 app.autodiscover_tasks,例如 app.autodiscover_tasks([‘your_module’], related_name=’my_tasks’) ,這樣就可以引入使用了。

 

Automate Django createsuperuser in Ansible

Ansible 有個 django_manage 模組,可以很方便的執行 django 裡的 manage.py,但是受限於 createsuperuser 的關係,並沒辦法在建立 superuser 的同時,一併設定密碼。

一般網路上的解決方法是自己寫個小 python 腳本 (可以看這篇 How to automate createsuperuser on django? ),丟給 shell 去執行。我是想到可以利用 manage.py 提供的 changepassword 並搭配 expect 來做,大致上是這樣子:

另外也做了避免重複建 superuser 的機制。

keyring

試用 python keyring 這個模組的一些紀錄:

  • 用法就這麼簡單:keyring.set_password(service, username, password) 或 keyring.get_password(service, username, password)
  • keyring password 的長度幾乎沒有限制,至少到 8192 個字元是沒問題。
  • keyring 的密碼存放在哪裡? 可以看看 keyring.get_keyring().file_path 。如果想換位置,沒問題,有兩個方法:
    1. 設置 XDG_DATA_HOME 這個環境變數,這比較簡單
    2. 把 keyring.util.platform_ 裡的 data_root 與 config_root 這兩個函式替換掉。
  • 要有加密功能的話,安裝 pycrypto ,backend 會自動替換為 EncryptedKeyring ,預設是 PlaintextKeyring 。
  • 要看有哪些 backend ,可以看 keyring.backends ,除了檔案為基礎的 PlaintextKeyring、EncryptedKeyring 以外,還有其他的可用。
    目前真正能用的 backends ,是用 keyring.backend.get_all_keyring()
  • 替換 backend 則是用 keyring.set_keyring() ;看目前的 backend 是用 keyring.get_keyring()。

Django model 的 validate

同事問,為什麼用 model 建立 instance 以後,呼叫 save 沒有觸發 validate?

上網找了一下,大致有兩種方法:

  1. 用 ModelForm 的 is_valid() 做檢查,Correct Way to Validate Django Model Objects?
  2. 覆寫 model 的 save(),Django model mixin to force Django to validate (i.e. call `full_clean`) before `save`

事情沒有這樣結束,因為很好奇 Django 的作法,就去追 Code。

如果是走 is_valid() :

  1. forms/models.py:ModelForm 繼承自 BaseModelForm,而 BaseModelForm 又繼承自 BaseForm,這個檔案裡,沒有看到 is_valid
  2. forms/forms.py:
    1. BaseForm.is_valid() 很簡單的只回傳 self.is_bound and not self.errors ,self.is_bound 跳過不看,self.errors 是一個 property,裏面呼叫了 self.full_clean()
    2. self.full_clean() 呼叫了 self._clean_fields(), self._clean_form() 與 self._post_clean() ,關鍵在 self._post_clean() 。BaseForm 裡的 _post_clean() 是空的,讓繼承 BaseForm 的類別去決定。
  3. forms/models.py: 回頭看 BaseModelForm._post_clean(),這裡呼叫了 self.instance.full_clean() ,並處理 ValidationError 。instance 就是 model 類別所創出的實體,Model 繼承自 ModelBase 所以接著看 ModelBase 類別。
  4. db/models/base.py: ModelBase 的 full_clean() 呼叫了 self.clean_fields(), self.clean() 與 self.validate_unique ,並且在這裡處理了 ValidationError 例外,最後判斷了 errors 裡是否有東西,有的話,丟出 ValidationError。先看 self.clean_fields() ,函式裡去取得定義在類別裡的欄位,並且呼叫每個欄位 (Field) 類別的 clean() 函式。如果有錯誤就丟出 ValidationError()
  5. db/models/fields/__init__.py:Field 類別定義在這個檔案裡,裏面的 clean() 呼叫了 self.validate() 、self.run_validators()

至此,ModelForm 的路徑就確定了。

來看看第二個方法,他是要求 Model 類別除了繼承 Django Model 之外,再繼承 ValidateModelMixin 這個自訂類別以覆寫 save() ,看看裏面的 save(),就只是多呼叫了 full_clean() 來檢查,也就是走上述步驟 4 以後的路線。

Django 官方網站不建議直接去呼叫 full_clean() 這件事,不過找了一下,也沒找到什麼更好的解法~

P.S.

pyenv, IronPython 與 pip

會去追 code 主要是試著解決 Linux 下的 pip 不能用 IronPython 的問題,後來是沒解,但這是在去年年底時看的,我不知道現在是不是已經解決了。

pyenv install -l 這指令,大致做下面幾件事情:

  • 呼叫 .pyenv/plugins/python-build/bin/pyenv-install
  • pyenv-install 又呼叫 .pyenv/plugins/python-build/bin/python-build –definitions
  • python-build –definitions 實際上是把 .pyenv/plugins/python-build/share/python-build 下的資料夾都列出來

因為 IronPython 2.7.5 才支援 pip,想要用 IronPython 2.7.5 (或更新的版本) ,可以這麼做:複製 .pyenv/plugins/python-build/share/python-build/ironpython-2.7.4 為 ironpython-2.7.5,把裏面的下載位址改為 https://github.com/IronLanguages/main/releases/download/ipy-2.7.5/IronPython-2.7.5.zip
這樣子,pyenv install -l 就會列出 ironpython-2.7.5,而且也可以下載安裝。

安裝 2.7.5 以後,用 pyenv shell ironpython-2.7.5 切換為 IronPython 2.7.5。
依照 2.7.5 的 release note 裡去執行 python -X:Frames -m ensurepip 時,卻出現以下的錯誤:

OSError: IronPython.Runtime.Exceptions.OSException: cannot load library 
  at IronPython.Modules.CTypes.LoadLibrary (System.String library, Int32 mode) [0x00000] in <filename unknown>:0 
  at IronPython.Modules.CTypes.dlopen (System.String library, Int32 mode) [0x00000] in <filename unknown>:0 
  at Microsoft.Scripting.Interpreter.FuncCallInstruction`3[System.String,System.Int32,System.Object].Run (Microsoft.Scripting.Interpreter.InterpretedFrame frame) [0x00000] in <filename unknown>:0 
  at Microsoft.Scripting.Interpreter.Interpreter.Run (Microsoft.Scripting.Interpreter.InterpretedFrame frame) [0x00000] in <filename unknown>:0

會有這樣的錯誤,最主要是因為 ironpython-2.7.5/bin/Lib/ctypes/init.py 裡的 436~441 行,這裡是這樣寫的:
[python]
if _os.name in (“nt”, “ce”):
pythonapi = PyDLL(“python dll”, None, _sys.dllhandle)
elif _sys.platform == “cygwin”:
pythonapi = PyDLL(“libpython%d.%d.dll” % _sys.version_info[:2])
else:
pythonapi = PyDLL(None)
[/python]

這邊傳了 None 到 PyDLL,而 PyDLL 裡 (就 353 行) 的 _dlopen() 無法開啟 None ,才丟出這個 exception,有試著加程式處理掉這個 exception,但是,後續會因為這行
memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr) 而出現 SystemError: LocalAlloc 錯誤

結論,Linux 下,IronPython 還是沒辦法用 pip ,另外就是,我不知道現在修正了沒。

Python InsecurePlatformWarning

碰到這個歡樂的錯誤,其實已經碰過兩三次了,前面幾次都不了了之。

InsecurePlatformWarning: A true SSLContext object is not available.

這次是確實的找到方法可以不用改程式避掉的方法,方法很簡單,就是安裝 pyopenssl ndg-httpsclient pyasn1 這幾個模組,這幾個模組會自動將 SSL 相關的憑證注射到 urllib3 模組裡,下載時就不會有 InsecurePlatformWarning 的警告。

這方法是在 StackOverflow 的 python – InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately 看到解法的,感謝。

用 pyenv 安裝指定版本時的注意事項

今天用 pyenv 安裝了 3.4.2,卻發現沒有 tkinter 模組,經過一番明查暗訪,終於知道原因。原因就是沒安裝 tk8.5-dev (我是用 12.04,14.04 要改為 tk8.6-dev),用 apt-get 裝上 tk8.5-dev,然後重新用 pyenv 安裝一次 3.4.2 就可以了:pyenv install 3.4.2 。

換言之,用 pyenv 安裝特定版本的 Python 時,會因為當前環境是否有安裝必要函式庫的 header 而影響能使用的模組,以後要特別注意。

MonkeyRunner easy module

前一陣子又在折騰 MonkeyRunner,意外發現有 easy 這個 module,順道研究了一下 ViewClient 。

easy 這個 module 是內建的,功用有點類似 jQuery,可以幫你直接找到目前畫面的某個元件,這樣就可以快速的在裏面填入文字或是按下按鈕等等。

這例子會啟動 Email ,然後將資料填入欄位後,寄出去。但實際上並不會寄出,而是存到草稿 (Draft) 裡,這是由於 Email 在寄送郵件的程式裡,有去判斷,假若是透過 MonkeyRunner 所觸發的事件,他就不寄送,而是存到草稿裡。

easy module 還有提供一些方便的函式,主要都是操作 UI 用的比較多。假若你有下載 Android 原始碼的話,這部份是在 sdk/monkeyrunner/src/com/android/monkeyrunner/easy 資料夾下面。

ViewClient 則是有人覺得不夠用,就另外自己開發了。一開始是配合 MonkeyRunner 才能使用,到了 3.0 以後,就可以不需要 MonkeyRunner,用 Python 搭配 Android SDK 就可以使用。 話是這麼說,但實際上,還是得視手機 Android 版本而定,總之使用 ViewClient 有看到錯誤訊息,就試著改用 2.3.25 的版本試試看吧。下面就是大致的用法:

ViewClient 在找元件的部份,有點亂,網站上文件也不太清楚,我追蹤原始碼,是一定要 dump ,才能找到。而且,沒辦法用 id 來找,只能用文字來找。看原始碼還有提到 UI Automator 這工具,但後來就沒詳究了。我後來沒有用 ViewClient ,所以…

 

小技巧,在找畫面上有哪些元件時,可以用 hierarchyviewer/hierarchyviewer1 這工具來看畫面佈局,這相當好用。

參考資料: