• gevent 使用踩坑


    简单介绍

    1. gevent 基本概念:
         调度器: hub
                上下文切换管理: switch
                主循环: loop
         协程: greenlet
    2. gevent 特性:
          优点:
                  高效,实现简单,易维护
          缺点:
                  和go不一样,并不是python原生支持的功能,所以使用起来难免会踩一些坑,但是由于并不是实现方式有问题,所以存在着点弊病并没有什么问题。

    开始使用

    1. 什么情况下可以使用:
          足够了解自己服务所使用的io,  写的代码足够规范,否则会出现出了问题都不知道那里有问题。
          需要并行化的代码不能阻塞时间过长,否则没有意义。
          存在大量的io, 前提是可以变成非阻塞的,否则不能满足条件2。
    2. 由于发现api的代码满足以上三条,所以开始进行优化:
      第一版代码如下:
             

      class ParallelTask(object):
          def __init__(self, timeout=5):
              # 覆盖python原生的socket
              gevent.monkey.patch_socket()
              self.timeout = timeout
              self.task = []
       
          def taskAppend(self, task, *args):
              self.task.append(gevent.spawn(task, *args))
       
          def run(self):
              errno, err_msg = get_error(AwemeStatus.SUCCESS)
              try:
                  gevent.joinall(self.task, timeout=self.timeout)
                  return errno, err_msg
              except Exception, ex:
                  logger.exception("[ParallelTask] run task fail error=%s" % ex)
                  errno, err_msg = get_error(AwemeStatus.REE_PARALLEL_TASK)
                  return errno, err_msg
      # handler
      def __getAwemeList(self, category_id, req_type):
          pass
        
      # 创建一个并行处理的任务
      paralle_obj = ParallelTask()
      paralle_obj.taskAppend(self.__getAwemeList, category_id, req_type)
      errno, err_msg = paralle_obj.run()

      代码看似没有问题,自测也没发现什么问题。

    3. 上线观察:
      果不其然,出问题了。。。

      错误发生在 challengeDetail 接口, 也是奇怪,上的明明是category 接口。
      不过看错误知道应该是掉RPC没有成功, 查看RPC的日志可以看到全是fail。
    4. 分析问题:
       首先RPC失败肯定是由于使用gevent 引起的。但是我在这category使用gevent 为啥会影响challengeDetail 呢。
       观察最上面的 ParallelTask 代码,里面使用了monkey patch , 没错就是这个东西,重写了python原生socket ,使其支持了非阻塞io。
       所以在没有并行化的代码里使用RPC调用,里面使用的socket变成了非阻塞的,而这些调用不受hup控制,所以肯定会出现调用失败的情况。
    5. 解决:
      解决办法很简单,在用完monkey patch 之后恢复原生的socket不久好了。 gevent 没有提供接口恢复,所以自己实现了下。

      def repatching(item):
          try:
              # 需要重新写回的module
              module = __import__(item)
              # 需要重写的属性
              saved = gevent.monkey.saved
              mapper = saved.get(item, {})
              for attr in mapper:
                  old_value = mapper.get(attr)
                  if not old_value:
                      continue
                  setattr(module, attr, old_value)
          except Exception, ex:
              logger.exception("[gevent] repatching fail error=%s" % ex)
       
      class ParallelTask(object):
          def __init__(self, timeout=5):
              # 覆盖python原生的socket, 使用完记得repatching 不然会有未知错误
              gevent.monkey.patch_socket()
              self.timeout = timeout
              self.task = []
       
          def taskAppend(self, task, *args):
              self.task.append(gevent.spawn(task, *args))
       
          def run(self):
              errno, err_msg = get_error(AwemeStatus.SUCCESS)
              try:
                  gevent.joinall(self.task, timeout=self.timeout)
                  return errno, err_msg
              except Exception, ex:
                  logger.exception("[ParallelTask] run task fail error=%s" % ex)
                  errno, err_msg = get_error(AwemeStatus.REE_PARALLEL_TASK)
                  return errno, err_msg
              finally:
                  # 使用完非阻塞的网络io之后一定要改回来
                  repatching('socket')
    6. 线上观察:
      bug 修复之后线上没有新的问题爆出来,至今稳定运行着, 本来打算用go优化的接口看样子也不需要了, 并行化之后接口的时延由3s 降低到了300ms, 降低的幅度也符合预期 (30个rpc并行)
  • 相关阅读:
    矩阵旋转
    clang-format 规范及 Visual Stido Code 自定义格式化代码风格
    Windows 安装 MinGW-w64
    Linux配置Visual Stdio Code
    Ubuntu中安装eclipse
    Ubuntu安装JDK11
    Ubuntu安装搜狗输入法
    C/C++算术运算(类型使用)的注意事项
    闰年判断与日期计算
    查看变量类型
  • 原文地址:https://www.cnblogs.com/acvc/p/6307412.html
Copyright © 2020-2023  润新知