当前的Python Flask程序部署到包含三个pod的一个deployment中,业务中需要一个消息通知到三个pod,目前不想引入消息队列组件,所以调用现有的kubernetes python库,直接取出三个pod的IP,直接访问三遍。方法略显粗糙,虽然不优雅,但是轻量级的解决了问题。
1 | resp = requests.post(url, |
但是在调试中发现,在一个API代码中一旦发起新的request请求,系统直接卡住,甚至所有的POD IP都无法支持访问了。
这个问题,一开始的研究思路是以为循环调用一个API导致的无限循环,但是加入debug log以后并没有打出log来,看起来并不是。
最后突然想起,之前把flask的app启动方式换成了gevent.pywsgi.WSGIServer, 会不会是线程无法启动的问题。
最后查到了如下内容:
1 | gevent is a coroutine -based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libev or libuv event loop. |
所以,gevent是协程级别的,那么自然就阻塞了IO,那如何解决呢?
https://www.gevent.org/api/gevent.monkey.html#module-gevent.monkey
官方提供了一个patch库。总结起来就是这样:
gevent 是一个基于协程的 Python 网络库,它使用 Greenlet 库提供了一种高效的协程实现。协程是一种轻量级的线程,允许并发执行,但没有真正的并行性。协程可 以在遇到 I/O 操作时自动地切换到其他任务,从而提高程序的并发能力。
然而,在标准的 Python 线程模型中,当一个线程遇到 I/O 操作时,它会被阻塞,直到 I/O 操作完成。这意味着在传统的多线程模型下,一个线程在等待 I/O 完成时 会占用一个线程资源,而其他线程则无法被调度执行。
monkey.patch_all() 是 gevent 提供的一个函数,用于实现对标准库的自动补丁,以便与协程一起使用。这个函数会对一些常见的阻塞式 I/O 操作进行替换,使其在 遇到阻塞时能够自动地切换到其他协程任务。
当你在执行 monkey.patch_all() 后,gevent 会修改 Python 的内置库,例如 socket、threading、time 等,以便在这些库中的 I/O 操作发生时进行协程切换。 这样一来,当使用 gevent 的 WSGIServer 启动一个服务器时,它会在遇到阻塞的 I/O 操作时自动切换到其他协程,而不会阻塞整个服务器。
总结起来,执行 monkey.patch_all() 会对 Python 的内置库进行补丁,使得在使用 gevent 的 WSGIServer 启动服务器时,能够自动地在 I/O 操作发生时切换到其 他协程,从而实现多线程的并发处理能力。这使得 gevent.pywsgi.WSGIServer 能够更高效地处理并发请求。
所以在整个flask最开始的地方加上这样一段,问题就解决了。
1 | # noinspection PyUnresolvedReferences |
同时,借这个机会,再次重温了一些线程(Process),进程(Thread)和协程(Coroutine)。这篇文章讲的挺好。
https://juejin.cn/post/7027998293351202853
有两个重要的的点:
- 线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
- 协程进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。