ManagementUtility.execute() 参考

https://blog.csdn.net/lin_keys/article/details/112186855

django源码解读:项目启动流程

前言

这篇笔记主要是对django启动流程源码查阅,我用的django版本是2.1.8,在记录的时候我将部分代码进行了删减来更好的理解。写这个笔记主要是为了让自己更了解django,我的能力也有限,写的时候花了时间和精力,但如果有理解上不适或者错误的地方请指点。

启动流程

先看下manager.py

  1. 1. def main():
  2. 2.
  3. 3. # 将settings模块设置到环境变量中
  4. 4. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoStu.settings')
  5. 5. try:
  6. 6. from django.core.management import execute_from_command_line
  7. 7. except ImportError as exc:
  8. 8. raise ImportError(
  9. 9. "Couldn't import Django. Are you sure it's installed and "
  10. 10. "available on your PYTHONPATH environment variable? Did you "
  11. 11. "forget to activate a virtual environment?"
  12. 12. ) from exc
  13. 13. # 执行命令(sys.argv:执行命令语句)
  14. 14. execute_from_command_line(sys.argv)

manager.py做了两件事情,第一是吧setting模块加载到环境变量中,第二就是执行命令。
下面我们来到execute_from_command_line 方法中看下它的作用是什么

execute_from_command_line方法

  1. 1. def execute_from_command_line(argv=None):
  2. 2. """Run a ManagementUtility."""
  3. 3. utility = ManagementUtility(argv)
  4. 4. utility.execute()

从代码可以知道首先实例化了ManagementUtility(封装django-admin和manage.py实用程序的类),然后执行了execute。
ManagementUtility的实例化方法init.py

  1. 1. def __init__(self, argv=None):
  2. 2. self.argv = argv or sys.argv[:]
  3. 3. # self.prog_name:manager.py
  4. 4. self.prog_name = os.path.basename(self.argv[0])
  5. 5. # 判断是都是__main__.py
  6. 6. if self.prog_name == '__main__.py':
  7. 7. self.prog_name = 'python -m django'
  8. 8. self.settings_exception = None

下面我们接着看看execute方法到底做了什么

execute方法

为了方便理解execute方法做了什么事情,我把部分代码去除掉,如果想要看具体的自己去查看源码吧

  1. 1. def execute(self):
  2. 2. try:
  3. 3. # 取得runserver参数
  4. 4. subcommand = self.argv[1]
  5. 5. except IndexError:
  6. 6. subcommand = 'help' # Display help if no arguments were given.
  7. 7.
  8. 8. # 预处理部分参数(--settings和--pythonpath)
  9. 9. parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
  10. 10. parser.add_argument('--settings')
  11. 11. parser.add_argument('--pythonpath')
  12. 12. parser.add_argument('args', nargs='*') # catch-all
  13. 13. try:
  14. 14. options, args = parser.parse_known_args(self.argv[2:])
  15. 15. # 所有命令应接受的所有默认选项处理
  16. 16. handle_default_options(options)
  17. 17. except CommandError:
  18. 18. pass
  19. 19. if settings.configured:
  20. 20. # 对runserver进行处理
  21. 21. if subcommand == 'runserver' and '--noreload' not in self.argv:
  22. 22. autoreload.check_errors(django.setup)()
  23. 23. else:
  24. 24. django.setup()
  25. 25. self.autocomplete()
  26. 26. # subcommand命令的判断
  27. 27. if subcommand == 'help':
  28. 28. pass
  29. 29. elif subcommand == 'version' or self.argv[1:] == ['--version']:
  30. 30. pass
  31. 31. elif self.argv[1:] in (['--help'], ['-h']):
  32. 32. pass
  33. 33. else:
  34. 34. self.fetch_command(subcommand).run_from_argv(self.argv)
  35. 35.

从上面代码我们可以看出,execute方法中首先对self.argv[1]是否是runserver进行处理,如果不是则赋值help,后面进行处理,处理代码见如下所示,判断subcommand 是否是 help ,是否是 version ,根据不同的情况展示不同的信息,如果都不是则走self.fetch_command(subcommand).run_from_argv(self.argv)。
判断self.argv[1]逻辑如下所示:

  1. 1. if subcommand == 'help':
  2. 2. if '--commands' in args:
  3. 3. sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
  4. 4. elif not options.args:
  5. 5. sys.stdout.write(self.main_help_text() + '\n')
  6. 6. else:
  7. 7. self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
  8. 8.
  9. 9. elif subcommand == 'version' or self.argv[1:] == ['--version']:
  10. 10. sys.stdout.write(django.get_version() + '\n')
  11. 11. elif self.argv[1:] in (['--help'], ['-h']):
  12. 12. sys.stdout.write(self.main_help_text() + '\n')
  13. 13. else:
  14. 14. self.fetch_command(subcommand).run_from_argv(self.argv)

