關(guān)于fastapi異步接口卡死的坑及解決
背景
開發(fā)任務(wù)是使用fastapi去寫一個(gè)對工業(yè)設(shè)備(PLC)的通信接口,方便其他后端服務(wù)與設(shè)備對接,將設(shè)備的功能抽象出來供MES調(diào)用。
通信協(xié)議是使用modbus TCP,由于fastapi是異步框架,很多以前在同步函數(shù)里開發(fā)的代碼移植過來發(fā)現(xiàn)出現(xiàn)了異常,這也是不斷踩坑的過程,問題解決之后也能體會(huì)到異步框架的優(yōu)美與高效。
問題細(xì)節(jié)
在視圖函數(shù)中我們需要?jiǎng)?chuàng)建一個(gè)socket連接,使用它跟plc進(jìn)行通信,完畢之后還要記得關(guān)閉該連接,否則就會(huì)造成異步框架的卡死,其他請求就進(jìn)不來了。
剛開始我的代碼是這樣的:
@router.put("/start/{id}") async def start( id:int, batsize:int=Body(default=15,description='批次大小'), noksize:int=Body(default=3,description='最大連續(xù)失敗次數(shù)') ): dip = await PLC.get(pk=id) # 配置數(shù)據(jù)庫中查詢設(shè)備網(wǎng)絡(luò)信息 zmqconn = get_conn(dip.host,dip.port) # 獲取網(wǎng)絡(luò)連接 # 發(fā)送命令 try: ret = zmqconn.request(action='start_batch',rqargs={'batch_size':batsize,'nok_num':noksize}) except Exception as e: raise HTTPException(status_code=503,detail=str(e)) else: return ret
????寫完之后我自己測試了一下,發(fā)現(xiàn)是可以正常發(fā)起命令的,而且接口也返回了期望的結(jié)果,但是我快速點(diǎn)了幾下,發(fā)現(xiàn)問題就來了,第六下的時(shí)候,接口卡住無法返回,異步函數(shù)中也不執(zhí)行任何動(dòng)作,這在之前的同步代碼中是沒有出現(xiàn)的。
后來我經(jīng)過調(diào)試發(fā)現(xiàn)是socket連接并沒有成功創(chuàng)建,懷疑是之前的連接沒有關(guān)閉,導(dǎo)致了網(wǎng)絡(luò)阻塞,于是加入了關(guān)閉連接的代碼。
@router.put("/start/{id}") async def start( id:int, batsize:int=Body(default=15,description='批次大小'), noksize:int=Body(default=3,description='最大連續(xù)失敗次數(shù)') ): dip = await PLC.get(pk=id) zmqconn = get_conn(dip.host,dip.port) try: ret = zmqconn.request(action='start_batch',rqargs={'batch_size':batsize,'nok_num':noksize}) except Exception as e: raise HTTPException(status_code=503,detail=str(e)) finally: zmqconn.close() return ret
這個(gè)時(shí)候就完全沒有卡住的問題了,接口可以一直點(diǎn)。
所以對于異步函數(shù),我們一定要注意手動(dòng)釋放資源,否則可能會(huì)造成意想不到的問題。
fastapi依賴注入
但是這又出現(xiàn)一個(gè)問題,我們的服務(wù)是經(jīng)常用到前面的socket框架的,其他接口也要用到,總不能每個(gè)接口都把這段代碼復(fù)制粘貼一邊吧。
fastapi作為最優(yōu)秀的異步框架,有一個(gè)很大的特點(diǎn)就是非??量痰拇a復(fù)用性,它在設(shè)計(jì)的時(shí)候考慮到了重復(fù)代碼的問題。
至于苛刻到什么程度呢,舉個(gè)例子,如果不同的接口用了相同的一組參數(shù),那么你可以把這組參數(shù)進(jìn)行封裝,寫到依賴?yán)锩妫玫臅r(shí)候引入一下,那么你新的接口就有了一組一模一樣的參數(shù)。
這里我們利用fastapi的依賴注入,把接口參數(shù)以及socket連接都封裝起來,供其他接口調(diào)用。
依賴代碼
async def get_conn_dep( id:int = Path(description='路徑參數(shù),設(shè)備id'), ): dip = await PLC.get(pk=id) try: zmqconn = get_conn(dip.host,dip.port) yield zmqconn # 向接口函數(shù)注入鏈接 finally: zmqconn.close() # 接口函數(shù)執(zhí)行完畢后會(huì)異步執(zhí)行關(guān)閉
而我們的接口函數(shù)就可以非常精簡
@router.put("/start/{id}") async def start( zmqconn = Depends(get_conn_dep), # 這一行直接復(fù)用了一堆代碼以及相應(yīng)的路徑字段,簡直太優(yōu)雅了 batsize:int=Body(default=15,description='批次大小'), noksize:int=Body(default=3,description='最大連續(xù)失敗次數(shù)') ): try: ret = zmqconn.request(action='start_batch',rqargs={'batch_size':batsize,'nok_num':noksize}) except Exception as e: raise HTTPException(status_code=503,detail=str(e)) else: return ret
我們使用swagger瘋狂點(diǎn)擊該接口,并傳入不同的參數(shù)進(jìn)行測試,發(fā)現(xiàn)接口響應(yīng)非常流暢,異常處理的時(shí)候也返回了我們想要的錯(cuò)誤信息。
我們上面的代碼使用了fastapi異步接口(據(jù)說性能逼近go),并使用了tortoise異步ORM,tortoise的使用與django-orm幾乎一樣,但它是一個(gè)純異步orm,這兩個(gè)配合起來開發(fā)簡直是非常的舒服。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
在Mac中PyCharm配置python Anaconda環(huán)境過程圖解
這篇文章主要介紹了在Mac中PyCharm配置python Anaconda環(huán)境過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Django重裝mysql后啟動(dòng)報(bào)錯(cuò):No module named ‘MySQLdb’的解決方法
這篇文章主要給大家介紹了關(guān)于Django重裝mysql后啟動(dòng)報(bào)錯(cuò):No module named ‘MySQLdb’的解決方法,分享出來,對同樣遇到這個(gè)問題的朋友們一個(gè)參考學(xué)習(xí),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04pandas pd.read_csv()函數(shù)中parse_dates()參數(shù)的用法說明
這篇文章主要介紹了pandas pd.read_csv()函數(shù)中parse_dates()參數(shù)的用法說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03