├── .github └── workflows │ └── build.yml ├── .gitignore ├── docs ├── CNAME ├── README.md ├── author │ ├── 作者微信.jpg │ └── 微信打赏.jpg ├── how-to-build-site-monitoring-service.md ├── how-to-configure-server.md ├── how-to-deploy-fastapi-services.md ├── how-to-deploy-using-docker.md ├── how-to-integrate-plugins.md ├── how-to-optimize-performance.md ├── how-to-quickly-deploy-database.md ├── how-to-quickly-develop-fastapi-application.md └── images │ └── portainer_demo.png └── mkdocs.yml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - name: Configure Git Credentials 17 | run: | 18 | git config user.name github-actions[bot] 19 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 20 | - uses: actions/setup-python@v4 21 | with: 22 | python-version: 3.x 23 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 24 | - uses: actions/cache@v3 25 | with: 26 | key: mkdocs-material-${{ env.cache_id }} 27 | path: .cache 28 | restore-keys: | 29 | mkdocs-material- 30 | - run: pip install mkdocs-material 31 | - run: pip install mkdocs-git-revision-date-localized-plugin 32 | - run: pip install mkdocs-git-authors-plugin 33 | - run: mkdocs gh-deploy --force -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # 代码暂时不动 9 | code/ 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | userData/ 16 | .idea/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | cover/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | .pybuilder/ 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | # For a library or package, you might want to ignore these files since the code is 93 | # intended to run in multiple environments; otherwise, check them in: 94 | # .python-version 95 | 96 | # pipenv 97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 100 | # install all needed dependencies. 101 | #Pipfile.lock 102 | 103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 104 | __pypackages__/ 105 | 106 | # Celery stuff 107 | celerybeat-schedule 108 | celerybeat.pid 109 | 110 | # SageMath parsed files 111 | *.sage.py 112 | 113 | # Environments 114 | .env 115 | .venv 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | 122 | # Spyder project settings 123 | .spyderproject 124 | .spyproject 125 | 126 | # Rope project settings 127 | .ropeproject 128 | 129 | # mkdocs documentation 130 | /site 131 | 132 | # mypy 133 | .mypy_cache/ 134 | .dmypy.json 135 | dmypy.json 136 | 137 | # Pyre type checker 138 | .pyre/ 139 | 140 | # pytype static type analyzer 141 | .pytype/ 142 | 143 | # Cython debug symbols 144 | cython_debug/ 145 | 146 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | fastapi.tplan.cc -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 后端工程师生存指南 2 | 3 | ![Readme](https://github-readme-stats.vercel.app/api/pin/?username=PY-GZKY&repo=PythonBackendEngineerGuide) 4 | 5 | 在线阅读:[https://fastapi.tplan.cc](https://fastapi.tplan.cc) 6 | 项目地址:[https://github.com/PY-GZKY/PythonBackendEngineerGuide](https://github.com/PY-GZKY/PythonBackendEngineerGuide) 7 | 8 | * [如何搭建一台后端服务器](how-to-configure-server.md) 9 | * [如何选择数据库服务](how-to-quickly-deploy-database.md) 10 | * [如何使用 Docker 部署服务](how-to-deploy-using-docker.md) 11 | * [FastAPI开发](how-to-quickly-develop-fastapi-application.md) 12 | * [如何快速开发 FastAPI 应用](how-to-quickly-develop-fastapi-application.md) 13 | * [如何快速部署 FastAPI 服务](how-to-deploy-fastapi-services.md) 14 | * [如何集成 FastAPI 插件](how-to-integrate-plugins.md) 15 | * [如何优化后端服务](how-to-optimize-performance.md) 16 | * [如何搭建一个站点监控服务](how-to-build-site-monitoring-service.md) 17 | -------------------------------------------------------------------------------- /docs/author/作者微信.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonBackendEngineerGuide/1e3b3c5d1de04e7c2a8b81115b9cec66f747f100/docs/author/作者微信.jpg -------------------------------------------------------------------------------- /docs/author/微信打赏.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonBackendEngineerGuide/1e3b3c5d1de04e7c2a8b81115b9cec66f747f100/docs/author/微信打赏.jpg -------------------------------------------------------------------------------- /docs/how-to-build-site-monitoring-service.md: -------------------------------------------------------------------------------- 1 | # 监控服务 2 | 3 | - https://github.com/louislam/uptime-kuma 4 | - https://github.com/louislam/uptime-kuma/wiki 5 | 6 | ## 如何使用 Upptime Kuma 搭建站点监控服务? 7 | 8 | Uptime Kuma 是一款易于使用的自托管监控工具: 9 | 10 | - 监控 HTTP(s) / TCP / HTTP(s) 关键字 / HTTP(s) 的正常运行时间 Json 查询 / Ping / DNS 记录 / 推送 / Steam 游戏服务器 / Docker 容器 11 | - 花哨的、反应式的、快速的 UI/UX 12 | - 通过 Telegram、Discord、Gotify、Slack、Pushover、电子邮件 (SMTP) 和 90+ 通知服务发送通知,单击此处查看完整列表 13 | - 多语言,多个状态页面,证书信息 14 | 15 | 16 | ### 如何使用 Docker 启动 Upptime Kuma 服务? 17 | 18 | ```shell 19 | docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1 20 | ``` 21 | 22 | > 启动后浏览到 http://localhost:3001 预览即可。 23 | 24 | 更新到最新的docker发布版本: 25 | 26 | ```shell 27 | docker pull louislam/uptime-kuma:1 28 | docker stop uptime-kuma 29 | docker rm uptime-kuma 30 | 31 | docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1 32 | ``` 33 | 34 | > 对于每个新版本,构建 docker 镜像都需要一些时间,如果还没有,请耐心等待。 35 | 36 | --- 37 | 38 | ## 如何使用 Prometheus + Grafana 进行可视化监控? 39 | 40 | ### 如何使用 Docker 启动 Prometheus 服务? 41 | 42 | 首先需要构造一个 `docker-compose.yml` 文件,内容如下: 43 | 44 | ```yaml 45 | version: '3' 46 | 47 | networks: 48 | monitoring: 49 | driver: bridge 50 | 51 | volumes: 52 | prometheus_data: { } 53 | 54 | services: 55 | grafana: 56 | image: grafana/grafana 57 | container_name: grafana 58 | restart: always 59 | ports: 60 | - "3000:3000" 61 | volumes: 62 | - ./grafana:/var/lib/grafana 63 | environment: 64 | - GF_DEFAULT_LOCALE=zh-CN 65 | 66 | mysql-exporter: 67 | image: prom/mysqld-exporter 68 | container_name: mysqld-exporter 69 | ports: 70 | - "9104:9104" 71 | volumes: 72 | - ./my.cnf:/.my.cnf 73 | networks: 74 | - monitoring 75 | depends_on: 76 | - prometheus 77 | 78 | node-exporter: 79 | image: prom/node-exporter:latest 80 | container_name: node-exporter 81 | restart: unless-stopped 82 | volumes: 83 | - /proc:/host/proc:ro 84 | - /sys:/host/sys:ro 85 | - /:/rootfs:ro 86 | command: 87 | - '--path.procfs=/host/proc' 88 | - '--path.rootfs=/rootfs' 89 | - '--path.sysfs=/host/sys' 90 | - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' 91 | ports: 92 | - "9100:9100" 93 | networks: 94 | - monitoring 95 | 96 | prometheus: 97 | image: prom/prometheus:latest 98 | container_name: prometheus 99 | restart: unless-stopped 100 | volumes: 101 | - ./prometheus.yml:/etc/prometheus/prometheus.yml 102 | - ./prometheus_data:/prometheus 103 | command: 104 | - '--config.file=/etc/prometheus/prometheus.yml' 105 | - '--storage.tsdb.path=/prometheus' 106 | - '--web.console.libraries=/etc/prometheus/console_libraries' 107 | - '--web.console.templates=/etc/prometheus/consoles' 108 | - '--web.enable-lifecycle' 109 | ports: 110 | - "9090:9090" 111 | networks: 112 | - monitoring 113 | ``` 114 | 115 | 这个docker-compose.yml文件中包含了三种服务: 116 | 117 | - Grafana:用于展示监控数据 118 | - 在本地创建一个 `grafana` 目录,用于存储 Grafana 的数据 119 | - 通过 `GF_DEFAULT_LOCALE` 环境变量设置默认语言为中文 120 | - 通过 `ports` 指定端口映射到本地的 3000 端口 121 | - Prometheus:用于存储监控数据 122 | - 在本地创建一个 `prometheus_data` 目录,用于存储 Prometheus 的数据 123 | - 通过 `volumes` 挂载 `prometheus.yml` 配置文件 124 | - 通过 `ports` 指定端口映射到本地的 9090 端口 125 | - Exporter:用于收集监控数据 126 | - Node Exporter:用于收集主机的监控数据,如 CPU、内存、磁盘等,映射到本地的 9100 端口 127 | - MySQL Exporter:用于收集 MySQL 数据库的监控数据,如连接数、查询数、响应时间等,映射到本地的 9104 端口,my.cnf 是 MySQL 的配置文件 128 | 129 | ### 编写 prometheus.yml 配置文件 130 | 131 | ```yaml 132 | global: 133 | scrape_interval: 5s 134 | 135 | scrape_configs: 136 | - job_name: 'prometheus' 137 | scrape_interval: 5s 138 | static_configs: 139 | - targets: [ '1.1.1.1:9090' ] 140 | 141 | - job_name: 'node' 142 | static_configs: 143 | - targets: [ '1.1.1.1:9100' ] 144 | 145 | - job_name: 'mysql' 146 | static_configs: 147 | - targets: [ '1.1.1.1:9104' ] 148 | ``` 149 | 150 | 这个配置文件中定义了三个 job: 151 | 152 | - prometheus:用于监控 Prometheus 本身,也就是上面说到的启动的 Prometheus 服务到 9090 端口 153 | - node:用于监控 Node Exporter,也就是上面说到的启动的 Node Exporter 服务到 9100 端口 154 | - mysql:用于监控 MySQL Exporter,也就是上面说到的启动的 MySQL Exporter 服务到 9104 端口 155 | 156 | 值得注意的是,MySQL Exporter 需要一个 my.cnf 配置文件,内容如下: 157 | 158 | ``` 159 | [client] 160 | host=1.1.1.1 161 | port=30004 162 | user=root 163 | password=123456 164 | ``` 165 | 166 | 最后,启动服务: 167 | 168 | ```shell 169 | docker-compose up -d 170 | ``` 171 | 172 | > 启动后浏览到 http://localhost:3000,使用默认的用户名和密码 `admin` 登录,然后配置数据源和导入仪表盘。 173 | -------------------------------------------------------------------------------- /docs/how-to-configure-server.md: -------------------------------------------------------------------------------- 1 | # Ubuntu服务器 装机指南 2 | 3 | ## 如何选配一台服务器? 4 | 5 | * CPU:8核心处理器 6 | * 主板:Kingston A2000 7 | * 硬盘:1T、2T 或更多 8 | * 内存:8GB、16GB 或更多 9 | 10 | 这是一个基本的配置,具体的选择还取决于你服务器的使用情况。如果你的服务器将用于运行大型数据库、虚拟化环境或其他内存密集型任务,你可能需要更多的内存。 11 | 12 | 同样,如果预算允许,可以考虑选择更高性能的 CPU 和更大容量的 SSD。 13 | 14 | ## 如何进行系统换源? 15 | 16 | 如果你的服务器来自 阿里云或者腾讯云,那自然是不用换源了。这些运营商会帮你配好。 17 | 18 | 但是如果你的服务器是本地搭建的,或者是来自国外的vps,那你就需要换成一些网络稳定的国内源。 19 | 20 | Ubuntu 的软件源配置文件是 `/etc/apt/sources.list`。将系统自带的该文件做个备份,将该文件替换为下面内容,即可使用选择的软件源镜像。 21 | 22 | 首先备份源文件: 23 | 24 | ```shell 25 | sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 26 | ``` 27 | 28 | 然后将 /etc/apt/sources.list 文件替换为以下内容: 29 | 30 | ```text 31 | # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释 32 | deb https://mirrors.bfsu.edu.cn/ubuntu/ jammy main restricted universe multiverse 33 | deb https://mirrors.bfsu.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse 34 | deb https://mirrors.bfsu.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse 35 | deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse 36 | ``` 37 | 38 | 然后apt更新一下源: 39 | 40 | ```shell 41 | sudo apt update 42 | ``` 43 | 44 | > 这是 https://mirrors.bfsu.edu.cn 中国清华镜像源,如果你不知道其他的更好的源,那就用这个就行了。 45 | 46 | ## 如何配置 Anaconda Python 环境? 47 | 48 | Anaconda 官方下载页面:https://www.anaconda.com/downloads 49 | 50 | 你只需要下载对应系统的安装包,然后直接运行安装包即可。如果遇到网络问题,可以使用清华大学的镜像,如 51 | 52 | ```shell 53 | wget https://mirrors.bfsu.edu.cn/anaconda/miniconda/Miniconda3-py310_23.3.1-0-Linux-x86_64.sh 54 | bash Miniconda3-py310_23.3.1-0-Linux-x86_64.sh -b 55 | ``` 56 | 57 | 检查 miniconda 是否安装成功 58 | 59 | ```shell 60 | conda --version 61 | ``` 62 | 63 | ## 如何配置zsh? 64 | 65 | zsh 是一个功能强大的 shell,它是 bash 的一个扩展,提供了更多的功能和更好的用户体验。 66 | 67 | ```shell 68 | # 配置zsh 69 | sh -c "$(wget https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)" 70 | 71 | # 配置完成后一定要运行下面命令修改环境变量 72 | ~/miniconda3/bin/conda init bash 73 | ~/miniconda3/bin/conda init zsh 74 | ``` 75 | 76 | 这是因为 zsh 会覆盖 bash 的环境变量,所以需要重新初始化一下。 77 | 78 | ## 如何设置免密ssh登录服务器? 79 | 80 | 首先进入本地电脑当前用户的.ssh文件夹中,再打开id_rsa.pub文件并复制其中所有内容 81 | 82 | ```shell 83 | ssh-keygen 84 | ``` 85 | 86 | 此然后运行下面命令获得自己的公钥: 87 | 88 | ```shell 89 | cat ~/.ssh/id_rsa.pub 90 | ``` 91 | 92 | 使用密码登录服务器,创建~/.ssh/authorized_keys文件,并设置相应权限。 93 | 94 | ```shell 95 | touch ~/.ssh/authorized_keys 96 | chmod 700 ~/.ssh 97 | chmod 600 ~/.ssh/authorized_keys 98 | nano ~/.ssh/authorized_keys 99 | 100 | # 添加你的公钥,如 ssh-rsa AAAA... 101 | ``` 102 | 103 | 并设置相应权限。把刚刚复制的密钥粘贴到~/.ssh/authorized_keys文件中,如果文件已经有内容就添加到最后一行,这样每次使用ssh登录服务器就不用再输入密码了 104 | 105 | 然后重启即可: 106 | 107 | ```shell 108 | sudo reboot 109 | ``` 110 | 111 | ## 如何把装好的环境copy到另一台机器上? 112 | 113 | 1.ssh连上服务器 114 | 115 | 2.找到配置的环境目录 116 | 117 | 3.直接rsync整个文件夹到eight2: rsync -avP 本地文件夹 用户名@远程服务器:远程地址 118 | 119 | 比如: 120 | 121 | ```shell 122 | rsync -avp /data/wutong/miniconda3 eight2.local:/data/wutong/ 123 | ``` 124 | 125 | 系统会提示你输出服务器密码,输入后等待拷贝完成就行了。 126 | 127 | 这样就把一台服务器的Python环境直接拷贝到另一台服务器上了,可直接使用。 128 | 129 | ## 如何查看一台服务器当前有多少个SSH连接数? 130 | 131 | ```shell 132 | who | grep -i pts | wc -l 133 | ``` 134 | 135 | ## 如何安装 Docker? 136 | 137 | 1. 首先apt安装相关依赖包: 138 | 139 | ```shell 140 | sudo apt-get install \ 141 | apt-transport-https \ 142 | ca-certificates \ 143 | curl \ 144 | gnupg-agent \ 145 | software-properties-common 146 | ``` 147 | 148 | 2. 添加 Docker 的官方 GPG 密钥: 149 | 150 | ```shell 151 | curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add - 152 | ``` 153 | 154 | 3. 使用以下指令设置稳定版仓库: 155 | 156 | ```shell 157 | sudo add-apt-repository \ 158 | "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ \ 159 | $(lsb_release -cs) \ 160 | stable" 161 | ``` 162 | 163 | 4. 更新 apt 包索引: 164 | 165 | ```shell 166 | sudo apt-get update 167 | ``` 168 | 169 | 5. 安装最新版本的 Docker: 170 | 171 | ```shell 172 | sudo apt-get install docker-ce docker-ce-cli containerd.io 173 | ``` 174 | 175 | 6. 测试 Docker 是否安装成功,输入以下指令,打印出以下信息则安装成功: 176 | 177 | ```text 178 | $ sudo docker run hello-world 179 | 180 | Unable to find image 'hello-world:latest' locally 181 | latest: Pulling from library/hello-world 182 | 1b930d010525: Pull complete Digest: sha256:c3b4ada4687bbaa170745b3e4dd8ac3f194ca95b2d0518b417fb47e5879d9b5f 183 | Status: Downloaded newer image for hello-world:latest 184 | 185 | 186 | Hello from Docker! 187 | This message shows that your installation appears to be working correctly. 188 | 189 | 190 | To generate this message, Docker took the following steps: 191 | 1. The Docker client contacted the Docker daemon. 192 | 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 193 | (amd64) 194 | 3. The Docker daemon created a new container from that image which runs the 195 | executable that produces the output you are currently reading. 196 | 4. The Docker daemon streamed that output to the Docker client, which sent it 197 | to your terminal. 198 | 199 | 200 | To try something more ambitious, you can run an Ubuntu container with: 201 | $ docker run -it ubuntu bash 202 | 203 | 204 | Share images, automate workflows, and more with a free Docker ID: 205 | https://hub.docker.com/ 206 | 207 | 208 | For more examples and ideas, visit: 209 | https://docs.docker.com/get-started/ 210 | 211 | ``` 212 | 213 | 7. 如果要使用 Docker 作为非 root 用户,则应考虑使用类似以下方式将用户添加到 docker 组: 214 | 215 | ```shell 216 | sudo usermod -aG docker ai 217 | ``` 218 | 219 | 8. 然后重启docker即可: 220 | 221 | ```shell 222 | sudo systemctl restart docker 223 | ``` 224 | 225 | 9. 设置docker开机自动启动: 226 | 227 | ```shell 228 | sudo systemctl enable docker 229 | ``` 230 | 231 | 这样每次开机就会自启动docker服务. 232 | 233 | ## 如何定时清理指定的目录数据? 234 | 235 | ```shell 236 | find ./ -type d -mtime +15 -exec rm -rf {} \; 237 | ``` 238 | 239 | 这会清理15天前的目录数据。 240 | 241 | 242 | ## 如何将服务添加到 Linux 开机启动项? 243 | 244 | 具体的服务文件在 `/etc/systemd/system/` 目录下,比如你的服务文件是 `my-service.service`,那么你可以使用下面的命令将服务添加到开机启动项: 245 | 246 | ```shell 247 | sudo systemctl enable my-service.service 248 | ``` 249 | 250 | 例如有一个 FastAPI 项目需要实现开机自动启动(虽然使用 Docker 会更加方便),main.py 主文件入口可能是这样的: 251 | 252 | ```python 253 | # -*- coding: utf-8 -*- 254 | import uvicorn 255 | 256 | from app.utils_app import create_app 257 | 258 | app = create_app() 259 | 260 | if __name__ == '__main__': 261 | uvicorn.run(app='main:app', host="0.0.0.0", port=30002) 262 | ``` 263 | 264 | 再编写一个启动脚本 `start.sh`: 265 | 266 | ```shell 267 | #!/bin/bash 268 | 269 | # 查找监听在30002端口上的服务的进程ID 270 | PID=$(lsof -t -i:30002) 271 | 272 | # 如果找到了进程,杀死它 273 | if [ ! -z "$PID" ]; then 274 | echo "Stopping existing service running on port 30002 with PID: $PID" 275 | kill -9 $PID 276 | fi 277 | 278 | # 等待一小会儿确保进程已经被杀死 279 | sleep 2 280 | 281 | # 在此处设置 ENV_TYPE 环境变量,在 centos 中的环境变量对 systemd 服务不可见 282 | export ENV_TYPE="prod" 283 | 284 | # 启动新的FastAPI服务,sdadmin_py311 是一个 Anaconda 环境 285 | echo "Starting FastAPI service..." 286 | cd /data/project/sdadmin && nohup /root/anaconda3/envs/sdadmin_py311/bin/uvicorn main:app --workers 4 --limit-concurrency 1000 --host=0.0.0.0 --port 30002 >> ./server.log 2>&1 & 287 | 288 | echo "FastAPI service started with new PID: $!" 289 | ``` 290 | 291 | 这个脚本会先查找是否有监听在 30002 端口上的服务,如果有则杀死它,然后启动新的 FastAPI 服务。 292 | 293 | 那么如何将这个服务添加到开机启动项呢?首先创建一个服务文件 `start_server.service`: 294 | 295 | ```shell 296 | [Unit] 297 | Description=My Server Start Script 298 | After=network.target 299 | 300 | [Service] 301 | ExecStart=/data/project/sdadmin/start_server.sh 302 | Type=forking 303 | 304 | [Install] 305 | WantedBy=multi-user.target 306 | ``` 307 | 308 | 这个启动文件会在网络启动后执行 `/data/project/sdadmin/start_server.sh` 脚本。`Type=forking` 表示这是一个后台服务。 309 | 310 | 然后使用下面的命令将服务添加到开机启动项: 311 | ```shell 312 | sudo cp start_server.service /etc/systemd/system/ 313 | sudo systemctl enable start_server.service 314 | ``` 315 | 316 | 确认一下服务是否已经添加到开机启动项: 317 | ```shell 318 | sudo systemctl is-enabled start_server.service 319 | ``` 320 | 321 | 执行 reload systemd 并启动服务: 322 | ```shell 323 | sudo systemctl daemon-reload 324 | sudo systemctl start start_server.service 325 | ``` 326 | 327 | 查看系统服务状态: 328 | ```shell 329 | sudo systemctl status start_server.service 330 | ``` 331 | 332 | 可以看到服务已经启动了: 333 | ```shell 334 | ● start_server.service - My Server Start Script 335 | Loaded: loaded (/etc/systemd/system/start_server.service; enabled; vendor preset: enabled) 336 | Active: active (running) since Wed 2024-11-13 17:11:04 CST; 17min ago 337 | Main PID: 352685 (start_server.sh) 338 | Tasks: 11 (limit: 396646) 339 | Memory: 269.4M 340 | CGroup: /system.slice/start_server.service 341 | ├─352685 /bin/bash /data/project/sdadmin/start_server.sh 342 | ├─352686 /root/anaconda3/envs/sdadmin_py311/bin/python /root/anaconda3/envs/sdadmin_py311/bin/uvicorn main:app --workers 4 --limit-concurrency 1000 --host=0.0.0.0 --port 30002 343 | ├─352687 /root/anaconda3/envs/sdadmin_py311/bin/python -c from multiprocessing.resource_tracker import main;main(4) 344 | ├─352688 /root/anaconda3/envs/sdadmin_py311/bin/python -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=7) --multiprocessing-fork 345 | ├─352689 /root/anaconda3/envs/sdadmin_py311/bin/python -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=9) --multiprocessing-fork 346 | ├─352690 /root/anaconda3/envs/sdadmin_py311/bin/python -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=11) --multiprocessing-fork 347 | └─352691 /root/anaconda3/envs/sdadmin_py311/bin/python -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=13) --multiprocessing-fork 348 | 349 | 11月 13 17:11:02 iZ8vbaggulwyv5797qfml6Z systemd[1]: Starting My Server Start Script... 350 | 11月 13 17:11:04 iZ8vbaggulwyv5797qfml6Z start_server.sh[352521]: Starting FastAPI service... 351 | 11月 13 17:11:04 iZ8vbaggulwyv5797qfml6Z start_server.sh[352521]: FastAPI service started with new PID: 352685 352 | 11月 13 17:11:04 iZ8vbaggulwyv5797qfml6Z systemd[1]: Started My Server Start Script. 353 | ``` 354 | 355 | 这样就可以实现开机自动启动 FastAPI 服务了。 356 | -------------------------------------------------------------------------------- /docs/how-to-deploy-fastapi-services.md: -------------------------------------------------------------------------------- 1 | # 如何快速部署 `FastAPI` 服务? 2 | 3 | `fastapi` 官方推荐使用 `uvicron` 服务器来部署其服务。 4 | 5 | `uvicorn` 是一款轻量快速的 `Python ASGI `框架(异步框架),基于`uvloop`和`httptools`构建。 6 | 7 | 直到最近,`Python`还没有为`asyncio`框架提供最小的低级服务器/应用程序接口。 `ASGI`规范填补了这一空白,意味着我们现在能够开始构建一个可用于所有`asyncio`框架的通用工具集。 8 | 9 | `ASGI`帮助实现一个`Python Web`框架生态系统,该框架在与IO绑定的上下文中实现高吞吐量方面与`Node`和`Go`竞争非常激烈。 它还提供对`HTTP/2`和`WebSockets`的支持,`WSGI`无法处理。 10 | 11 | `Uvicorn`目前支持`HTTP/1.1`和`WebSockets`。 计划支持`HTTP/2`。 12 | 13 | 由于 `Uvicron` 的诞生,以及 `fastapi` 本身支持高性能的异步请求,在 `Python` 轻量级框架中有着质的飞跃。 14 | 这也是 `fastapi` 近来大火的原因,某些程度上更好的替代了 `flask` 的江湖地位。 15 | 16 | ### 安装 `Uvicron` 17 | 18 | ```shell 19 | pip install uvicorn 20 | ``` 21 | 22 | ## 如何构建一个简单的应用程序? 23 | 24 | 新建一个 main.py 文件并编写代码: 25 | 26 | ```python 27 | import uvicorn 28 | from fastapi import FastAPI 29 | 30 | app = FastAPI() 31 | 32 | 33 | @app.get("/") 34 | def home(name="fastapi"): 35 | return f"Hello! {name}" 36 | 37 | 38 | if __name__ == "__main__": 39 | uvicorn.run(app, host='127.0.0.1', port=8000, debug=True) 40 | ``` 41 | 42 | 可以看到 `fastapi` 的语法和 `flask`非常接近,不管是从设计模式还是感官上来说都有着异曲同工之妙。 43 | 44 | 运行 `main.py` 文件: 45 | 46 | ````shell 47 | python main.py 48 | ```` 49 | 50 | 这样一个简单的`fastapi` 应用就起来了并且使用了 `uvicron` 服务器启动 51 | 输出如下: 52 | 53 | ```shell 54 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) 55 | INFO: Started reloader process [37928] using statreload 56 | INFO: Started server process [41144] 57 | INFO: Waiting for application startup. 58 | 2021-04-08 10:53:29.938 | DEBUG | app.api:connect_to_mysql:73 - MYSQL 数据库初始化成功 ... DONE 59 | 2021-04-08 10:53:30.101 | DEBUG | app.api:connect_to_mongo:89 - MONGODB 数据库初始化成功 ... DONE 60 | 2021-04-08 10:53:30.101 | DEBUG | app.api:connect_to_ws:119 - WEBSOCKET 连接初始化成功 ... DONE 61 | INFO: Application startup complete. 62 | ``` 63 | 64 | 在开发过程中,我们想知道代码被改变时接口或页面上实时的同步更改,指定 `--reload `参数为 `True` 可以解决这个问题,也叫热加载。 65 | 66 | ```shell 67 | if __name__ == "__main__": 68 | uvicorn.run(app='main:app', host="0.0.0.0", port=8000,reload=True, debug=True) 69 | ``` 70 | 71 | ## 如何启动 `uvicorn`? 72 | 73 | 在线上部署的时候,我们需要将 `web服务`永久的在后台运行,可以使用: 74 | 75 | ```shell 76 | nohup uvicorn main:app --workers 4 --host=0.0.0.0 --port 8080 >> ./output.log 2>&1 & 77 | ``` 78 | 79 | `nohup` 是 `linux` 操作系统中用于运行后台常驻服务的关键字,我们将输出`日志`到 `./output.log` 文件,方便查看。 80 | 81 | 我们启用了` 4` 个线程,一般来说线程数为宿主机内核的双倍数,比如 `2` 核的主机即开启 `4` 个线程即可。 82 | 83 | 查看 uvicorn 是否正常工作 84 | 85 | ```shell 86 | ps -ef | grep 8080 87 | ``` 88 | 89 | ```shell 90 | root 3180 1 0 4月03 ? 00:00:00 /root/anaconda3/bin/python /root/anaconda3/bin/uvicorn main:app --workers 4 --host=0.0.0.0 --port 8080 91 | root 3855 6709 0 11:27 pts/1 00:00:00 grep --color=auto 8006 92 | ``` 93 | 94 | `--port` 参数对外开放了 `8080` 端口,通过 `http://IP:8080` 访问目标主机可以看到服务已经正常启。 95 | 96 | `nohup` 会将所有的 `print`、 `log` 、`debug` 输出的信息汇总到`./output.log` 文件,大概是这样的: 97 | 98 | ```bash 99 | 2021-04-08 09:00:06.622 | DEBUG | app.api.db.mysqlDB:bulk_save:21 - [+] 插入成功 !!! ========= erp3c_customer_delivery ======== 100 | 2021-04-08 09:00:06.639 | DEBUG | app.api.db.mysqlDB:bulk_save:21 - [+] 插入成功 !!! ========= erp3c_customer_delivery ======== 101 | 2021-04-08 09:00:07.198 | DEBUG | app.api.api_v1.spider_m.erp3c_delivery_out.erp_delivery_16h:crawl:252 - [+] 更新成功 ..... done 102 | {'status_code': 200, 'message': '获取状态成功', 'data': 1} 103 | INFO: 113.66.254.2:54315 - "POST /api/v1/erp3c_delivery_16h/1f11b32dec39262e84df HTTP/1.1" 200 OK 104 | ``` 105 | 106 | ## 如何使用 `Uvicorn` + `Nginx` 部署应用? 107 | 108 | 如果是`单机/单服务器`应用的话,通过上面的部署之后项目已经完美启动。 109 | 110 | 但是我们总是热衷于使用 `Nginx` 来反向代理我们的`web 服务`,`Nginx` 似乎能够让我们的网站更像一个网站。 111 | 112 | 更重要的方面是 `Nginx` 确实是一款高效实用的`反向代理服务器`,安装简单,配置容易,支持负载均衡,确为线上部署的一把利器,受到广泛开发者的青睐也在情理之中了。 113 | 114 | > 接下来我们要构建 `Uvicorn` + `Nginx` 这套组合,并让这套组合拳有效的运行于我们的服务器中。 115 | 116 | `Nginx` 的可配置项非常多,我们这里不做过多的详述,毕竟篇幅不小。 117 | 118 | 这里假设已经成功搭建并启动了 `Nginx` 反向代理服务器,如果未启动则通过文档中的命令重启即可。 119 | 120 | 查看 `Nginx` 安装的所在目录: 121 | 122 | ```shell 123 | [root@~] whereis nginx 124 | nginx: /usr/local/nginx 125 | ``` 126 | 127 | 进入` /usr/local/nginx/conf` 并编辑 `nginx.conf`配置文件 128 | 129 | ```shell 130 | upstream app { 131 | ip_hash; 132 | server localhost:8080; 133 | } 134 | 135 | server { 136 | listen 80; 137 | server_name localhost; 138 | 139 | location /static/ { 140 | autoindex on; 141 | alias /code/collected_static/; 142 | } 143 | 144 | location / { 145 | proxy_pass http://app/; 146 | } 147 | } 148 | ``` 149 | 150 | 这样 `Nginx` 将监听 `:8080 `端口服务并转发到宿主机的` 80` 端口上,如果是`前后端分离`项目则不做 `location /static/` 配置,只提供 `api` 接口服务即可。 151 | 152 | 否则则要声明项目的静态文件目录,比如你使用的是 Vue 编写的前端项目,那么需要将打包好的前端项目放于 `location /static/` 之内,通过直接访问 `Vue`页面,再由`Vue`调用我们的后端接口 153 | 154 | 验证 `Nginx` 配置文件是否异常: 155 | 156 | ```shell 157 | [root@gzky_gz conf] /usr/local/nginx/sbin/nginx -t 158 | nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok 159 | nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful 160 | ``` 161 | 162 | 出现以上信息则说明配置文件无误,否则根据报错行数修正配置文件即可。 163 | 164 | > 只要确保 `:8080` 服务正常和`Nginx 配置`文件无异常,项目将顺利启动。 165 | 166 | 通过 `http://IP `地址访问项目根路径,打开控制台发现由 `Nginx` 提供服务。 167 | 168 | ![](images/Nginx.png) 169 | 170 | 需要注意的是如果我们使用 `https://IP ` 访问项目会出现 `无法访问此网站` 的情况,原因是我们并没有做 https 配置和重定向,这需要一些 ssl 证书的支持,这个不做详述。 171 | 172 | ## 如何使用 `Docker` 进行部署? 173 | 174 | > `Docker` 是一个开源的应用容器引擎,基于 `Go` 语言 并遵从 `Apache2.0 `协议开源。 175 | > `Docker` 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 `Linux` 机器上,也可以实现虚拟化。 176 | > 号称`远程小型虚拟机`,但是更轻更快 ! 177 | 178 | 当你应对不同的语言版本、不同的框架版本而举足无措的时候,`docker` 可能成为你的第一选择。 179 | 180 | 假设我们的工程目录 `code`,请在 `code` 下: 181 | 182 | - 生成 `requirements.txt` 文件 183 | 184 | ```shell 185 | pip freeze > requirements.txt 186 | ``` 187 | 188 | - `Nginx` 配置文件 189 | 190 | 在 `code/config/nginx` 下新建 `web_app.conf` 文件并写入以下配置 191 | 192 | ```shell 193 | upstream app { 194 | ip_hash; 195 | server localhost:8080; 196 | } 197 | 198 | server { 199 | listen 80; 200 | server_name localhost; 201 | 202 | location /static/ { 203 | autoindex on; 204 | alias /code/collected_static/; 205 | } 206 | 207 | location / { 208 | proxy_pass http://app/; 209 | } 210 | } 211 | ``` 212 | 213 | - 构建 `Dockerfile` 文件 214 | 215 | ```shell 216 | # 基于 Py3.8 构建 217 | FROM python:3.8 218 | ENV PYTHONUNBUFFERED 1 219 | 220 | # 换源 221 | RUN echo \ 222 | deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free\ 223 | deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free\ 224 | deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free\ 225 | deb https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free\ 226 | > /etc/apt/sources.list 227 | 228 | # 更新源 229 | RUN apt-get update 230 | 231 | # 新建和指定工程目录为 code 232 | RUN mkdir /code 233 | WORKDIR /code 234 | # 升级 pip 和安装依赖 235 | RUN pip install pip -U -i https://pypi.tuna.tsinghua.edu.cn/simple 236 | ADD requirements.txt /code/ 237 | RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 238 | ADD . /code/ 239 | ``` 240 | 241 | `docker` 本质上是`Linux`操作系统上的一个进程,但又是一个独立的环境,容器间相互隔离,同样具有 `Unix` 内核。 242 | 243 | ### 使用 `Docker-compose` 244 | 245 | 既然我们已经在本地安装了 `Python`版本,可以使用 `pip` 安装 `Docker-compose` 246 | 247 | ```shell 248 | pip install Docker-compose 249 | ``` 250 | 251 | 安装后即刻生效,验证是否正常工作: 252 | 253 | ```shell 254 | [root@gzky_gz conf]# Docker-compose -v 255 | Docker-compose version 1.28.6 256 | ``` 257 | 258 | ### 构建 `docker-compose.yml` 文件 259 | 260 | ```yml 261 | version: "3" 262 | services: 263 | app: 264 | restart: always 265 | build: . 266 | # 启动命令 267 | command: bash -c "nohup uvicorn main:app --workers 4 --host=0.0.0.0 --port 8080 >> ./output.log 2>&1 &" 268 | # 挂载 code 目录 269 | volumes: 270 | - .:/code 271 | # expose 只对内部暴露 8080 272 | expose: 273 | - "8080" 274 | networks: 275 | - web_network 276 | nginx: 277 | restart: always 278 | image: nginx:latest 279 | ports: 280 | - "80:80" 281 | volumes: 282 | - ./config/nginx:/etc/nginx/conf.d 283 | depends_on: 284 | - app 285 | networks: 286 | - web_network 287 | networks: 288 | web_network: 289 | driver: bridge 290 | ``` 291 | 292 | ### 开始 `build` 并启动容器 293 | 294 | ```shell 295 | # 先不要 -d 后台启动,看看build详细情况 296 | Docker-compose up 297 | ``` 298 | 299 | 此命令会顺序执行: 300 | 301 | - 编译 `Dockerfile` 文件,会在`docker`安装 `Python3.8` 镜像 以及`requirements.txt`中的所有库 302 | - 安装 `Nginx` 最新版本并同步挂载`./config/nginx` 目录到 容器中的 `/etc/nginx/conf.d` 目录,也就是修改其配置文件了 303 | - 启动`web服务`暴露 `:8080`,这里没使用`ports` 端口映射是因为我们只是把`8080`端口转发给 `Nginx` 而已,而不是对外开放 304 | - 启动 `Nginx`,注意是要对外暴露 `80` 端口,所以使用 `ports` 关键字 305 | 306 | 容器启动成功后,通过 `http://IP` 地址访问项目,实际效果和本地宿主机部署一样。 307 | 308 | ## 如何集成 `Vue` 项目? 309 | 310 | 用官方的话说,`Vue` 是一套用于构建用户界面的渐进式框架。 与其它大型框架不同的是,`Vue` 被设计为可以自底向上逐层应用。 311 | 312 | 这里我们主要注重基于 `Fastapi` + `Vue` 的前后端分离的部署部分。 313 | 314 | 如果你没有做一些额外的特殊的设置的话: 315 | 316 | ```shell 317 | npm run build 318 | ``` 319 | 320 | 通过以上命令可以对 `Vue` 项目进行打包,不出意外的话就生成一个默认名为 `dist` 的静态文件目录,也就是最后打包的结果了。 321 | 322 | 至于`fastapi`的后端接口则可以存在于任何一台有`公网ip`的机器。 323 | 324 | ## 如何使用 `Nginx`部署`Vue`项目? 325 | 326 | 在 `nginx.conf`中编辑以下配置 327 | 328 | ```shell 329 | server { 330 | listen 80; 331 | server_name localhost; 332 | 333 | location / { 334 | root /code/dist; 335 | index index.html index.html; 336 | } 337 | } 338 | ``` 339 | 340 | 让 `Nginx` 找到 `/code/dist` 目录并将目录下的`index.html` 作为`Nginx`首页,`纯静态部署`总是如此的简单。 341 | 342 | ## 如何部署多个`Vue`项目? 343 | 344 | 虽然一个`Nginx`也能够部署多个 `Vue`项目,但是我还是不建议这样的做法,一旦`Vue`项目体量过大,调度的接口过多,则会发生性能不足的情况。 345 | 可以使用搭建多个`Nginx` 服务器来挂载多个`Vue`项目(或其他前端项目),一个`Nginx`对应一个`Vue`项目。 346 | 而理论上,只要我们的机器的`cpu`和`内存`足够,可以搭建很多很多的`Nginx`,只需改变其默认端口即可。 347 | 348 | ## 如何使用 acme.sh 部署 SSL 证书? 349 | 350 | ### 安装 acme.sh 351 | 352 | 安装很简单,就一个命令: 353 | 354 | ```bash 355 | curl https://get.acme.sh | sh 356 | ``` 357 | 358 | 普通用户和 root 用户都可以安装使用。这会安装在 ~/.acme.sh/ 目录下,以后生成的证书也会在这里面,按照域名为文件夹安置。 359 | 360 | 理论上会自动添加一个 acme.sh 别名,但有时候并不会生成,需要手动执行以下命令: `source ~/.bashrc` 361 | 362 | ### 使用 dns api 的模式进行证书申请 363 | 364 | 获取 AccessKey ID和AccessKey Secret 365 | 366 | ```bash 367 | export Ali_Key="key" 368 | export Ali_Secret="key Secret" 369 | 370 | # 下次就不用再次执行这个命令了 371 | ./acme.sh --issue --dns dns_ali -d *.example.com --force 372 | ``` 373 | 374 | ### 生成 .key 和 .crt证书 375 | 376 | ```shell 377 | ./acme.sh --install-cert -d "*.gzsonic.com" --cert-file cert.crt --key-file key.key --fullchain-file ca.crt 378 | ``` 379 | 380 | .key 和 .crt证书可用于 nginx 证书部署. 381 | 382 | > 参考: https://github.com/acmesh-official/acme.sh 383 | 384 | ### 自动更新证书 385 | 386 | Let's 的证书有效期为60天 387 | 388 | 目前手动添加DNS获取证书的方式无法自动更新,但是使用DNS API的方式进行获取证书可以在 60 天以后会自动更新, 你无需任何操作. 389 | 390 | 强制执行更新任务: `acme.sh --cron -f` 391 | 392 | ### 升级 393 | 394 | 目前由于 acme 协议和 Let`s CA 都在频繁的更新, 因此 acme.sh 也经常更新以保持同步. 395 | 396 | 升级 acme.sh 到最新版 : `acme.sh --upgrade` 397 | 398 | 如果你不想手动升级, 可以开启自动升级: `acme.sh --upgrade --auto-upgrade` 之后, acme.sh 就会自动保持更新了. 399 | 400 | 你也可以随时关闭自动更新: `acme.sh --upgrade --auto-upgrade 0` 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /docs/how-to-deploy-using-docker.md: -------------------------------------------------------------------------------- 1 | # 如何使用 Docker 2 | 3 | ## 什么是 Docker 应用? 4 | 5 | 使用Docker部署应用是当今开发和运维领域的一种流行做法,它不仅提高了开发和部署的效率,还通过容器化技术保证了应用的可移植性和安全性。 6 | 以下是一个更详细的指南,帮助你使用Docker进行应用部署。 7 | 8 | ## Docker 有什么优势? 9 | 10 | - **灵活性**:Docker可以将应用及其依赖打包成一个镜像,无论应用环境多么复杂,都可以轻松管理和迁移。 11 | - **便携性**:镜像一旦被打包,可以在任何支持Docker的环境中运行,确保了一致的运行效果。 12 | - **高效性**:容器直接运行在宿主机的操作系统内核上,不需要额外的虚拟化开销,提高了资源利用率。 13 | - **安全性**:容器之间相互隔离,即使一个容器崩溃,也不会影响到其他容器。 14 | 15 | ## 如何构建 Docker 镜像? 16 | 17 | ### 1. 构建Docker镜像 18 | 19 | 首先,需要有一个`Dockerfile`,它是一个文本文件,包含了构建Docker镜像所需的指令和步骤。 20 | 21 | ```shell 22 | docker build -t app_name -f Dockerfile . 23 | ``` 24 | 25 | 这条命令会根据当前目录下的`Dockerfile`文件内容,构建一个名为`app_name`的Docker镜像。 26 | 27 | ### 2. 保存镜像为文件(可选) 28 | 29 | 构建完成后,可以将Docker镜像保存为文件,方便迁移和备份。 30 | 31 | ```shell 32 | docker save app_name:latest | gzip > app_name.tar.gz 33 | ``` 34 | 这条命令会将`app_name`镜像保存为一个名为`app_name.tar.gz`的压缩文件。 35 | 36 | ## 如何启动 Docker 容器? 37 | 38 | ### Redis部署 39 | 40 | ```shell 41 | docker run -d --name redis \ 42 | --restart=always \ 43 | -v /path/to/redis/conf/redis.conf:/etc/redis.conf \ 44 | -v /path/to/redis/data:/data \ 45 | -p 6379:6379 \ 46 | redis:5.0.3 redis-server /etc/redis.conf 47 | ``` 48 | 49 | ### MySQL部署 50 | 51 | ```shell 52 | docker run -d --name mysql \ 53 | --restart=always \ 54 | -e MYSQL_ROOT_PASSWORD=123456 \ 55 | -e MYSQL_TZ='+8:00' \ 56 | -v /home/server/mysql/datadb:/var/lib/mysql \ 57 | -v /home/server/mysql/mysql.conf.d:/etc/mysql/mysql.conf.d \ 58 | -p 3306:3306 mysql:5.7.30 59 | ``` 60 | 61 | ### MongoDB部署 62 | 63 | ```shell 64 | docker run -d --name mongodb \ 65 | --restart=always \ 66 | -e MONGO_INITDB_ROOT_USERNAME=admin \ 67 | -e MONGO_INITDB_ROOT_PASSWORD=123456 \ 68 | -v /home/server/mongo/datadb:/data/db \ 69 | -p 27017:27017 mongo:4.4.6 70 | ``` 71 | 72 | 这条命令会以守护进程方式运行Redis容器,使用自定义的配置文件和数据目录,并将容器的6379端口映射到宿主机的6379端口。 73 | 74 | ## 如何删除或清理 Docker 容器? 75 | 76 | ```shell 77 | docker ps -a | grep Exited | awk '{print $1}' | xargs docker rm 78 | ``` 79 | 这条命令会查找所有状态为Exited的容器,并将它们删除。 80 | 81 | ```shell 82 | docker images -q --filter "dangling=true" | xargs docker rmi 83 | ``` 84 | 这条命令会删除所有未被标记或未被容器使用的镜像,释放空间。 85 | 86 | Docker的维护命令可以帮助你保持环境的清洁和高效。记得在实际部署前,根据自己的需要调整命令中的参数,如镜像名、容器名、端口映射等。 87 | 88 | ## 如何搭建 Docker 可视化管理? 89 | 90 | Portainer 是一款开源的轻量级管理工具,旨在帮助用户通过Web用户界面轻松管理Docker或Kubernetes环境。它的直观界面使得即使是Docker和Kubernetes的新手也能快速上手,同时也为高级用户提供了强大的功能。下面是如何安装和使用Portainer进行Docker环境管理的详细指南。 91 | 92 | ### Portainer 的特点 93 | 94 | - **直观的Web界面**:提供用户友好的界面,简化容器、镜像、网络和卷的管理。 95 | - **全面的管理功能**:支持容器的生命周期管理,包括创建、启动、停止、删除等操作,以及镜像、网络和卷的管理。 96 | - **实时监控**:查看容器日志、资源使用情况、环境变量等信息,帮助用户监控和调试应用。 97 | - **多用户支持**:支持多用户登录和权限管理,适合团队协作。 98 | - **跨平台支持**:不仅可以管理Docker,还可以管理Kubernetes集群。 99 | 100 | ### 安装 Portainer 101 | 102 | Portainer 可以作为一个容器运行在任何安装了Docker的机器上。下面是安装Portainer的步骤: 103 | 104 | 1. **运行 Portainer 容器** 105 | 106 | 使用以下命令来启动Portainer容器: 107 | ```shell 108 | docker run -d -p 9000:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer/portainer 109 | ``` 110 | 111 | 2. **访问 Portainer** 112 | 113 | 安装完成后,通过浏览器访问 `http://<你的机器IP或域名>:9000` 114 | 来打开Portainer的Web界面。首次访问时,系统会提示你创建一个管理员账户。完成注册后,登录到Portainer的仪表板。 115 | 116 | ### 使用 Portainer 117 | 118 | 在Portainer的Web界面中,你可以进行以下操作: 119 | 120 | - **容器管理**:在“容器”菜单下,你可以查看所有运行中和已停止的容器,创建新容器,以及启动、停止、删除或进入容器的控制台。 121 | - **镜像管理**:在“镜像”菜单下,你可以查看本地存储的镜像,拉取新镜像,或删除无用镜像。 122 | - **网络管理**:在“网络”菜单下,你可以查看和管理Docker网络,创建新网络或删除不再需要的网络。 123 | - **数据卷管理**:在“卷”菜单下,你可以管理Docker的数据卷,这对于持久化容器数据非常重要。 124 | - **查看日志和监控**:Portainer允许你查看容器的实时日志和历史日志,以及容器的资源使用情况,帮助你监控和调试应用。 125 | 126 | Portainer还提供了更高级的功能,如堆栈、服务、秘密和配置管理,特别是在Docker 127 | Swarm或Kubernetes环境中。通过这些功能,Portainer成为了一个非常强大的容器管理工具,无论是对于个人开发者还是团队来说都是一个很好的选择。 128 | 129 | ![Portainer](./images/portainer_demo.png) 130 | 131 | ## Harbor 访问控制 132 | 133 | 什么是 Harbor? 134 | 135 | Harbor 是一个先进的企业级容器镜像仓库(Registry),它为存储、签名和扫描容器镜像提供了丰富的功能,确保容器化应用的安全性和一致性。作为一个由中国网易公司捐赠给Cloud 136 | Native Computing Foundation(CNCF)并已毕业的项目,Harbor 在全球范围内被广泛认可和使用,特别是在需要严格遵守数据治理和安全性要求的企业环境中。 137 | 138 | ### Harbor 的核心特性 139 | 140 | 1. **基于角色的访问控制(RBAC)**:Harbor 允许管理员根据用户的角色分配不同的权限,从而细粒度地控制对镜像仓库的访问。这种方式确保只有授权的用户才能推送、拉取或管理镜像。 141 | 142 | 2. **漏洞扫描**:Harbor 集成了漏洞扫描工具,如Clair和Trivy,能够自动扫描仓库中的镜像以识别已知的安全漏洞。这有助于保证部署到生产环境中的镜像是安全的。 143 | 144 | 3. **内容信任**:通过与Notary的集成,Harbor 支持Docker Content Trust,允许用户签名镜像并验证镜像的完整性和来源,确保部署的镜像未被篡改。 145 | 146 | 4. **复制策略**:Harbor 支持跨多个注册表的镜像复制,这意味着企业可以轻松地在多个环境或地理位置之间同步镜像,支持灾难恢复和数据本地化需求。 147 | 148 | 5. **高可用性和多租户**:Harbor 设计考虑到了企业级的高可用性和多租户需求,支持横向扩展以适应高负载,同时通过项目来实现资源隔离,满足多团队或多项目的管理需求。 149 | 150 | ### 为什么选择 Harbor 而不是 DockerHub? 151 | 152 | 尽管 DockerHub 是最流行的公共容器镜像仓库,提供了大量的社区镜像资源,但对于企业用户来说,选择 Harbor 作为私有或内部镜像仓库有以下几个理由: 153 | 154 | - **访问速度**:对于在中国或其他网络连接到DockerHub较慢的地区的用户,使用Harbor部署在本地或者更靠近用户的数据中心可以显著提高镜像拉取的速度。 155 | 156 | - **安全和合规性**:Harbor 提供了细粒度的访问控制和安全扫描功能,帮助企业满足严格的安全和合规性要求。同时,通过内容信任和镜像签名,确保镜像的安全性和完整性。 157 | 158 | - **自主控制**:与将镜像存储在公共云服务如DockerHub相比,部署Harbor使企业能够完全控制其镜像仓库,包括数据的存储位置、访问策略和管理过程。 159 | 160 | - **成本控制**:对于需要大量存储和带宽的企业,使用Harbor可以更有效地控制成本,特别是可以通过内部网络来分发镜像,避免了使用外部服务可能产生的额外费用。 161 | 162 | Harbor 通过其企业级特性,如基于角色的访问控制、漏洞扫描、内容信任、复制策略以及对高可用性和多租户的支持,提供了一个安全、可靠和高效的容器镜像管理平台。对于追求高性能、安全合规以及自主控制的组织来说,Harbor 163 | 是一个理想的选择。 -------------------------------------------------------------------------------- /docs/how-to-integrate-plugins.md: -------------------------------------------------------------------------------- 1 | # 如何为 `FastAPI` 集成插件 2 | 3 | ## 如何使用 Apscheduler 进行任务调度? 4 | 5 | `Apscheduler` 可能是作为 `fastapi` 任务队列比较理想的选择,毕竟 `Celery` 有点大,而且 `Celery` 本身就是一个独立的服务,`Apscheduler` 可以直接集成到 `fastapi` 项目中。 6 | 7 | [https://apscheduler.readthedocs.io/en/stable/userguide.html](https://apscheduler.readthedocs.io/en/stable/userguide.html) 8 | 9 | ```shell 10 | $ pip install apscheduler 11 | ``` 12 | 13 | ### `APScheduler` 组件 14 | 15 | - `触发对象`(声明要触发的类型以及参数) 16 | - `JOB存储`(`Redis、MongoDB`) 17 | - `执行器`(`AsyncIOExecutor`) 18 | - `调度器`(`BackgroundScheduler、AsyncIOScheduler` ) 19 | 20 | ### `Apscheduler` 模块 21 | 22 | - `BlockingScheduler`:当调度程序是您的流程中唯一运行的东西时使用 23 | - `BackgroundScheduler`:在不使用以下任何框架且希望调度程序在应用程序内部的后台运行时使用 24 | - `AsyncIOScheduler`:如果您的应用程序使用`asyncio`模块,则使用 25 | - `GeventScheduler`:如果您的应用程序使用`gevent`,则使用 26 | - `TornadoScheduler`:在构建`Tornado`应用程序时使用 27 | - `TwistedScheduler`:在构建`Twisted`应用程序时使用 28 | - `QtScheduler`:在构建`Qt`应用程序时使用 29 | 30 | `BlockingScheduler` 和 `BackgroundScheduler` 自不必说,用于非异步操作下的任务队列调度。 31 | 32 | 而 `fastapi` 是一个基于 `asyncio` 的异步框架,自然就是 `AsyncIOScheduler` 调度器啦 33 | 34 | `TornadoScheduler` 调度器常用于 `Tornado` 框架,这也是 `Python` 另一个牛逼的异步框架,并且比 `fastapi` 更早。实际上 `fastapi` 是 `stsrlette` 35 | 的二次产物,这个我们在开篇的时候早已说过。 36 | 37 | `TwistedScheduler` 调度器可用于 `Scrapy` 框架等基于`Twisted` 的应用程序。 38 | 39 | ### `Apscheduler` 触发器类型 40 | 41 | - `date`:在您希望在特定时间点仅运行`一次`作业时使用 42 | - `interval`:在您要以固定的`时间间隔`运行作业时使用 43 | - `cron`:当您想在一天中的特定时间`定期`运行作业时使用 44 | 45 | ### `Apscheduler` 持久化 46 | 47 | 为了项目或者程序异常重启和查看最近的任务队列信息,我们应该使用持久化存储 `Apscheduler` 的任务信息,这里以 Redis 为例。 48 | 49 | ```python 50 | # -*- coding: utf-8 -*- 51 | 52 | import asyncio 53 | import datetime 54 | 55 | from apscheduler.events import EVENT_JOB_EXECUTED 56 | from apscheduler.executors.asyncio import AsyncIOExecutor 57 | from apscheduler.jobstores.redis import RedisJobStore 58 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 59 | from apscheduler.triggers.interval import IntervalTrigger 60 | 61 | # redis 存储job信息 62 | default_redis_jobstore = RedisJobStore( 63 | db=2, 64 | jobs_key="apschedulers.default_jobs", 65 | run_times_key="apschedulers.default_run_times", 66 | host="127.0.0.1", 67 | port=6379, 68 | password=None 69 | ) 70 | 71 | # asyncio是的调度执行器 72 | async_executor = AsyncIOExecutor() 73 | 74 | # 初始化scheduler,直接指定 jobstore 和 executor 75 | init_scheduler_options = { 76 | "jobstores": { 77 | "redis": default_redis_jobstore 78 | }, 79 | "executors": { 80 | "redis": async_executor 81 | }, 82 | 83 | "job_defaults": { 84 | 'coalesce': False, # 是否合并执行 85 | 'max_instances': 8 # 最大实例数 86 | } 87 | } 88 | # 创建 scheduler 实例,字典形式参入 89 | scheduler = AsyncIOScheduler(**init_scheduler_options) 90 | 91 | # 启动调度 92 | scheduler.start() 93 | ``` 94 | 95 | - 我们使用了 `AsyncIOExecutor` 执行器 96 | - 我们使用了 `AsyncIOScheduler` 调度器 97 | - 我们使用 `redis` 命名了 `jobstores` 和 `executors` 98 | 99 | ### 准备两个被调函数 100 | 101 | ```python 102 | def interval_func(message): 103 | print(f'普通函数 ==> {message} ==> {format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}') 104 | 105 | 106 | async def async_func(message): 107 | print(f'异步函数 ==> {message} ==> {format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}') 108 | ``` 109 | 110 | 这里我们准备了一个 `普通函数`和一个`异步函数`,不出意外的话对于 `AsyncIOScheduler` 而言可以正常调度这两个方法。 111 | 112 | ### 监听事件 113 | 114 | ```python 115 | from apscheduler.events import EVENT_JOB_EXECUTED 116 | 117 | 118 | def job_execute(event): 119 | """ 120 | 监听事件处理 121 | :param event: 122 | :return: 123 | """ 124 | print(f"job执行: job.id => {event.job_id} \t jobstore =>{event.jobstore}") 125 | 126 | 127 | # 这就相当于一个钩子方法(程序回调),这里简单的打印一下信息 128 | scheduler.add_listener(job_execute, EVENT_JOB_EXECUTED) 129 | ``` 130 | 131 | ### 开启定时任务 132 | 133 | ```python 134 | scheduler.add_job(interval_func, "interval", 135 | args=["15s执行一次"], # 方法参数 136 | seconds=15, # 时间间隔 137 | id="interval_func_test", # 指定任务 ID,如果不指定默认 UUID 指 138 | jobstore="redis", # 指定工作存储器,前面定义的 Redis,找不到工作存储将报错 139 | executor="redis", # 指定执行器,前面定义的 Redis,找不到执行器将报错 140 | start_date=datetime.datetime.now(), # now 开始 141 | end_date=datetime.datetime.now() + datetime.timedelta(seconds=240) # 结束事件,240 s后结束 142 | ) 143 | 144 | print(f'任务启动 ==> 当前时间: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}') 145 | 146 | # asyncio 启动 147 | asyncio.get_event_loop().run_forever() 148 | ``` 149 | 150 | ### 完整代码示例 151 | 152 | ```python 153 | # -*- coding: utf-8 -*- 154 | 155 | import asyncio 156 | import datetime 157 | 158 | from apscheduler.events import EVENT_JOB_EXECUTED 159 | from apscheduler.executors.asyncio import AsyncIOExecutor 160 | from apscheduler.jobstores.redis import RedisJobStore # 需要安装redis 161 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 162 | from apscheduler.triggers.interval import IntervalTrigger 163 | 164 | # 定义 jobstore 使用 redis 存储job信息 165 | default_redis_jobstore = RedisJobStore( 166 | db=2, 167 | jobs_key="apschedulers.default_jobs", 168 | run_times_key="apschedulers.default_run_times", 169 | host="127.0.0.1", 170 | port=6379, 171 | password=None 172 | ) 173 | 174 | # 定义executor 使用asyncio是的调度执行规则 175 | async_executor = AsyncIOExecutor() 176 | 177 | # 初始化scheduler时,可以直接指定jobstore和executor 178 | init_scheduler_options = { 179 | "jobstores": { 180 | "redis": default_redis_jobstore 181 | }, 182 | "executors": { 183 | "redis": async_executor 184 | }, 185 | 186 | "job_defaults": { 187 | 'coalesce': False, # 是否合并执行 188 | 'max_instances': 8 # 最大实例数 189 | } 190 | } 191 | # 创建scheduler 192 | scheduler = AsyncIOScheduler(**init_scheduler_options) 193 | 194 | # 启动调度 195 | scheduler.start() 196 | 197 | 198 | def job_execute(event): 199 | """ 200 | :param event: 201 | :return: 202 | """ 203 | print(f"job执行: job.id => {event.job_id} \t jobstore =>{event.jobstore}") 204 | 205 | 206 | # 这就相当于一个钩子方法(程序回调),这里简单的打印一下信息 207 | scheduler.add_listener(job_execute, EVENT_JOB_EXECUTED) 208 | 209 | 210 | def interval_func(message): 211 | print(f'普通函数 ==> {message} ==> {format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}') 212 | 213 | 214 | async def async_func(message): 215 | print(f'异步函数 ==> {message} ==> {format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}') 216 | 217 | 218 | scheduler.add_job(interval_func, "interval", 219 | args=["10s执行一次"], 220 | seconds=10, 221 | id="interval_func_test", 222 | jobstore="redis", 223 | executor="redis", 224 | start_date=datetime.datetime.now(), 225 | end_date=datetime.datetime.now() + datetime.timedelta(seconds=240)) 226 | 227 | print(f'任务启动 ==> 当前时间: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}') 228 | 229 | asyncio.get_event_loop().run_forever() 230 | ``` 231 | 232 | 控制台结果: 233 | 234 | ```text 235 | 任务启动 ==> 当前时间: 2021-05-18 16:19:02 236 | 普通函数 ==> 10s执行一次 ==> 2021-05-18 16:19:12 237 | job执行: job.id => interval_func_test jobstore =>redis 238 | 普通函数 ==> 10s执行一次 ==> 2021-05-18 16:19:22 239 | job执行: job.id => interval_func_test jobstore =>redis 240 | 普通函数 ==> 10s执行一次 ==> 2021-05-18 16:19:32 241 | job执行: job.id => interval_func_test jobstore =>redis 242 | ... 243 | ``` 244 | 245 | 说明任务被正常调度,如期所料。 246 | 247 | 任务信息被存入 `Redis` 数据库,`apschedulers.default_jobs` 和 `apschedulers.default_run_times` 对应着初始化 `RedisJobStore` 248 | 时传入的 `jobs_key` 和 `run_times_key`,在 `Redis` 中以 `hash键` 存在 249 | 250 | ```python 251 | default_redis_jobstore = RedisJobStore( 252 | db=2, 253 | jobs_key="apschedulers.default_jobs", 254 | run_times_key="apschedulers.default_run_times", 255 | host="127.0.0.1", 256 | port=6379, 257 | password=None 258 | ) 259 | ``` 260 | 261 | 一旦已定义的 `ID` 存在于 `Redis` 中,`add_job` 方法将会失效并抛出一个`异常` 262 | 263 | ```shell 264 | Traceback (most recent call last): 265 | File "D:\conda3\lib\site-packages\apscheduler\schedulers\base.py", line 448, in add_job 266 | self._real_add_job(job, jobstore, replace_existing) 267 | File "D:\conda3\lib\site-packages\apscheduler\schedulers\base.py", line 872, in _real_add_job 268 | store.add_job(job) 269 | File "D:\conda3\lib\site-packages\apscheduler\jobstores\redis.py", line 77, in add_job 270 | raise ConflictingIdError(job.id) 271 | apscheduler.jobstores.base.ConflictingIdError: 'Job identifier (interval_func_test) conflicts with an existing job' 272 | ``` 273 | 274 | 报错信息时 `job` 已存在,也就是说任务 `ID` 冲突了。这时候要么更换 `ID`值,要么删除 `Redis` 中的原有`ID`后重新设置。 275 | 276 | `apscheduler` 调度器同时提供了 `get_job` 和 `remove_job` 方法,参数就是任务 `ID` 和 `jobstore`。 277 | 278 | ```python 279 | if scheduler.get_job(job_id="interval_func_test", jobstore="redis"): 280 | print("key interval_func_test 存在 干掉它") 281 | scheduler.remove_job(job_id="interval_func_test", jobstore="redis") 282 | ``` 283 | 284 | 这样每次运行前判断任务是否存在,存在则干掉它,就不会产生冲突了。 真是个小机灵鬼。 285 | 286 | 需要注意的是,如果 `redis`队列中有任务时,当我们重新启动 `apscheduler` 时, 287 | `apscheduler` 会有一个任务消费的机制,详细请下面几个参数。注意另一个`hash键`记录着任务添加的`时间戳`。 288 | 289 | - `max_instances` 最多可以`并发`多少个`实例`,可以在初始化调度器的时候设置一个`全局默认值`,添加任务时可以再单独指定 290 | - `coalesce` 值为 `True` 或 `False`,如果 `True`,那么下次这个 `job` 被提交给执行器时,只会执行 `1` 次,也就是最后这次,如果为 `False`,那么会执行堆积`所有次数`(比如某时刻突然断电,再次重启时发现队列中已经堆积了`10`个任务,那么就会执行 `10` 次该任务),但是不是绝对的,下面这个参数可能影响它。 291 | - `misfire_grace_time`:单位为秒,`misfire_grace_time` 参数则是在线程池有可用线程时会比对该 `job` 的应调度时间跟当前时间的`差值`,如果差值`小于` `misfire_grace_time` 时,调度器会再次调度该 `job`;反之该 `job` 的执行状态为 `EVENTJOBMISSED` 了,即`错过运行`。也就是: 给`executor` 一个`容错`的`超时时间`,这个时间范围内要是该跑的`还没跑完`,就别再跑了。 292 | 293 | `coalesce` 与 `misfire_grace_time` 可以在初始化调度器的时候设置一个全局默认值,添加任务时可以再单独指定(即覆盖)。 294 | 295 | 296 | ```python 297 | 298 | import asyncio 299 | import datetime 300 | 301 | from apscheduler.events import EVENT_JOB_EXECUTED 302 | from apscheduler.executors.asyncio import AsyncIOExecutor 303 | from apscheduler.jobstores.redis import RedisJobStore # 需要安装redis 304 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 305 | from apscheduler.triggers.interval import IntervalTrigger 306 | 307 | # 定义 jobstore 使用 redis 存储job信息 308 | default_redis_jobstore = RedisJobStore( 309 | db=2, 310 | jobs_key="apschedulers.default_jobs", 311 | run_times_key="apschedulers.default_run_times", 312 | host="127.0.0.1", 313 | port=6379, 314 | password=None 315 | ) 316 | 317 | # 定义executor 使用asyncio是的调度执行规则 318 | async_executor = AsyncIOExecutor() 319 | 320 | # 初始化scheduler时,可以直接指定jobstore和executor 321 | init_scheduler_options = { 322 | "jobstores": { 323 | "redis": default_redis_jobstore 324 | }, 325 | "executors": { 326 | "redis": async_executor 327 | }, 328 | 329 | "job_defaults": { 330 | 'coalesce': False, # 是否合并执行 331 | 'max_instances': 1000, # 支持1000个实例并发 332 | 'misfire_grace_time': 30 # 30秒的任务超时容错 333 | } 334 | } 335 | # 创建scheduler 336 | scheduler = AsyncIOScheduler(**init_scheduler_options) 337 | 338 | # 启动调度 339 | scheduler.start() 340 | 341 | print(f'任务启动 ==> 当前时间: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}') 342 | asyncio.get_event_loop().run_forever() 343 | ``` 344 | 345 | 该程序启动后会根据 **`misfire_grace_time`** 和 **调度时间跟当前时间的`差值`** 做比较,如果差值大于`misfire_grace_time` ,则不执行`堆积`的任务。 346 | 347 | 同时会从队列中清除该任务。 348 | 349 | ### 关闭调度器 350 | 351 | `scheduler.shutdown()` 352 | 353 | 默认情况下,会关闭 `job` 存储和执行器,并等待所有正在执行的 `job` 完成。如果不想等待则可以使用以下方法关闭,也就是`强制关闭`: 354 | 355 | `scheduler.shutdown(wait=False)` 356 | 357 | ### `暂停`/`恢复`调度器 358 | 359 | 暂停调度器: 360 | `scheduler.pause()` 361 | 362 | 恢复调度器: 363 | `scheduler.resume()` 364 | 365 | 366 | -------------------------------------------------------------------------------- /docs/how-to-optimize-performance.md: -------------------------------------------------------------------------------- 1 | # 如何优化后端服务的性能 2 | 3 | ## cpu密集型计算的场景有哪些? 4 | 5 | CPU密集型计算的场景是指那些需要大量CPU计算资源来处理任务的应用场景。 6 | 这类计算通常涉及到复杂的数学和逻辑运算,而与磁盘I/O、网络通信或用户交互相比,CPU的运算能力是这些任务的瓶颈。 7 | 8 | 以下是一些典型的CPU密集型计算场景: 9 | 10 | - 科学计算:物理模拟、气候模型、分子建模、天体物理学等领域需要大量的数值计算。 11 | - 数据分析:大数据处理、数据挖掘、机器学习模型训练等需要进行复杂的数据处理和分析。 12 | - 图形渲染:3D图形渲染和视频编码通常需要大量的CPU资源来计算光线追踪、纹理映射等。 13 | - 游戏:现代计算机游戏中的物理引擎和AI计算通常需要大量的CPU资源。 14 | - Web服务器:在高流量的情况下,动态内容生成(如模板渲染)可能会导致CPU资源成为瓶颈。 15 | - 数据库服务器:在处理复杂查询或大量并发请求时,数据库服务器可能会遇到CPU瓶颈。 16 | 17 | 更具体的后端应用下,我们通常会涉及到大量的数据处理、计算和逻辑运算,这些任务可能会成为CPU密集型任务的瓶颈。 18 | 19 | 比如: 20 | 21 | - 在处理一些的用户请求时,会涉及到大量的模板渲染和逻辑计算 22 | - 对于服务器,在高并发的场景下,系统cpu开始繁忙的处理用户请求(上下文切换,同步和锁竞争等) 23 | - 对于数据库,处理复杂的查询和大量的并发请求 24 | 25 | 这些操作会消耗大量的CPU资源。 26 | 27 | ## IO密集型计算的场景有哪些? 28 | 29 | I/O密集型计算的场景是指那些需要大量I/O资源(如磁盘I/O、网络I/O)来处理任务的应用场景。 30 | 31 | - 文件处理:大量的文件读写操作,如文件复制、文件压缩、文件解压等。 32 | - 数据库操作:大量的数据库读写操作,如查询、插入、更新、删除等。 33 | - 网络通信:大量的网络通信操作,如HTTP请求、TCP连接、UDP数据包等。 34 | 35 | 更具体的后端应用下,我们通常会涉及到大量的磁盘操作(如文件读写)或网络通信(如数据库访问、HTTP请求)。 36 | 37 | 这些任务的瓶颈主要是因为I/O操作的速度远远低于CPU的处理速度,导致CPU在等待I/O操作完成时出现空闲,从而影响了应用的整体性能。以下是I/O密集型任务可能遇到的一些主要瓶颈: 38 | 39 | - 磁盘速度:磁盘的读写速度是I/O操作中常见的瓶颈之一。尤其是当使用传统的机械硬盘(HDD)时,其转速和寻道时间限制了数据的读写速度。 40 | - 网络延迟:网络请求涉及到数据在网络中的传输,这通常受到网络带宽、延迟和丢包率的影响。网络延迟会直接影响到应用响应的速度。 41 | - 操作系统的I/O调度:操作系统的I/O调度器对I/O请求的处理效率也会影响I/O密集型任务的性能。不同的调度算法和设置可能会对I/O性能产生显著影响。 42 | - 数据库性能:如果应用程序频繁地与数据库交互,数据库的性能可能成为瓶颈。这可能包括数据库的查询优化、索引、并发连接数、内存及其I/O子系统的性能。 43 | - 同步I/O操作:如果应用程序使用同步I/O,那么每个I/O操作必须在进行下一个计算之前完成,这会导致CPU等待I/O操作的完成,从而造成资源的浪费。 44 | - 资源争用:在多任务环境中,多个进程或线程可能会同时进行I/O操作,导致资源争用,进而影响I/O性能。 45 | 46 | 那我们应该如何优化CPU/IO密集型任务的性能呢? 47 | 48 | ## 为什么多核心 CPU 会提高性能? 49 | 50 | 多核心 CPU 是指在一个 CPU 芯片上集成了多个 CPU 核心,**每个核心都可以独立运行程序**。 51 | 52 | 多核心可以提高计算机的性能,主要有以下几个原因: 53 | 54 | - 并行处理:同时处理多个任务,提高计算机的并行处理能力。 55 | - 负载均衡:将任务分配到不同的核心上执行,从而减少每个核心的负载,提高计算机的负载均衡能力。 56 | - 提高响应速度:同时处理多个任务,提高计算机的响应速度。 57 | 58 | ## 如果应用是 I/O 密集型操作,如何提高响应的速度? 59 | 60 | 假如数据库查询是 I/O 密集型操作,那么使用多进程时可能会遭遇性能瓶颈,因为每个进程在等待 I/O 时不会主动释放 CPU 资源,而是阻塞在 I/O 操作上,导致进程间的资源竞争增加。 61 | 62 | 这个时候多进程并不能提高性能,我们应该选择多线程或者异步方法来处理 I/O 密集型操作。但需注意线程安全以及锁的使用。 63 | 64 | ## 如何对 FastAPI 框架进行性能优化? 65 | 66 | ### 1. 如何在合适的地方使用异步方法? 67 | 68 | 在 FastAPI 中,可以使用异步方法来提高性能。比如在处理请求时,特别是一些 I/O 密集型的操作,可以使用异步方法来处理,这样可以提高并发处理能力。 69 | 70 | 那什么时候可以使用异步方法呢? 71 | 72 | ### 2. 使用多进程部署充分利用多核 CPU 73 | 74 | uvicorn 默认使用单进程处理请求,可以通过设置 `--workers` 参数来启动多个进程,充分利用多核 CPU。 75 | 76 | ```shell 77 | uvicorn main:app --workers 4 78 | ``` 79 | 80 | ## FastAPI 启动1个进程和4个进程有什么区别? 81 | 82 | 在 FastAPI 中,使用 uvicorn 来启动应用时,如果你选择了多进程(--workers 参数),它将启动多个独立的进程来处理请求。 83 | 这对于 I/O 密集型任务来说并不总是最优的,因为每个进程都有自己的内存空间,并且缺乏 asyncio 的异步能力。 84 | 85 | 在数据库查询、接口方法的逻辑计算并没有那么复杂时,比如只是简单的查询数据库并返回结果,设想一下: 86 | 87 | 假如你发现单进程接口的响应速度是 50ms,而使用4个进程却需要100ms。你会奇怪为什么多进程的性能反而下降了? 88 | 89 | 这其实是因为多进程的开销,每个进程都需要占用一定的内存空间,而且进程间的通信也会带来一定的开销。因此,对于一些简单的任务,多进程并不一定能提高性能。 90 | 91 | 反观单进程,对于 FastAPI这种异步web框架来说,它只需要占用一份内存空间,在线程中的任务开销也比进程间的开销要小得多,因此在一些简单的任务中,单进程可能会比多进程更快。 92 | 93 | ## 如何优化数据库性能? 94 | 95 | ### 如何查看正在连接的数据库用户? 96 | 97 | 在 MySQL 中,可以通过以下命令查看当前正在连接的数据库用户: 98 | 99 | ```shell 100 | SHOW FULL PROCESSLIST; 101 | ``` 102 | 103 | ### 如何设置 MySQL 的 wait_timeout? 104 | 105 | `wait_timeout` 是 MySQL 中的一个系统变量,用于设置客户端连接的超时时间,即连接在空闲一段时间后会自动断开。 106 | 107 | 这个变量的默认值是 `28800` 秒(8 小时),也就是说,如果一个客户端连接在 8 小时内没有任何活动,那么 MySQL 会自动断开这个连接。 108 | 109 | 但是有时候我们不希望数据库在闲置 8 小时之后才断开连接,如果我的后台服务并发性较高,那么 8 小时的时间可能会导致数据库连接数过多,从而影响数据库的性能。 110 | 111 | 在 MySQL 中,可以通过以下方式设置 `wait_timeout` 变量: 112 | 113 | ```shell 114 | SHOW SESSION VARIABLES LIKE 'wait_timeout'; 115 | ``` 116 | 117 | 然后,你需要找到启动 MySQL 的配置文件,通常是 `my.cnf` 或 `my.ini`,在这个文件中修改或添加以下配置: 118 | 119 | ```shell 120 | [mysqld] 121 | wait_timeout = 14400 122 | ``` 123 | 124 | 这里的 `14400` 是你希望设置的超时时间,单位是秒。修改完配置文件后,重启 MySQL 服务,新的 `wait_timeout` 设置就会生效。 125 | 126 | 设置 `wait_timeout` 的目的是为了优化数据库的性能和资源利用,避免因为连接数过多而导致数据库性能下降。 127 | 128 | 设置过小的 `wait_timeout` 可能会导致一些问题,比如客户端连接频繁断开、连接池无法正常工作等,因此需要根据实际情况来合理设置这个值。 129 | 130 | ### 什么是数据库索引? 131 | 132 | 数据库索引可以提高查询的效率,减少数据库系统的负载。当我们查询数据库时,数据库系统会首先检查索引,然后根据索引的信息来定位数据,从而减少查询的时间。 133 | 134 | 常见的索引类型有单列索引、多列索引(组合索引)、唯一索引、主键索引等。 135 | 136 | ### 如何创建数据库索引? 137 | 138 | 在创建数据库表时,我们可以为表的某些列创建索引。例如,我们可以为用户表的用户名列创建索引,以提高查询用户信息的效率。 139 | 140 | 例如有一个用户表的表结构如下: 141 | 142 | ```sql 143 | CREATE TABLE users 144 | ( 145 | id INT PRIMARY KEY, 146 | username VARCHAR(255), 147 | email VARCHAR(255), 148 | password VARCHAR(255), 149 | created_at TIMESTAMP 150 | ); 151 | ``` 152 | 153 | 我们假设这个表有100万条记录,我们要查询用户名为`test123`的用户信息,如果没有索引,数据库系统会逐条扫描表中的记录, 154 | 直到找到用户名为`admin`的记录。这样的查询效率非常低。 155 | 156 | #### 创建单列索引 157 | 158 | ```sql 159 | CREATE INDEX idx_username ON users (username); 160 | ``` 161 | 162 | 上面的语句创建了一个名为`idx_username`的索引,该索引是对`users`表的`username`列进行索引,这是一个单列索引。 163 | 164 | #### 创建组合索引 165 | 166 | 如果我们想要查询 username 和 email 列的组合信息,比如查询用户名为`test123`且邮箱为`test123@qq.com`的用户信息,我们可以创建一个组合索引: 167 | 168 | ```sql 169 | CREATE INDEX idx_username_email ON users (username, email); 170 | ``` 171 | 172 | 这是一个多列索引,使用场景是查询 username 和 email 列的组合信息。它和分别创建单列索引的区别是: 173 | 174 | - 单列索引只能用于查询单列信息,而多列索引可以用于查询多列信息 175 | - 多列索引的查询效率高于单列索引,因为多列索引可以减少数据库系统的扫描次数 176 | 177 | #### 创建全文索引 178 | 179 | 还有一种情况是模糊查询,具体的sql执行过程可能是: 180 | 181 | ```text 182 | SELECT * FROM users WHERE username LIKE '%Love%'; 183 | ``` 184 | 185 | (为了演示用了 * 号,生产环境不要使用 * 号) 186 | 187 | 为了提高模糊查询的效率,我们可以为`username`列创建全文索引: 188 | 189 | ```sql 190 | CREATE 191 | FULLTEXT INDEX idx_users ON users (username); 192 | ``` 193 | 194 | 如何使用全文索引: 195 | 196 | ```sql 197 | SELECT * 198 | FROM users 199 | WHERE MATCH (username) AGAINST('Love'); 200 | ``` 201 | 202 | - `MATCH` 关键字用于指定要匹配的列 203 | - `AGAINST` 关键字用于指定要匹配的字符串 204 | 205 | 全文索引可以提高模糊查询的效率,但是需要注意的是,全文索引只能用于 MyISAM 和 InnoDB 引擎,且只能用于 CHAR、VARCHAR、TEXT 类型的列。 206 | 207 | 208 | -------------------------------------------------------------------------------- /docs/how-to-quickly-deploy-database.md: -------------------------------------------------------------------------------- 1 | # 数据库应用 2 | 3 | ## 为什么选择云数据库实例? 4 | 5 | 如果你的预算ok的话,我建议你购买阿里云或者其他运营商的数据库实例应用。 6 | 7 | 选择云数据库服务有许多好处: 8 | 9 | - 可扩展性:云数据库可以根据需求轻松扩展资源,无论是存储空间、CPU、内存还是网络带宽,都可以快速调整。 10 | - 灾难恢复:在云环境中,数据备份和恢复过程通常会自动化,减少了数据丢失的风险,并且在出现问题时可以快速恢复数据。 11 | - 维护和管理:云服务提供商负责数据库的日常维护,包括更新、补丁应用和系统升级,这样用户就可以专注于业务发展而不是IT管理。 12 | - 即时备份和点时间恢复:许多云数据库服务都提供自动备份和点时间恢复功能,这意味着可以恢复到任何特定时间点的数据状态,对于数据保护至关重要。 13 | 14 | ## 自主搭建数据库 15 | 16 | 常用的数据库包括但不限于: MySQL,MongoDB,Redis等等。 17 | 18 | 每种数据库都有其特定的用例和优点: 19 | 20 | - MySQL: 这是一个关系型数据库管理系统(RDBMS),非常适合那些需要复杂查询和事务一致性的应用。它使用结构化查询语言(SQL)进行数据库管理,适用于各种规模的应用,从小型网站到大型企业应用。 21 | - MongoDB: MongoDB是一个面向文档的NoSQL数据库,它存储数据在类似JSON的格式中,这使得它在存储结构化或半结构化数据时非常灵活。MongoDB适合需要快速迭代和处理大量不规则数据结构的应用。 22 | - Redis: Redis是一个开源的键值存储系统,通常被用作数据库、缓存或消息传递系统。它以其高性能而著称,非常适合需要快速读写操作的场景,如会话缓存、实时分析、地理空间数据处理等。 23 | 24 | 除了这些,还有: 25 | 26 | - PostgreSQL: 一种功能强大的开源关系型数据库系统,经常被用于处理复杂的查询、大数据量和高并发。 27 | - Microsoft SQL Server: 一个广泛用于企业环境的关系型数据库管理系统,提供了广泛的数据分析、商业智能和数据挖掘功能。 28 | - SQLite: 一个轻量级的关系型数据库,它的数据库是一个独立的文件,适用于嵌入式系统、移动应用等场景。 29 | 30 | 每种数据库都有其设计和使用的最佳场景,选择哪种数据库通常取决于具体的业务需求、数据模型、并发需求、一致性要求和开发者的熟悉程度等因素。 31 | -------------------------------------------------------------------------------- /docs/how-to-quickly-develop-fastapi-application.md: -------------------------------------------------------------------------------- 1 | # 如何快速开发 `FastAPI` 应用 2 | 3 | * 在线文档:[https://fastapi.tiangolo.com/](https://fastapi.tiangolo.com/) 4 | * 项目地址:[https://github.com/tiangolo/fastapi](https://github.com/tiangolo/fastapi) 5 | 6 | ## `FastAPI` 是不是银弹? 7 | 8 | 首先,`FastAPI` 只是说它能把「功能开发速度提升约 `200%` 至 `300%`」,距离十倍还差了一些,自然不能算是银弹。 9 | 10 | ## 如何使用 FastAPI 连接数据库? 11 | 12 | 在 Python Web 后端开发中,如果是关系型数据库交互的话,可以无脑的选择 SQLAlchemy 作为 ORM(对象关系映射)工具。 13 | 14 | 当然,如果你对 SQL 语句很熟悉,使用 PyMySQL这一类的库也是可以的。db.py 文件代码如下: 15 | 16 | ```python 17 | from typing import Generator 18 | 19 | from sqlalchemy import create_engine 20 | from sqlalchemy.orm import sessionmaker 21 | from sqlalchemy.pool import QueuePool 22 | 23 | from app.config import settings 24 | 25 | engine = create_engine(settings.SQLALCHEMY_DATABASE_URL, poolclass=QueuePool, pool_pre_ping=True, pool_size=20, pool_recycle=3600) 26 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 27 | 28 | 29 | def get_mysql_db() -> Generator: 30 | try: 31 | db = SessionLocal() 32 | yield db 33 | finally: 34 | db.close() 35 | ``` 36 | 37 | - `SQLALCHEMY_DATABASE_URL` 是数据库的连接地址,可以是 SQLite、MySQL、PostgreSQL 等。 38 | - `poolclass=QueuePool` 是设置数据库连接池的类型,`QueuePool` 是一种线程安全的连接池。 39 | - `pool_pre_ping=True` 是设置数据库连接池的预检测,当连接池中的连接在使用前被检测到失效时,会被自动移除。 40 | - `pool_size=20` 是设置数据库连接池的大小,即最大连接数。 41 | - `pool_recycle=3600` 是设置数据库连接池的回收时间,即连接在被放回连接池前的最大生存时间。 42 | - `SessionLocal` 是一个 `sessionmaker` 对象,用于创建数据库会话。 43 | 44 | 利用 get_mysql_db 生成器函数,可用于获取数据库会话。 45 | 46 | > 由于 create_engine 自带了连接池,所以不需要手动关闭数据库连接。 47 | 48 | ## 执行 ORM 对象还是原生 SQL 语句? 49 | 50 | 这个问题没有标准答案,取决于你的业务需求和个人习惯。 51 | 52 | sqlalchemy 推荐使用 ORM 对象,可以直接操作数据表,也可以使用 execute 方法执行原生 SQL 语句。 53 | 54 | ## sqlalchemy 进行 ORM 操作时的 SQL 语句是什么? 55 | 56 | ```python 57 | engine = create_engine(settings.SQLALCHEMY_DATABASE_URL, eoch=True, poolclass=QueuePool, pool_pre_ping=True, pool_size=20, pool_recycle=3600) 58 | ``` 59 | 60 | 使用 `echo=True` 参数,可以在控制台输出 ORM 操作时的 SQL 语句,方便调试。 61 | 62 | 63 | ## 如何按需查询字段? 64 | 65 | ```python 66 | # -*- coding: utf-8 -* 67 | import time 68 | from typing import Any, Optional 69 | 70 | from fastapi import APIRouter, Depends 71 | from sqlalchemy.orm import Session, load_only 72 | 73 | from app import schemas 74 | from app.api.utils.response_code import resp_200, resp_208, resp_400, resp_403 75 | from app.common import deps 76 | from app.db.mysql_db import get_mysql_db 77 | from app.models import Ploy, Campaign 78 | 79 | router = APIRouter() 80 | 81 | 82 | @router.get("/list", response_model=schemas.Response, summary="策略管理") 83 | def get_ploy( 84 | *, 85 | db: Session = Depends(get_mysql_db), 86 | limit: int, 87 | page: int, 88 | title: Optional[str] = None, 89 | nickname: Optional[str] = None, 90 | shopname: Optional[str] = None, 91 | token_check=Depends(deps.get_current_active_user) 92 | ) -> Any: 93 | """ 94 | 策略管理 查询 95 | """ 96 | query = db.query(Ploy).options( 97 | load_only( 98 | Ploy.PloyId, Ploy.Title, Ploy.TjDays, Ploy.Sort, Ploy.Status, 99 | Ploy.Platform, Ploy.Remark, Ploy.ShopName, Ploy.Nickname, Ploy.Nickname2, Ploy.CreateTime, Ploy.EditTime 100 | ) 101 | ) 102 | if title: 103 | query = query.filter(Ploy.Title.ilike(f"%{title}%")) 104 | if nickname: 105 | query = query.filter(Ploy.Nickname.ilike(f"%{nickname}%")) 106 | if shopname: 107 | query = query.filter(Ploy.ShopName == shopname) 108 | 109 | total = query.count() # 获取总数. 挖一个坑,后面再填 110 | 111 | # 根据 CreateTime 排序 112 | items = query.order_by(Ploy.CreateTime.desc()).limit(limit).offset((page - 1) * limit).all() 113 | 114 | results = [] 115 | for item in items: 116 | result_item = { 117 | "PloyId": item.PloyId, 118 | "Title": item.Title, 119 | "TjDays": item.TjDays, 120 | "Sort": item.Sort, 121 | "Status": item.Status, 122 | "Platform": item.Platform, 123 | "Remark": item.Remark, 124 | "ShopName": item.ShopName, 125 | "Nickname": item.Nickname, 126 | "Nickname2": item.Nickname2, 127 | "CreateTime": item.CreateTime.strftime('%Y-%m-%d %H:%M:%S'), 128 | "EditTime": item.EditTime.strftime('%Y-%m-%d %H:%M:%S') 129 | } 130 | results.append(result_item) 131 | 132 | data = {"items": results, 'total': total} 133 | return resp_200(data=data) 134 | ``` 135 | 136 | 通过操作 load_only 属性来加载所需的字段而不是全部字段,这在一定程度上可以提高查询的性能以及减少数据传输。 -------------------------------------------------------------------------------- /docs/images/portainer_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonBackendEngineerGuide/1e3b3c5d1de04e7c2a8b81115b9cec66f747f100/docs/images/portainer_demo.png -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Python后端工程师生存指南 2 | site_description: Python后端工程师生存指南 3 | site_author: 梧桐 4 | copyright: 梧桐 5 | site_url: https://fastapi.tplan.cc 6 | repo_url: https://github.com/py-gzky/fastapi-docs 7 | repo_name: py-gzky/fastapi-docs 8 | remote_name: https://github.com/py-gzky/fastapi-docs 9 | remote_branch: site 10 | 11 | theme: 12 | name: 'material' 13 | features: 14 | - content.code.annotate 15 | - content.tabs.link 16 | - navigation.indexes 17 | - navigation.instant 18 | - navigation.sections 19 | - navigation.tabs 20 | - navigation.tabs.sticky 21 | - navigation.top 22 | - navigation.tracking 23 | - search.highlight 24 | - search.share 25 | - search.suggest 26 | language: 'zh' 27 | palette: 28 | - scheme: default 29 | primary: indigo 30 | accent: indigo 31 | toggle: 32 | icon: material/toggle-switch-off 33 | name: 打开深色模式 34 | - scheme: slate 35 | primary: indigo 36 | accent: indigo 37 | toggle: 38 | icon: material/toggle-switch-outline 39 | name: 关闭深色模式 40 | 41 | extra: 42 | search: 43 | analytics: 44 | provider: google 45 | property: 'UA-111276663-2' 46 | 47 | markdown_extensions: 48 | - admonition 49 | - codehilite 50 | - pymdownx.inlinehilite 51 | - pymdownx.critic 52 | - toc: 53 | permalink: false 54 | - pymdownx.highlight: 55 | anchor_linenums: true #代码块行号锚链接,默认false 56 | use_pygments: true #在构建期间使用Pygments或在浏览器中使用 JavaScript 语法高亮器进行高亮显示,默认true 57 | auto_title: true #自动为所有代码块添加标题,显示正在使用的语言的名称,默认false 58 | linenums: true #向所有代码块添加行号,默认false 59 | linenums_style: pymdownx-inline #三种添加行号的方法,建议table或pymdownx-inline。默认table 60 | - pymdownx.superfences 61 | 62 | extra_javascript: 63 | - https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.2.1/tablesort.min.js 64 | - https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.2.1/sorts/tablesort.number.min.js 65 | 66 | plugins: 67 | - search: 68 | separator: '[\s\u200b\-]' 69 | - git-revision-date-localized: 70 | enable_creation_date: true 71 | 72 | nav: 73 | - 首页: 74 | - 首页: README.md 75 | - 服务器配置: 76 | - 如何搭建一台后端服务器: how-to-configure-server.md 77 | - 如何选择数据库服务: how-to-quickly-deploy-database.md 78 | - Docker部署服务: 79 | - 如何使用 Docker 部署服务: how-to-deploy-using-docker.md 80 | - FastAPI框架: 81 | - 如何快速开发 FastAPI 应用: how-to-quickly-develop-fastapi-application.md 82 | - 如何快速部署 FastAPI 服务: how-to-deploy-fastapi-services.md 83 | - 如何集成 FastAPI 插件: how-to-integrate-plugins.md 84 | - 优化后端性能: 85 | - 如何优化后端服务: how-to-optimize-performance.md 86 | --------------------------------------------------------------------------------