Tornado Server在使用tornado-sockjs时只使用了大部分cpu而且只使用了两个客户端。

时间:2017-12-18 18:11:43

标签: python tornado sockjs sockjs-tornado

我正在使用Tornado Server,4.4.2和pypy 5.9.0以及python 2.7.13, 在Ubuntu 16.04.3 LTS上托管

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

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类如下所示,我使用tornado periodiccallback每隔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 stat
  • 15.77 0.025811 0 524072阅读
  • 10.90 0.017840 129 138 brk
  • 2.41 0.003937 9 417 madvise
  • 2.04 0.003340 0 524072 lseek
  • 0.56 0.000923 3 298 sendto
  • 0.06 0.000098 0 23779 gettimeofday
  • 100.00 0.163663 1989527 2410总计

注意上面的2410错误。

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

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

1 个答案:

答案 0 :(得分:0)

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

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并不熟悉),那么坚持第一个解决方案。