WSGI接口改多线程接口

**

1.操作环境

**

  • Python 2.7
  • Centos 7.5

2.项目需求

由于项目最终要在一个封闭式服务器(无法连接外网)上部署一个小型API,由于其规模较小,一开始选择了WSGI作为项目框架,但是由于WSGI本身为单线程串行的服务器,导致一旦API出现了网络错误,或者较多人请求的时候,其会出现堵塞的现象,为了解决这个问题,我需要将API改为多线程并行模式来适应项目需求。

3.尝试

  • uWSGI

一开始,我当然是考虑到利用uWSGI来直接部署WSGI应用为多线程模式,然而我发现在封闭式服务器上安装uWSGI时缺少Python.h依赖文件,这意味着我需要安装了python-devel在服务器上,但是由于服务器无法上网,我也无法确定其需要的python-devel的版本信息,而且如果后续还缺少相关的文件,还需要继续踩坑。

  • Dejongo

Dejongo也是一个很不错的web服务框架,但是用这个总有一种杀鸡焉用牛刀的感觉,此外,由于我内部API为Inception,其调用了MySQLdb的库,封闭服务器上依旧缺少该库,并且安装时,同样提示缺少python-devel,这意味着我无法在服务器上用过python来启动服务,那么Dejongo和uWSGI的命令行式启动同样并不适合我

  • 尝试修改WSGI源码并通过工具打包成二进制文件来运行

最后自己想想觉得,其实只需要将WSGI的运行模式由单线程串行模式改成监听-启动线程执行的模式即可,这不就是上大学时学的基础内容吗?其实难度也没有自己想的那么难,既然轮子这么难找,那何不尝试一下自己造轮子呢?于是我开始阅读WSGI的源码

4.WSGI执行过程

WSGI接口改多线程接口_第1张图片

  • make_server

make_server是一个装配的过程,它的作用是制定了我们要监听的域名和端口,并且将API的功能方法通过set_app装配进来。

def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server
  • server_forever

server_forever是一个等待请求的过程,_eintr_retry会不断地监视请求就像是我们的while(1){socket.accept()}一样,看到这里,相信大家已经对如何造轮子应该已经有了大致思路。

def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
    
        self.__is_shut_down.clear()
        try:
            while not self.__shutdown_request:
                # XXX: Consider using another file descriptor or
                # connecting to the socket to wake this up instead of
                # polling. Polling reduces our responsiveness to a
                # shutdown request and wastes cpu at all other times.
                r, w, e = _eintr_retry(select.select, [self], [], [],
                                       poll_interval)
                if self in r:
                    self._handle_request_noblock()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()
  • get_request

当_eintr_retry捕捉到用户的request后,就会调用_handle_request_noblock()
,可以看到get_request里面就是accept()函数,由此来获得请求者的IP和Port。

def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that select.select has returned that the socket is
        readable before this function was called, so there should be
        no risk of blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except socket.error:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except:
                self.handle_error(request, client_address)
                self.shutdown_request(request)

def get_request(self):
        """Get the request and client address from the socket.

        May be overridden.

        """
        return self.socket.accept()
  • process_request

之后就是执行application会完成API的相关功能并对其回应,可以看到process_request中就是完成请求,结束请求,这就是为什么WSGI是单线程的原因,因为它只是串行地调用函数来完成一系列API操作,那么我们要做成多线程自然要从这一块下手,如果我们在每收到一个请求时,选择创建一个新的线程,并去执行finish_request+shutdown_request,两步操作,那么他不就变成了一个多线程并行API了吗?

def process_request(self, request, client_address):     
        self.finish_request(request, client_address)
        self.shutdown_request(request)
  • 最终修改后的process_request

每次收到一个请求,我们都会创建一个线程,并在其中执行self.finish_request(request, client_address),self.shutdown_request(request)完成API的相关操作,这样就将WSGI变成了一个便携式的多线程API框架,最后通过pyinstaller打包成二进制文件,拿去封闭式服务器中,直接后台运行即可nohup ./testAPI &。

def process_request(self, request, client_address):
        """Call finish_request.
        """Start a new thread to process the request."""
        t = threading.Thread(target=self.process_request_thread,
                             args=(request, client_address))
        t.daemon = self.daemon_threads
        t.start()
        

def process_request_thread(self, request, client_address):
        try:
            self.finish_request(request, client_address)
            self.shutdown_request(request)
        except:
            self.handle_error(request, client_address)
            self.shutdown_request(request)

你可能感兴趣的:(Python)