排查calibre-web服务阻塞问题

问题

k8s集群中使用linuxServer的linuxserver/docker-calibre-web镜像部署了janeczku/calibre-web,在211011升级了最新的镜像后,发现网页频繁出现无响应的状况:浏览器标签页持续保持转圈的状态,直到很久以后才会报超时,且从此之后所有请求都无法正常完成。

经过多次尝试,发现在前端复现该问题的操作方法是:编辑书籍,获取元数据,点击保存。

排查

  1. 首先直接用kubectl port-forward calibre-6c5c84fd4f-z2mgb 8083:12345代理方式访问集群内calibre的pod,绕过集群中pod以外的其他网络组件,比如traefik,排除集群本身问题。发现问题依旧,初步排除集群网络问题。

  2. 此时需要看calibre-web日志:经过一番搜索研究,发现该项目的日志不会输出到stdout,而是位于项目目录里的calibre-web.log文件。相应的,在容器内的位置是/app/calibre-web/calibre-web.log

  3. 日志文件中看到很多重复的日志块如下。初步怀疑是由于编辑书的元数据时请求了google scholar,但是由于返回了status code 403,而相关代码在处理请求重试时逻辑有误导致了无限重试,进一步导致阻塞了web server主线程。

    INFO {scholarly:116} Got an access denied error (403).
    [2021-10-13 10:10:29,801]  INFO {scholarly:118} No other connections possible.
    [2021-10-13 10:10:29,801]  INFO {scholarly:124} Will retry after 80.11549845444705 seconds (with another session).
    [2021-10-13 10:11:54,220]  INFO {scholarly:105} Session proxy config is {}
    
  4. 此时需要查看janeczku/calibre-web的代码逻辑,相关代码如下:如果pip安装了scholarly库,则会去请求google scholor,否则会跳过。而项目把一些额外的pip依赖放到了单独的requirements文件optional-requirements.txt中。

    # Improve this to check if scholarly is available in a global way, like other pythonic libraries
    try:
        from scholarly import scholarly
        have_scholar = True
    except ImportError:
        have_scholar = False
    
    @editbook.route("/scholarsearch/<query>",methods=['GET'])
    @login_required_if_no_ano
    @edit_required
    def scholar_search(query):
        if have_scholar:
            scholar_gen = scholarly.search_pubs(' '.join(query.split('+')))
            i=0
            result = []
            for publication in scholar_gen:
                del publication['source']
                result.append(publication)
                i+=1
                if(i>=10):
                    break
            return Response(json.dumps(result),mimetype='application/json')
        else:
            return "[]"
    
  5. 查看linuxserver/docker-calibre-web中的Dockerfile可以看到确实默认安装optional-requirements.txt

本地复现

  1. 第一次在本地启动calibre-web,仅安装requirements.txt里的依赖,此时可以正常编辑书籍的元数据。

  2. pip install optional-requirements.txt后,再编辑书籍元数据,问题被复现。

  3. 本文问题的出现,和calibre web server侧的代理情况密切相关,总结如下:

    1. 首先,显然,不安装scholarly,无论对google scholar的可达性如何都不会有问题
    2. 安装启用了scholarly后,如果访问google scholar正常,也不会有问题
    3. 安装启用了scholarly后,如果访问gogole scholar返回403,则会出现本文描述的问题。
    4. 安装启用了scholarly后,如果server在国内,且未有任何proxy的情况下,经过测试,scholarly在重试超过最大次数后会抛出MaxTriesExceededException异常,在异常抛出之前server也会被阻塞,但抛出异常后恢复正常。

溯源

安装scholarly,该库提供访问google scholar的api。pip install scholarly

❯ pip show scholarly
Name: scholarly
Version: 1.2.2

截取有问题的代码片段如下,可以看到访问google 得到403 status code时会导致循环无法跳出。

# scholarly/_navigator.py
                if resp.status_code == 200 and not has_captcha:
                    return resp.text
                elif has_captcha:
                    self.logger.info("Got a captcha request.")
                    self._session = self.pm._handle_captcha2(pagerequest)
                    continue # Retry request within same session
                elif resp.status_code == 403:
                    self.logger.info(f"Got an access denied error (403).")
                    if not self.pm.has_proxy():
                        self.logger.info("No other connections possible.")
                        if not self.got_403:
                            self.logger.info("Retrying immediately with another session.")
                        else:
                            if not self.pm._use_luminati:
                                w = random.uniform(60, 2*60)
                                self.logger.info("Will retry after {} seconds (with another session).".format(w))
                                time.sleep(w)
                        self._new_session()
                        self.got_403 = True
                        
                        continue # Retry request within same session
                    else:
                        self.logger.info("We can use another connection... let's try that.")
                else:
                    self.logger.info(f"""Response code {resp.status_code}.
                                    Retrying...""")

临时解决

 

docker pull lwabish/calibre-web:china

较快速的解决方法是:fork linuxserver/docker-calibre-web,在其中的dockerfile中加入pip uninstall scholarly -y,取消google scholar支持。

新构建的镜像已上传至docker hub:lwabish/calibre-web - Docker Image | Docker Hub

posted @ 2021-12-05 11:23  憨厚的小怪  阅读(1059)  评论(0编辑  收藏  举报