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并行)

转载于:https://www.cnblogs.com/acvc/p/6307412.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值