关于单例模式:
问题:单例模式在本地测试时一切正常,当运行在生产环境下,单例不生效,会创建出多个实例。
原因:Django/Flask本地环境的runserver为单进程多线程,单进程下当然共享一份内存,而在生产环境的多worker下,每个进程都有自己的内存空间,因此也有自己的实例。
关于全局变量:
同样的问题,在生产环境中,多个worker之间是无法共享一个全局变量的,一个worker修改了这个变量,其他worker是看不到改变后的结果的。
如果需要共享数据,应该使用数据库或者单独的缓存服务器。
补充:
- 使用execute_from_command_line(django自带runserver)方式启动django应用时, 会先加载urls, 从而会加载我们写的业务代码(views中的代码); 然后再加载中间件代码. 在应用启动完成时, 所有相关代码都已经被加载入内存。
- 使用get_wsgi_application(uwisg或gunicorn)方式启动django应用时, 会先加载中间件代码, 这与上面完全相反。 此时, 我们的业务代码仍然没有被加载, 直到第一个请求过来。 如果我们在代码中, 使用了未加载的代码中的全局变量, 就会出现莫名其妙的bug
- 生产环境下,一般都会设置多个worker(进程),中间件在master进程中加载完成后, 才开始fork子进程, 所以,切勿在中间件中写block的代码, 万一deadlock, 整个服务就挂了。 其实这也是符合Nginx的设计理念的, Nginx的master进程负责处理request信息, 包括处理处理起始行、提取头部、负载等, 然后把请求随机下发到worker进程。同样的, django的中间件也是处理request的, 包括加载session等等。 所以应该把中间件代码放在master进程。
- 启动时,所有的worker进程都会加载我们的业务代码。 如果某个worker进程, 没有加载过业务代码, 那么当有一个request被下发给它时, 就会去加载。
由于每个worker进程都会加载一次我们的views代码, 那么就会产生一个问题。如果我们在全局的位置, 做了一些特殊的操作, 比如说开了一个线程, 或者定义一把全局锁, 那么, 在生产环境多进程下, 就会发生, 每个进程都开了一个线程, 或者每个进程都有自己的锁。 之前就遇到过一个bug, 全局位置开了线程去轮询某个资源, 然后写入数据库, 部署到Nginx后, 发现每个item都被写了4次...... - 还要注意,这些worker之间并不会共享全局变量,在worker A中的修改不会同步到worker B, 必然会出bug
- 一个worker(进程)可以拥有多个线程,每个线程使用其所属进程的栈空间。同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。
- 一份全局变量只存在于一个python进程中,这个python进程下的子进程可以通过python自带的multiprocess提供的value和array方法共享全局变量。子进程继承父进程的全局变量,而且是以复制的形式完成,所以子进程修改后的全局变量只对自己和自己的子进程有影响。父子进程不共享这些全局变量,也就是说:父进程中对全局变量的修改不影响子进程中的全局变量,同理,子进程也不影响父进程的。
- uwisg或gunicorn的多worker(进程)与python的一个进程下的多个子进程不是一个层面上的,后者子进程可以通过multiprocess共享全局变量,前者做不到。