測試平臺系列(82) 解決APScheduler重複執行的問題

米洛丶發表於2021-11-25

大家好~我是米洛

我正在從0到1打造一個開源的介面測試平臺, 也在編寫一套與之對應的完整教程,希望大家多多支援。

歡迎關注我的公眾號測試開發坑貨,獲取最新文章教程!

回顧

上一節我們編寫了線上執行Redis命令的功能,頁面也勉強能用了。對於前置條件這塊來說,就好像沙魯吞了17號,已經算半個完全體了。

我們趁熱打鐵,解決一下因為部署多機器引發的Apscheduler重複執行的問題。

APScheduler帶來的問題

APScheduler其實本質上還是一個定時任務元件,它並沒有celery那麼強大複雜的系統。針對多臺機器,或者uvicorn(gunicorn)的多個worker,它是會重複執行的。

這裡感謝一下小右君,他告訴我前面有大坑。

像我們平時那麼啟動:

if __name__ == "__main__":
    uvicorn.run(app='main:pity', host='0.0.0.0', port=7777)

這樣其實只開了1個worker,你想呀,1個工人執行定時任務,當然不存在競爭的問題,但多個工人一起執行,你就存在資訊不對稱的問題。

工人A到點了,要幹活了,他不知道B也準備幹同樣的活兒,所以任務重複執行的問題,就出現了。

解決問題的方向

  1. 我只啟動1個worker

有點傻,而且效能不好使,多臺機器部署依然有問題。

推薦指數: 0顆星

  1. 初始化sceduler的時候,利用socket佔據一個固定的埠比如2333

埠號只能被1個worker佔領,其他worker拿不到,也就起不來了。但還是不能支援多節點部署,實際上只有1個worker使用,浪費了1個

推薦指數: ⭐⭐⭐

  1. 分散式鎖

不夠輕量,需要引入第三方元件如: Redis/Zookeeper/Etcd。但能很好解決多worker和多節點的問題。

推薦指數: ⭐⭐⭐


辦法很多,但是好用的真不多。由於我們本身就需要引入Redis,還是秉著不濫用中介軟體的原則,所以我們打算用Redis的分散式鎖。

而關於redis分散式鎖,有很多介紹。我們用牛人們封裝好的RedLock來幫我們解決同時執行問題。

Redlock

比起自己setnx+用lua指令碼保障分散式鎖執行,官方後面給出了redlock的解決方案。更多這些細節可以自行搜尋Redlock

我們這邊採用第三方的庫: Redlock來簡化我們的開發。

它的api比較簡單

基本上用with獲取lock,傳入redis節點資訊即可,接著我們就可以編寫相關程式碼了。

我們還是用裝飾器的方法,在想要加鎖的方法引入此裝飾器。key是自己定義的執行key,用於確定鎖的唯一性。

分散式鎖的原理就是多臺裝置同時去試圖建立key,先建立成功的就執行對應的操作。所以對於所有節點來說,key必須都統一起來,並且不能和其他分散式鎖的key衝突。

import functools
import os

from redlock import RedLock, RedLockError

from config import Config


def lock(key):
    def decorator(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            try:
                # 試圖獲取分散式鎖,如果沒有獲取到則會丟擲RedLockError,所以我們這裡捕獲它
                with RedLock(f"distributed_lock:{func.__name__}:{key}:{str(args)}",
                             connection_details=Config.RedisCluster,
                             ):
                    return await func(*args, **kwargs)
            except RedLockError:
                print(f"程式: {os.getpid()}獲取任務失敗, 不用擔心,還有其他哥們給你執行了")

        return wrapper

    return decorator

關於唯一key的確認,我這邊首先加上了distributed_lock的字首,是因為方便區分其他key,接著通過函式名稱+唯一key確認分散式key,但由於有的方法是帶引數的,所以我選擇再加一個args,來支援那些同方法不同引數的任務

運用到pity之中

在裝飾器檔案中新增lock裝飾器

我們只需要在run_test_plan方法加上lock這個裝飾器即可,總體來說還是非常方便的。

如果需要測試的話,大家可以用以下命令啟動pity:

uvicorn main:pity --host=0.0.0.0 --port=7777 --workers=4

可以看到,日誌都輸出了4份,因為有4個worker。用這個模式啟動PITY的話,可以看到對應的效果。

關於Redlock,這節就介紹到這裡了。下一節我們要在前置條件中支援Redis語句,敬請期待吧。

相關文章