但还是推荐检查一下云引擎的代码,看看是不是可以优化查询。云引擎访问 API 是没有网络延迟的,如果同时发起多个查询很容易就超限了。

您的意思是问题可能发生在云引擎端对吗?就是被调用的云函数如果在服务器端发起多个数据库的查询操作也会导致这一情况?

@engine.define
def geneId(**params):
    print 'geneId is called!!!!!'
    if engine.current_user != None: # 只有已登录用户才有权限调用该云函数
        objId = params["id"]
        queryForGene = leancloud.Query('Gene')
        try:
            gene = queryForGene.get(objId)                       # 标记点1
        except leancloud.LeanCloudError as e:
            print '查询gene出错,id = ', objId
            raise e                                              # 标记点2
        else:
            #查询成功
            #有pointer的需要特殊处理,只记录pointer的id
            oldStatDict = gene.get(key_Gene_stat)

            newStatDict = {}
            if oldStatDict is not None:
                newStatDict = statDictPointerToIdString(oldStatDict)

            geneDataDict = {
                key_Gene_name_zh_Hans:gene.get(key_Gene_name_zh_Hans),
                key_Gene_dominant:gene.get(key_Gene_dominant),
                key_Gene_color:gene.get(key_Gene_color),
                key_Gene_shape:gene.get(key_Gene_shape),
            }

            if len(newStatDict) > 0:
                geneDataDict[key_Gene_stat] = newStatDict
        
            jsonString = json.dumps(geneDataDict, separators = (',',':'))
            return jsonString

就是以上云函数 geneId 被调用多次。但我仔细看了一遍代码,该云函数只进行了一次查询(标记点1),调用的statDictPointerToIdString函数里只是对字典里的poiner类型都处理为string(否则用json.dumps会出错),没有查询操作。当出现Error429时,行号是指向的标记点2。

按我的粗浅理解,我的那个调用频率和代码是不会造成线程不够用的,问题有可能是当调用这个云函数时,还有其他大量的云函数或者查询调用请求我没注意到,只是运行到这行因为显式raise了才误以为问题是发生在这里。这样理解对吗?

云函数的请求实际上是通过 Python SDK 封装的 REST API,所以只要遇到异常都会抛出来的。

之前我贴那一大段Error信息时说当有这些时没有Error429,这一点我说错了,因为在那一大段Error中间我也发现了这句 LeanCloudError: LeanCloudError: [429] Too many requests.

您之前说建议我检查云引擎代码,我检查过了(见贴的云函数代码那条回复),没有发现该函数使用大量查询请求。另外我也在客户端检查了,发生问题的时刻可以理解为瞬间进行了以下操作:

  • 6次geneId云函数调用
  • 1次云函数B调用 云函数B构造与geneId基本一致但更简单。
  • 20次使用UnitySDK默认接口通过id查询某个表A的该条数据
  • 4次使用UnitySDK默认接口通过id查询某个表B的该条数据

这两条云函数的开始都进行了engine.current_user != None的判断,除了该current_user语句外各自只进行了一次通过id查询的操作(例上个回复中代码的标记点A处)。我的判断是问题不出在这些云函数的代码中。

之后我没有什么头绪了,请问该如何定位问题呢? 谢谢!

我的建议是如果没办法快速定位问题的话可以先忽略 429 错误,在抛出 LeanCloudError 的时候做一下异常处理,如果愿意的话可以同时在这里打印一条日志以便日后 debug。

出现超限的工作线程数只能说明有超限,但并不一定能 100% 反映实际情况。因为和工作线程数正相关的指标是 QPS,而 QPS 的数量又和服务端处理请求的时间相关。我们丢弃一个请求所需的时间非常小,这样客户端很快就可以再次发起另一个请求,可能会引起非常大的 QPS。也就是说你在图表里看到的这么猛的超限很可能只会在开发阶段出现。

所以我的建议如前面所说,如果没发现代码有大的问题,就建议先绕过这里。如果这个问题频繁复现,再考虑如何解决。解决方案一般都是优化查询。

我对异常理解的不是很好“在抛出 LeanCloudError 的时候做一下异常处理”我这样做您看是否可以:
在我贴出的那段代码中的以下片段:

        try:
            gene = queryForGene.get(objId)                       # 标记点1
        except leancloud.LeanCloudError as e:
            print '查询gene出错,id = ', objId
            raise e                                              # 标记点2
        else:
            #查询成功
            do somthing

将except这段的raise e这行删除。客户端查询失败后有重新发起查询的机制。而在服务器端,我也不清楚except得到该异常后我能做什么处理,所以“catch”后不作任何动作,后面的代码就不执行了,等客户端过些毫秒再重新发起查询。

