一次重構的踩坑記錄

  最初的要求是做一個加載數據模型、數據預測的可視化頁面,而且手裏已經又一個現成的頁面(類似百度翻譯)可以滿足展示輸入輸出結果,於是簡單寫了一個tornado的腳本就把功能給實現了。後來又來了三個模型,輸入輸出也是同樣的要求,內容處理也是雷同,再加上時間比較緊張,就直接複製了三份修改了模型路徑,直接跑起來三個web服務解決了要求。

  考慮到以後模型會越來越多(大概率事件),每個腳本只使用一個模型的話,部署也將越來越複雜。將處理過程抽象,將功能內聚,這是首次重構的目標。於是給頁面加上了下拉框可以選擇模型,給模型設計id,前端頁面將模型id及預測內容發送給web服務器。在web服務器上,構建一個dict用於保存模型id 及模型對象。請求被接受後,根據模型id獲取模型對象,來預測輸入內容。理想情況下新增模型的時候只需要在dict中新增一個id及模型對象。

  第一個重構版穩定運行了半年之後,又出現了新的問題:模型越來越多,各種模型的加載方式、內容預處理差異越來越大,最嚴重的是腳本加載模型的耗時越來越嚴重,這與快速啓動、快速部署、快速測試的出發點是衝突的。而且功能內聚的設計也因爲模型對數據的不同處理過程遭到破壞,開閉原則早拋到九霄雲外了。

  於是在工作不忙的時候構思了第二套重構方案。將web服務器跟模型徹底分開,採用類似上下位機通信的工作模式,web服務器作爲上位機提供標準的web服務(其他同事要也用來處理數據),模型作爲下位機提供數據處理功能,上下位機使用socket通訊。還可以給web頁面加一個心跳展示,顯示當前工作的模型。這樣設計的好處在於,web頁面輸入輸出統一了(之前版本中輸入統一,輸出不統一);模型端可以單獨啓動,也可批量啓動(當然全部啓動時就跟上個版本一樣,啓動速度慢;當然根據配置文件靈活配置啓動的模型也是可以的)。模型端定期給web服務器發送心跳,用來顯示狀態,web服務器可以再通過websocket來實時展示。

  由於代碼過於簡單就不再提供源碼,只說一下二次重構過程中的坑。 

Socket通訊

  網上常見的demosocket通訊在recv的時候都是指定長度,比如1024。然後就直接取數據了,起初我以爲py已經封裝了recv,讓recv方法在指定buffer長度的情況下,循環讀取buffer將一次發送的消息全部接收。結果可想而知,長數據直接粘包。

解決方案:

  發送端可以對消息長度做一個處理,如果是buffer長度的整倍數,可以在消息結尾出加一個空格。

  接收端循環讀取,當接收到消息的長度小於buffer長度,就結束循環。

 

                total_data = bytes()
                while True:
                    buff = conn.recv(1024)
                    total_data += buff
                    if len(buff) < 1024:
                        break
                data = total_data.decode('utf-8')

 

Websocket實時數據:

  獲取實時數據的常用方案有:

  1. web頁面定期向服務器獲取實時信息。但是由於瀏覽器無法控制請求間隔,且由瀏覽器來決定在任何中斷情況下重新打開請求,這無疑會限制服務器對併發請求的處理能力。請聯想一下購物車的秒殺場景,如果定期發送請求獲取商品剩餘數量,你可能永遠只能看到絕對買不到。
  2. web服務器向頁面推送實時信息。websocket是持久的雙向通訊,客戶端有變化可以及時通知到服務器,服務器有更新可以及時推送到客戶端。

  二次重構裏採取了服務器推送實時數據的方案。但是在往頁面推送時,遇到了個奇怪的errorThere is no current event loop in thread Thread-6. 找不到當前的io loop了,衆所周知的是tornado是基於一個io loop,在單線程上實現高併發。現在在推送的過程中找不到了io loop

  網上不少方案裏提到使用使用IOLoop.instance() ,但是通過源碼上我們可以看到實際返回的依然是IOLoop.current()。確實沒能使用instance()解決這個問題。

  其實解決思路也很簡單,python中萬物皆對象,那我們把他作爲對象傳過去就好了。在創建了ioloop對象後,先將ioloop作爲參數傳入tornado.web.Applicationhandlers對象中,無論是RequestHandler或者WebSocketHandler都可以在initialize方法中獲取到ioloop對象,然後由這個對象推送數據到web頁面,問題就解決了。代碼如圖:

 

 

 

 

 

 

  其實這個實時數據推送還有另一種解決方法,這種方法解決的原理不太明白,只是實現了目的。使用協程定期去向前端推送數據,這種情況下實時數據可以使用while True循環,在不使用協程的情況下,線程會被while True循環給佔據,其他的http請求反而無法響應。代碼如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章