handle_default_options(默认处理)

再往下看就是对对—settings和—pythonpath进行了预处理,因为这个如果有问题会影响使用,并且后面对其进行默认处理。

  1. 1. def handle_default_options(options):
  2. 2. """
  3. 3. 在此处包括所有命令应接受的所有默认选项,以便ManagementUtility可以在搜索用户命令之前对其进行处理。
  4. 4. """
  5. 5. if options.settings:
  6. 6. os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
  7. 7. if options.pythonpath:
  8. 8. sys.path.insert(0, options.pythonpath)

runserver处理

  1. 1. # 对runserver进行处理
  2. 2. if subcommand == 'runserver' and '--noreload' not in self.argv:
  3. 3. autoreload.check_errors(django.setup)()
  4. 4. else:
  5. 5. django.setup()

再然后对runserver进行处理,如上面execute源码所示:第一个是会自动重装的路线,通过 autoreload.check_errors(django.setup)() 代理完成(通过闭包来捕捉了异常)。另一个路线是参数中有 --noreload 时,就用 django.setup() 来启动服务。

subcommand 进行处理

最后就是开始所说的对subcommand 进行处理,如果不是help或者version则进入 self.fetch_command(subcommand).run_from_argv(self.argv) 。这方法分两步,一步是获取执行命令所需要的类,其次是将命令参数作为参数传递给执行函数执行。
1- 我们先看下fetch_command代码,为了方便理解拿掉部分代码

  1. 1. def fetch_command(self, subcommand):
  2. 2. """
  3. 3. 尝试获取给定的子命令,如果找不到,则使用从命令行调用的相应命令(通常为“ django-admin”或“ manage.py”)打印一条消息。
  4. 4. """
  5. 5. # 将字典映射命令名称返回到其回调应用程序。
  6. 6. # {command_name:app_name} 例子:{'check': 'django.core', 'compilemessages': 'django.core',.....}
  7. 7. commands = get_commands()
  8. 8. try:
  9. 9. app_name = commands[subcommand]
  10. 10. except KeyError:
  11. 11. # 退出告知用户
  12. 12. sys.exit(1)
  13. 13. if isinstance(app_name, BaseCommand):
  14. 14. # 如果已经加载,则直接使用
  15. 15. klass = app_name
  16. 16. else:
  17. 17. # load_command_class:自动装载
  18. 18. klass = load_command_class(app_name, subcommand)
  19. 19. return klass

fetch_command方法中 get_commands返回的是一个字典{command_name:app_name},get_commands 这部分作者是采用了python 的functools.lru_cache来实现缓存,不需要每次都重新加载,如下所示:

  1. 1. @functools.lru_cache(maxsize=None)
  2. 2. def get_commands():
  3. 3. """
  4. 4. 将字典映射命令名称返回到其回调应用程序。
  5. 5.
  6. 6. """
  7. 7. commands = {name: 'django.core' for name in find_commands(__path__[0])}
  8. 8.
  9. 9. if not settings.configured:
  10. 10. return commands
  11. 11.
  12. 12. for app_config in reversed(list(apps.get_app_configs())):
  13. 13. path = os.path.join(app_config.path, 'management')
  14. 14. commands.update({name: app_config.name for name in find_commands(path)})
  15. 15.
  16. 16. return commands

如果app_name 已经加载了则直接使用,反之通过load_command_class来动态加载模块的:

  1. 1. def load_command_class(app_name, name):
  2. 2. module = import_module('%s.management.commands.%s' % (app_name, name))
  3. 3. return module.Command()

了解了fetch_command的代码,我们看看run_from_argv
2- run_from_argv代码解析
如执行 runserver 命令的模块就是 django.contrib.staticfiles.management.commands.runserver 返回该模块中定义的 Command 类的实例。获得实例后调用了 run_from_argv(self.argv) :

  1. 1. def run_from_argv(self, argv):
  2. 2. self._called_from_command_line = True
  3. 3. parser = self.create_parser(argv[0], argv[1])
  4. 4. # Namespace(addrport=None, ...) 返回一个Namespace的实例
  5. 5. options = parser.parse_args(argv[2:])
  6. 6. cmd_options = vars(options) # 对象转成字典
  7. 7. # 将位置参数移出选项以模仿旧版optparse
  8. 8. args = cmd_options.pop('args', ())
  9. 9. # 设置默认参数
  10. 10. handle_default_options(options)
  11. 11. try:
  12. 12. # 异常捕获包裹的execute
  13. 13. self.execute(*args, **cmd_options)
  14. 14. except Exception as e:
  15. 15. sys.exit(1)
  16. 16. finally:
  17. 17. connections.close_all()