另外我没明白“我们丢弃一个请求所需的时间非常小,这样客户端很快就可以再次发起另一个请求,可能会引起非常大的 QPS。”这句话中,客户端很快再次发起另一个请求,这是指的例如我在客户端写的只要未成功就重新请求的类,还是unity SDK中有类似的不成功则重试发起请求的机制?

建议判断一下 LeanCloudErrorcode,根据不同情况分别处理。譬如当 e.code == 429 时打印「并发超限」。

错误代码可以参考 我们的文档2,不过一般来说只要处理几种比较常见的情况就可以了。

非常感谢!

我的代码现在没有对任何异常做任何处理,都是只再次raise了。

那么我就先尝试catch到429只打印,其他的才raise,然后看看是否还有这个问题。

另外我一个用户在一瞬间发起20多个普通查询和10次左右的云函数调用,这个不算过份吧?

更新:

我尝试了将项目里所有的except句中的raise e改为以下,该问题仍然存在。

except leancloud.LeanCloudError as e:
    print '查询Species出错,id = ', objId
    if e.code == 429:
        print '并发超限, code = 429'
    else:
        raise e

云引擎网页后台中的出错信息:

ERROR web1 17:21:34 Traceback (most recent call last):
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 884, in handle_one_response
ERROR web1 17:21:34     self.run_application()
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/geventwebsocket/handler.py", line 88, in run_application
ERROR web1 17:21:34     return super(WebSocketHandler, self).run_application()
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 870, in run_application
ERROR web1 17:21:34     self.result = self.application(self.environ, self.start_response)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/engine/__init__.py", line 52, in __call__
ERROR web1 17:21:34     return self.cloud_app(environ, start_response)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/werkzeug/local.py", line 228, in application
ERROR web1 17:21:34     return ClosingIterator(app(environ, start_response), self.cleanup)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/engine/cors.py", line 51, in __call__
ERROR web1 17:21:34     return self.app(environ, cors_start_response)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/engine/authorization.py", line 43, in __call__
ERROR web1 17:21:34     return self.app(environ, start_response)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/engine/leanengine.py", line 73, in __call__
ERROR web1 17:21:34     self.process_session(environ)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/engine/leanengine.py", line 84, in process_session
ERROR web1 17:21:34     user = leancloud.User.become(session_token)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/user.py", line 59, in become
ERROR web1 17:21:34     response = client.get('/users/me', params={'session_token': session_token})
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/client.py", line 90, in new_func
ERROR web1 17:21:34     return func(headers=headers, *args, **kwargs)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/client.py", line 171, in new_func
ERROR web1 17:21:34     response = func(*args, **kwargs)
ERROR web1 17:21:34   File "/usr/local/lib/python2.7/dist-packages/leancloud/client.py", line 163, in new_func
ERROR web1 17:21:34     raise leancloud.LeanCloudError(content.get('code', 1), content.get('error', 'Unknown Error'))
ERROR web1 17:21:34 LeanCloudError: LeanCloudError: [429] Too many requests.
ERROR web1 17:21:34 {'CONTENT_LENGTH': '33',
ERROR web1 17:21:34  'CONTENT_TYPE': 'application/json',
ERROR web1 17:21:34  'GATEWAY_INTERFACE': 'CGI/1.1',
ERROR web1 17:21:34  'HTTP_ACCEPT': '*/*',
ERROR web1 17:21:34  'HTTP_CONNECTION': 'close',
ERROR web1 17:21:34  'HTTP_HOST': 'api.leancloud.cn',
ERROR web1 17:21:34  'HTTP_USER_AGENT': 'UnityPlayer/5.5.0f3 (http://unity3d.com)',
ERROR web1 17:21:34  'HTTP_X_FORWARDED_FOR': '::ffff:10.10.225.216',
ERROR web1 17:21:34  'HTTP_X_FORWARDED_PORT': '80',
ERROR web1 17:21:34  'HTTP_X_FORWARDED_PROTO': 'https',
ERROR web1 17:21:34  'HTTP_X_FORWARDED_PROTOCOL': 'http',
ERROR web1 17:21:34  'HTTP_X_LC_CLIENT_VERSION': 'net-portable-2.0.0.0',
ERROR web1 17:21:34  'HTTP_X_LC_ID': 'GSIRUlB5taiAdvjI1jynXHu7-gzGzoHsz',
ERROR web1 17:21:34  'HTTP_X_LC_INSTALLATION_ID': 'ce91dfe8-3c0d-455f-bae1-354fab2fe3ad',
ERROR web1 17:21:34  'HTTP_X_LC_KEY': 'zPc1Cs5CKAr2i0QGPxtHhOma',
ERROR web1 17:21:34  'HTTP_X_LC_SESSION': '28l6z4jr28u2b8lo84f74nide',
ERROR web1 17:21:34  'HTTP_X_REAL_IP': '125.33.93.118',
ERROR web1 17:21:34  'HTTP_X_UNITY_VERSION': '5.5.0f3',
ERROR web1 17:21:34  'PATH_INFO': '/1.1/functions/geneId',
ERROR web1 17:21:34  'QUERY_STRING': '',
ERROR web1 17:21:34  'REMOTE_ADDR': '10.10.58.223',
ERROR web1 17:21:34  'REMOTE_PORT': '40748',
ERROR web1 17:21:34  'REQUEST_METHOD': 'POST',
ERROR web1 17:21:34  'SCRIPT_NAME': '',
ERROR web1 17:21:34  'SERVER_NAME': '3e196bed2a81',
ERROR web1 17:21:34  'SERVER_PORT': '3000',
ERROR web1 17:21:34  'SERVER_PROTOCOL': 'HTTP/1.1',
ERROR web1 17:21:34  'SERVER_SOFTWARE': 'gevent/1.1 Python/2.7',
ERROR web1 17:21:34  '_app_params': {'id': u'GSIRUlB5taiAdvjI1jynXHu7-gzGzoHsz',
ERROR web1 17:21:34                  'key': u'zPc1Cs5CKAr2i0QGPxtHhOma',
ERROR web1 17:21:34                  'master_key': None,
ERROR web1 17:21:34                  'session_token': u'28l6z4jr28u2b8lo84f74nide'},
ERROR web1 17:21:34  'leanengine.request': <Request 'http://api.leancloud.cn/1.1/functions/geneId' [POST]>,
ERROR web1 17:21:34  'werkzeug.request': <Request 'http://api.leancloud.cn/1.1/functions/geneId' [POST]>,
ERROR web1 17:21:34  'wsgi.errors': <open file '<stderr>', mode 'w' at 0x7f05898e91e0>,
ERROR web1 17:21:34  'wsgi.input': <gevent.pywsgi.Input object at 0x7f0584ea8188>,
ERROR web1 17:21:34  'wsgi.multiprocess': False,
ERROR web1 17:21:34  'wsgi.multithread': False,
ERROR web1 17:21:34  'wsgi.run_once': False,
ERROR web1 17:21:34  'wsgi.url_scheme': 'http',
ERROR web1 17:21:34  'wsgi.version': (1, 0)} failed with LeanCloudError
ERROR web1 17:21:34

