简单介绍
- gevent 基本概念:
调度器: hub
上下文切换管理: switch
主循环: loop
协程: greenlet - gevent 特性:
优点:
高效,实现简单,易维护
缺点:
和go不一样,并不是python原生支持的功能,所以使用起来难免会踩一些坑,但是由于并不是实现方式有问题,所以存在着点弊病并没有什么问题。
开始使用
- 什么情况下可以使用:
足够了解自己服务所使用的io, 写的代码足够规范,否则会出现出了问题都不知道那里有问题。
需要并行化的代码不能阻塞时间过长,否则没有意义。
存在大量的io, 前提是可以变成非阻塞的,否则不能满足条件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()
代码看似没有问题,自测也没发现什么问题。
- 上线观察:
果不其然,出问题了。。。
错误发生在 challengeDetail 接口, 也是奇怪,上的明明是category 接口。
不过看错误知道应该是掉RPC没有成功, 查看RPC的日志可以看到全是fail。 - 分析问题:
首先RPC失败肯定是由于使用gevent 引起的。但是我在这category使用gevent 为啥会影响challengeDetail 呢。
观察最上面的 ParallelTask 代码,里面使用了monkey patch , 没错就是这个东西,重写了python原生socket ,使其支持了非阻塞io。
所以在没有并行化的代码里使用RPC调用,里面使用的socket变成了非阻塞的,而这些调用不受hup控制,所以肯定会出现调用失败的情况。 解决:
解决办法很简单,在用完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'
)
- 线上观察:
bug 修复之后线上没有新的问题爆出来,至今稳定运行着, 本来打算用go优化的接口看样子也不需要了, 并行化之后接口的时延由3s 降低到了300ms, 降低的幅度也符合预期 (30个rpc并行)