https://www.cnblogs.com/liuhaidon/archive/2020/01/20/12217153.html

Python—守护进程管理工具(Supervisor)

一、前言简介

1、Supervisor 是一个 Python 开发的 client/server 系统,可以管理和监控类 UNIX 操作系统上面的进程。可以很方便的用来启动、重启、关闭进程(不仅仅是 Python 进程)。
2、Supervisor 安装完成之后,可以编写配置文件,来满足自己的需求。为了方便,我们把配置分成两部分:supervisord(这是 server 端,对应的有 client 端:supervisorctl)和应用程序(即我们要管理的程序)。

  • supervisord(server 部分):主要负责管理子进程,响应客户端命令以及日志的输出等,受控的监听进程都会以其子进程地方式出现,不过如果supervisord的主进程非正常退出时,被管理的进程仍然是运行状态。
  • supervisorctl(client 部分):命令行客户端,用户可以通过它与不同的 supervisord 进程联系,获取子进程的状态等。

3、Supervisord 要求管理的程序是非daemon程序,supervisord 会帮你把它转成daemon程序,因此如果用supervisord来管理nginx的话,必须在nginx的配置文件里添加一行设置daemon off让nginx以非daemon方式启动,当然了,redis等亦如是。

二、安装supervisor

1、安装supervisor

1 [root@localhost ~]``# pip install supervisor

2、测试supervisor安装是否成功

1 [root@localhost ~]``# echo_supervisord_conf

3、卸载supervisor

1 [root@localhost ~]``# pip uninstall supervisor

supervisor安装完成后会生成三个执行程序:supervisortd、supervisorctl、echo_supervisord_conf。分别是supervisor的守护进程服务(用于接收进程管理命令)、客户端(用于和守护进程通信,发送管理进程的指令)、生成初始配置文件程序。

三、配置supervisord

安装 supervisor 完成之后,可以编写配置文件,来满足自己的需求。为了方便,我们把配置分成两部分:supervisord(这是 server 端,对应的 client 端:supervisorctl)和应用程序(即我们要管理的程序)。首先来看 supervisord 的配置文件。
1、通过运行 echo_supervisord_conf 命令生成 supervisor 的初始化配置文件:supervisord.conf(supervisord.conf文件的位置无所谓,这里当作第二个位置)

1
2
[root@localhost ~]``# echo_supervisord_conf > /etc/supervisord.conf
[root@localhost ~]``# echo_supervisord_conf > /etc/supervisor/supervisord.conf

2、启动 supervisord 服务(通过 -c 选项指定配置文件路径,如果不指定会按照这个顺序查找配置文件:$CWD/supervisord.conf, $CWD/etc/supervisord.conf, /etc/supervisord.conf, /etc/supervisor/supervisord.conf):

1
2
3
[root@localhost ~]``# supervisord # 默认去找 /etc/supervisord.conf 这个配置文件
[root@localhost ~]``# supervisord -c /etc/supervisord.conf # 到指定路径下去找配置文件
[root@localhost ~]``# supervisord -c /etc/supervisor/supervisord.conf # 到指定路径下去找配置文件

3、查看 supervisord 服务是否在运行:

1 `[root@localhost ~]``# ps aux grep supervisord`

4、查看 supervisord 服务是否生效

1 `[root@localhost ~]``# ps -ef grep ProjectName`

四、配置文件参数说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[unix_http_server]
file``=``/tmp/supervisor``.sock ;UNIX socket 文件,supervisorctl 会使用
;``chmod``=0700 ;socket文件的mode,默认是0700
;``chown``=nobody:nogroup ;socket文件的owner,格式:uid:gid

;[inet_http_server] ;HTTP服务器,提供web管理界面
;port=127.0.0.1:9001 ;Web管理后台运行的IP和端口,如果开放到公网,需要注意安全性
;username=user ;登录管理后台的用户名
;password=123 ;登录管理后台的密码

[supervisord]
logfile=``/tmp/supervisord``.log ;日志文件,默认是 $CWD``/supervisord``.log
logfile_maxbytes=50MB ;日志文件大小,超出会rotate,默认 50MB,如果设成0,表示不限制大小
logfile_backups=10 ;日志文件保留备份数量默认10,设为0表示不备份
loglevel=info ;日志级别,默认info,其它: debug,warn,trace
pidfile=``/tmp/supervisord``.pid ;pid 文件
nodaemon=``false ;是否在前台启动,默认是``false``,即以 daemon 的方式启动
minfds=1024 ;可以打开的文件描述符的最小值,默认 1024
minprocs=200 ;可以打开的进程数的最小值,默认 200

[supervisorctl]
serverurl=unix:``///tmp/supervisor``.sock ;通过UNIX socket连接supervisord,路径与unix_http_server部分的``file``一致
;serverurl=http:``//127``.0.0.1:9001 ; 通过HTTP的方式连接supervisord