3张后台统计数据截图:

app ID: GSIRUlB5taiAdvjI1jynXHu7-gzGzoHsz
麻烦请您帮忙看一下,谢谢!

另外我一个用户在一瞬间发起20多个普通查询和10次左右的云函数调用,这个不算过份吧?

如果这三十多个请求都是并发发送的话,确实会给服务器造成压力,达到工作线程上限,并且对客户端(手机)造成一定性能瓶颈。现代浏览器中,如果对同一个 Host 的 URL 并发请求的话,浏览器也会做并发限制,将并发请求改为串行的。

具体到这个问题,我建议把这三十多个请求,改成串行调用。或者考虑到速度,改为最大两个/三个的并发调用。这样就不再很容易的达到并发上限了。


关于不能捕获所有 429 异常的问题,根据日志发现现在的 SDK 在执行实际的云函数代码之前,还会根据在 SDK 当前登录的用户,获取一次当前用户信息。这个时候抛出的 429 异常并没有被捕获,导致请求挂掉。

目前此问题已经记录在 GitHub: https://github.com/leancloud/python-sdk/issues/2951 ,我们尽快解决,请查看此 issue 来跟踪问题解决状态。

1 人赞了这个帖子.

非常感谢!
关于此issue中“在执行实际的云函数代码之前,还会根据在 SDK 当前登录的用户,获取一次当前用户信息”描述的情况,是否就是描述的我代码中在云引擎最初的if engine.current_user != None:

我想办法在客户端重构成例如每帧最多调用2/3次查询,然后再测试反馈。

问一下,如果某个瞬间的并发请求占满了线程,那是会丢弃请求,还是排队处理呢?

会丢弃请求,这样客户端可以自行处理重试逻辑

另外如果是游戏里的「每帧」都会发起请求,那肯定是有问题的……

不是每帧都发起请求,而是原来一帧发起来近30个请求,我准备改成分散到15个帧中去。

这样区别应该也不会太大。要改成「上一个请求发送完成并响应,再发送下一个请求」才可以。

是会被丢弃的,就是这个帖子里描述的 429 异常。

如果改成以下这样呢?
原来一帧调用了20个按ID查询、10个云引擎函数;现在服务器端更新增加2个云函数,客户端改为2个调用,一个是传入20个要查询的ID,另一个是传入10个要查到云引擎;在服务器端的新的云函数中分别再去调用相应的查询和原云引擎接口调用。也就是说只省掉了多次网络传输,但原接口调用的次数并没减少。

不管是否是在云引擎还是在客户端,串行调用就可以解决问题。并发调用还是会有问题。