大家好~我是
米洛
!
我正在從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個worker
有點傻,而且性能不好使,多台機器部署依然有問題。
推薦指數: 0顆星
- 初始化sceduler的時候,利用socket占據一個固定的端口比如2333
端口號只能被1個worker占領,其他worker拿不到,也就起不來了。但還是不能支持多節點部署,實際上只有1個worker使用,浪費了1個端口
。
推薦指數: ⭐⭐⭐
- 分布式鎖
不夠輕量,需要引入第三方組件如: Redis/Zookeeper/Etcd
。但能很好解決多worker和多節點的問題。
推薦指數: ⭐⭐⭐
辦法很多,但是好用的真不多
。由於我們本身就需要引入Redis,還是秉着不濫用中間件的原則,所以我們打算用Redis的分布式鎖。
而關於redis分布式鎖,有很多介紹。我們用牛人們封裝好的RedLock來幫我們解決同時執行問題。
Redlock
比起自己setnx+用lua腳本保障分布式鎖執行,官方后面給出了redlock的解決方案。更多這些細節可以自行搜索Redlock
。
我們這邊采用第三方的庫: Redlock來簡化我們的開發。
基本上用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之中
我們只需要在run_test_plan方法加上lock這個裝飾器即可,總體來說還是非常方便
的。
如果需要測試的話,大家可以用以下命令啟動pity:
uvicorn main:pity --host=0.0.0.0 --port=7777 --workers=4
可以看到,日志都輸出了4份,因為有4個worker。用這個模式啟動PITY
的話,可以看到對應的效果。
關於Redlock,這節就介紹到這里了。下一節我們要在前置條件中支持Redis語句,敬請期待吧。