; [program:xx]是被管理的进程配置参数,xx是进程的名称
[program:xx]
command``=``/opt/apache-tomcat-8``.0.35``/bin/catalina``.sh run ; 程序启动命令
autostart=``true ; 在supervisord启动的时候也自动启动
startsecs=10 ; 启动10秒后没有异常退出,就表示进程正常启动了,默认为1秒
autorestart=``true ; 程序退出后自动重启,可选值:[unexpected,``true``,``false``],默认为unexpected,表示进程意外杀死后才重启
startretries=3 ; 启动失败自动重试次数,默认是3
user=tomcat ; 用哪个用户启动进程,默认是root
priority=999 ; 进程启动优先级,默认999,值小的优先启动
redirect_stderr=``true ; 把stderr重定向到stdout,默认``false
stdout_logfile_maxbytes=20MB ; stdout 日志文件大小,默认50MB
stdout_logfile_backups = 20 ; stdout 日志文件备份数,默认是10
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile=``/opt/apache-tomcat-8``.0.35``/logs/catalina``.out
stopasgroup=``false ;默认为``false``,进程被杀死时,是否向这个进程组发送stop信号,包括子进程
killasgroup=``false ;默认为``false``,向进程组发送``kill``信号,包括子进程

;包含其它配置文件
[include]
files = ``/www/server/panel/plugin/supervisor/profile/``*.ini ;可以指定一个或多个以.ini结束的配置文件

五、配置supervisord开机自启

1.在 /usr/lib/systemd/system/ 目录下面新建文件supervisord.service,并写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Process Monitoring and Control Daemon(Supervisor daemon)
After=rc-``local``.service nss-user-lookup.target

[Service]
Type=forking
ExecStart=``/usr/bin/supervisord -c ``/etc/supervisor/supervisord``.conf
ExecStop=``/usr/bin/supervisord shutdown
ExecReload=``/usr/bin/supervisord reload
killMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

2.启动服务:执行开机启动命令,让 supervisord 服务开机自启。

1 [root@localhost ~]``# systemctl enable supervisord

3.验证一下是否为开机启动。enabled:开机自启。disabled:开机不自启。

1 [root@localhost ~]``# systemctl is-enabled supervisord

4.重启服务器,检查 supervisor 是否启动。

1 `[root@localhost ~]``# ps aux grep supervisor`

六、Web管理页面

supervisor提供了可视化的Web管理页面,允许远程操作,但是需要在配置文件中手动打开。可以在网页端访问supervisor服务。
注意:如果是腾讯云或阿里云服务器,需要安全组放行9001端口,才能访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1.打开配置配件
[root@localhost ~]``# vi /etc/supervisor/supervisord.conf
` <br /># 2.将下面几行前面的分号去掉,修改一下数据保存退出(用户名与密码自己定义)<br />[inet_http_server] # inet (TCP) server disabled by default`<br />`port=0.0.0.0:9001# ip_address:port specifier, *:port for all iface<br />username=user # default is no username (open server)`<br />`password=123# default is no password (open server)<br /> <br /># 3.重新载入配置文件<br />[root@localhost ~]`# supervisorctl reload
` <br /># 4.在浏览器输入:IP:9001,再输入用户名与密码即可登陆查看。`

七、开启多个进程

1
2
numprocs=2 ``# 启动进程的数目,默认为1。; 若 numprocs 不为1时,就是进程池的概念。process_name 的表达式中一定要包含 process_num 来区分不同的进程。
process_name=%(program_name)s_%(process_num)02d ``# 直接就可以使用

八、Supervisor管理的项目名称是中文的问题解决

在配置文件中添加或修改:environment=LANG=”en_US.UTF-8”,LC_ALL=”en_US.UTF-8”。例如:

1
2
3
4
environment=LANG=``"en_US.UTF-8"``,LC_ALL=``"en_US.UTF-8"

[include]
files = ``/www/server/panel/plugin/supervisor/profile/``*.ini

 https://www.osgeo.cn/post/71f93

九、常见报错问题

1、Error: Another program is already listening on a port that one of our HTTP servers is configured to use. Shut this program down first before starting supervisord。

1
2
`[root@localhost ~]``# ps -ef grep supervisord # 获取所有supervisord正在运行的pid。假设pid值为2506<br />[root@localhost ~]`# kill -s SIGTERM 2506 # 杀死进程,之后再重新执行:supervisord -c /etc/supervisor/supervisord.conf 即可。

2、pip下载报错

1 [root@localhost ~]``# /www/server/panel/pyenv/bin/pip install supervisor -i [http://pypi.douban.com/simple](http://pypi.douban.com/simple) --trusted-host pypi.douban.com --ignore-installed meld3