1

我正在使用 Tornado 服务器,4.4.2 和 pypy 5.9.0 和 python 2.7.13,托管在 Ubuntu 16.04.3 LTS

一个新的客户端登录并创建了一个新类并传递了套接字,因此可以维护对话。我正在使用全局客户端 [] 列表来包含这些类。初始对话框如下所示:

clients = []

class RegisterWebSocket(SockJSConnection):
  # intialize the class and handle on-open (some things left out) 

    def on_open(self,info):
        self.ipaddress = info.headers['X-Real-Ip']

    def on_message(self, data):
        coinlist = []
        msg = json.loads(data)
        if 'coinlist' in msg:
            coinlist = msg['coinlist']
        if 'currency' in msg:
            currency = msg['currency']
            tz = pendulum.timezone('America/New_York')
            started = pendulum.now(tz).to_day_datetime_string()
            ws = WebClientUpdater(self, self.clientid, coinlist,currency, 
                 started, self.ipaddress)
            clients.append(ws)

ws 类如下所示,我使用龙卷风周期性回调每 20 秒更新客户端的特定信息

class WebClientUpdater(SockJSConnection):

    def __init__(self, ws,id, clist, currency, started, ipaddress):
        super(WebClientUpdater,self).__init__(ws.session)
        self.ws = ws
        self.id = id
        self.coinlist = clist
        self.currency = currency
        self.started = started
        self.ipaddress = ipaddress
        self.location = loc
        self.loop = tornado.ioloop.PeriodicCallback(self.updateCoinList, 
                  20000, io_loop=tornado.ioloop.IOLoop.instance())                                    
        self.loop.start()
        self.send_msg('welcome '+ id)

    def updateCoinList(self):
        pdata = db.getPricesOfCoinsInCurrency(self.coinlist,self.currency)
        self.send(dict(priceforcoins = pdata))

    def send_msg(self,msg):
        self.send(msg)

我还在启动时以 60 秒的周期回调开始,以监视客户端的关闭连接并将它们从客户端 [] 列表中删除。我把它放在启动线上以在内部调用 def

if __name__ == "__main__":
    app = make_app()
    app.listen(options.port) 
    ScheduleSocketCleaning()

def ScheduleSocketCleaning():
    def cleanSocketHouse():
        print "checking sockets"
        for x in clients:
            if x.is_closed:
              x = None

    clients[:] = [y for y in clients if not y.is_closed ]

    loop = tornado.ioloop.PeriodicCallback(cleanSocketHouse, 60000,                             
          io_loop=tornado.ioloop.IOLoop.instance())
    loop.start()

如果我使用 TOP 监视服务器,我会看到它使用 4% 的 cpu 典型值,并立即爆发到 60+,但后来,说几个小时后它变成了 90% 并保持在那里。

我使用了 strace,我看到在 strace -c 视图中显示了对同一文件的大量 Stat 调用,但我在使用 -o trace.log 的文本文件中找不到任何错误。我怎样才能找到这些错误?

但我也注意到大部分时间都消耗在 epoll_wait 中。

%时间

  • 41.61 0.068097 7 9484 epoll_wait
  • 26.65 0.043617 0 906154 2410 统计
  • 15.77 0.025811 0 524072 读取
  • 10.90 0.017840 129 138 刹车
  • 2.41 0.003937 9 417
  • 2.04 0.003340 0 524072
  • 0.56 0.000923 3 298 发送至
  • 0.06 0.000098 0 23779 获取时间
  • 100.00 0.163663 1989527 2410 总计

注意上面的 2410 错误。

当我使用附加的 pid 查看 strace 输出流时,我只看到对相同文件的无休止 Stat 调用..

有人可以建议我如何更好地调试这种情况吗?由于只有两个客户端和客户端更新之间的 20 秒,我预计 CPU 使用率(在此原型阶段没有其他站点用户)将低于 1% 左右。

4

1 回答 1

0

您需要关闭 PeriodicCallbacks,否则会导致内存泄漏。您只需调用.close()PeriodicCallback 对象即可。解决这个问题的一种方法是定期清洁任务:

def cleanSocketHouse():
    global clients
    new_clients = []
    for client in clients:
        if client.is_closed:
            # I don't know why you call it loop,
            # .timer would be more appropriate
            client.loop.close()
        else:
            new_clients.append(client)
    clients = new_clients

我不确定准确.is_closed度如何(需要进行一些测试)。另一种方法是改变updateCoinList.send()当客户端不再连接时,该方法应该会失败,对吧?因此try: except:应该做的伎俩:

def updateCoinList(self):
    global clients
    pdata = db.getPricesOfCoinsInCurrency(self.coinlist,self.currency)
    try:
        self.send(dict(priceforcoins = pdata))
    except Exception:
        # log exception?
        self.loop.close()
        clients.remove(self)  # you should probably use set instead of list

如果,send()实际上没有失败(无论出于何种原因,我对 Tornado 并不熟悉)然后坚持第一个解决方案。

于 2017-12-19T08:04:00.753 回答