一次重构的踩坑记录

  最初的要求是做一个加载数据模型、数据预测的可视化页面,而且手里已经又一个现成的页面(类似百度翻译)可以满足展示输入输出结果,于是简单写了一个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请求反而无法响应。代码如下:

 

posted @ 2023-02-02 15:32  单亚林  阅读(33)  评论(0编辑  收藏  举报