实习
- 近期在某家公司实习,顺应公司部门文化有一场为期十天左右的实习生小组赛,内容就像黑客马拉松一样。
- 正好在此小组负起后台开发的职责,技术选择是 Python 的 Web框架 Flask, 至于本文想要记录的并不是
flask
这个框架怎么用,好不好用,而是其他一些一直以来比较零散的知识 - 也许这是在学校中开发所注意不到的,那就是实际情况总是与理想状态相去甚远。
架构图
- 这个结构是我为本小组提供的后台拓扑,包含三个部分:
- Nginx反向代理
- Flask逻辑服务器
- MySQL数据库服务器
- 看起来非常朴实,实际上门门道道,细节问题着实费了我两天时间,特别是在逻辑服务器哪里,因为粗心大意,愣是搭了一个晚上还是失败。
- 本次是将终端和前端的接口进行了统一,实际上这样对一个快速开发项目来说的确是必须的,不必考虑太多细节,追求的是效率,毕竟Flask框架追求的的确就是开发效率,至于并发情况实际上在众多框架中,并不能算太好。
开始记录
Nginx反向代理
- 这个的应用应该是非常广泛的,而且十分有效
- 立竿见影的效果就是屏蔽了后端的大部分细节,提高了容错性,以及其本身就是一个超高性能的静态资源服务器,更是能够帮助很多应用提升性能。
- 在Linux下安装 Nginx 就像明面上说的那么简单,但是一般主流的发行版中的 Nginx 的版本都比较低,所以可能有时候用起来不如传闻中的那么神速。
- 于此你可以选择接受它
- 或者去官方网下载最新的稳定版本,按照步骤一步一步的安装。
- 在这里就不再详述,这要是详细记录,又是一个独立的话题了。
- Nginx在Ubuntu下,默认配置文件放在了
/etc/nginx/nginx.conf
以及/etc/nginx/site-available/default
中- 一般情况下,我们修改后者就行,改前记得先备份
- Nginx有自己的URL一套匹配规则,通俗的来说就是将一个完整的链接解析成服务器知道的绝对路径
例如: http://www.domain.com/index.html 解析成服务器上的 /var/www/html/index.html 路径
这个匹配规则,也是可以单独写成一个话题,那么直接看本次配置的时候遇到的一个问题:除了几种特殊情况意外,其他路径都返回
index.html
这个文件,该如何做到?server { listen 8080; server_name xxx.xxx.xxx.xxx; #公网地址 location /api/ { include uwsgi_params; uwsgi_pass 127.0.0.1:7001; # 指向uwsgi 所应用的内部地址,所有请 求将转发给uwsgi 处理 uwsgi_param UWSGI_CHDIR /data/mini/www; # 指向网站根目录 uwsgi_param UWSGI_SCRIPT manage:app; # 指定启动程序 } location ~ ^/(js|css)/{ root /data/mini/source/Mini_Production/build; } location ~ ^/(img|lib)/{ root /data/mini/source/Mini_Production/static; } location / { root /data/mini/source/Mini_Production/; index index.html; try_files $uri /index.html; } }
重点在于最后一个
location
解决了我的问题至于其他的规则,网上详细叙述的太多了,这里只是记录一个特殊情况。
说完这个就该来说说,反向代理的简单配置了
- 说是简单配置,因为将会使用Nginx自带的功能,而不引入第三方模块的功能
- 由于服务的需求,我们的应用需要保存
session
,所以Nginx反向代理的时候必须要保证的一个地方就是:同一个IP必须反向代理到同一个服务器上,否则会导致某些意想不到的错误。 - 而Nginx自带的两个策略之一ip_hash正为此而生。
反向代理的配置分为两个块,
upstream
和server
直接写在
/etc/nginx/nginx.conf
里就行upstream yuehuo{ ip_hash; server xxx.xxx.xxx.xx1:8080; server xxx.xxx.xxx.xx2:8080; ... } server { listen 9090; location / { proxy_pass http://yuehuo; proxy_set_header X-Real-IP $remote_addr; } }
有几台在
upstream
写几个server
,以分号结尾,这样一来就实现了一个最简单的反向代理,对于晓得项目而言其实是已经足够了。不用怀疑什么,Nginx是一个很成熟的开源项目,不像某些开源产品只是一副外壳而已。
- 实际上,着一些就构成了我们小组后台结构的第一部分,也就是理论上暴露给外部的访问点,就是这台独自承载着Nginx反向代理功能的服务器。
- 任何有效访问只需要访问这台服务器,就能得到他们想要的东西,而不需要去记下多台服务器的域名或者IP。
- 而我也可以随时替换后端的服务器,并且服务的请求者什么也不会察觉。
- 甚至还可以配置一个
backup
,在所有服务器都崩溃的时候,它能挽救一下局面。所谓的最初级的容灾。
Flask逻辑服务器
- 在这个部分,实际上是用的是Nginx + Supervisor + uwsgi + Flask 进行的搭建
- 看起来很复杂,很多组建,实际上也就这样:
- Nginx用来引导所有请求至正确的位置:动态请求转发给 Flask,静态资源自己处理就行。
uwsgi
则是Flask
运行的一个平台,不在叙述,可以将两者看成一体,实际上从上面的Nginx配置文件中也能发现一些端倪。- 其实这样已经能够很好完整的服务各方了,那为什么还有一个
Supervisor
呢?就是为了让这个服务更加规范和自动化,这相当于一个自动化可配置的uwsgi
启动器(写过ruby on rails
的应该也很熟悉这个东西)。
- Nginx的配置就如上所示,在
etc/nginx/sites-available/default
里写入上一个模块介绍的内容 - 安装完
Supervisor
和uwsgi
之后,将uwsgi
的配置文件存放好,比如放在flask
程序的结构包里,总之记好路径,在之后的Supervisor
中会用到,之后写好它的配置文件之后,启动它的服务就行。 - 讲的有点泛泛但是却没什么难点,细心一些就能配置对,当然是找到网上的配置教程。
- 从前端上传的图片,被存放在了
Flask
逻辑服务器上,但是这导致一个缺点,就是如何同步多台逻辑服务器之间的图片资源?- 虽然图片是静态资源的一种,但是它具有动态更新的可能性,所以解决方案可以有两种:多台服务器同步或者单独存放在一个独立的图片服务器上。
- 前者的缺点很明显,这是当前技术领域最难的分布式同步问题。后者相对容易而且能带来很不错的性能,可以选择专业的图片存储服务商,或者自己搭建。
- 当前架构还算小规模,暂时使用
scp
定时拷贝的策略来同步。
总体来说,Flask 服务器被配置成 Nginx -> supervisor(uwsgi -> flask) 的模式
数据库分离 之 数据库服务器与读写分离
- 这个部分是耗时比较久的
- 首先问题描述:
- 如果数据库内嵌在
flask Server
中,万一某台Flask Server
宕机,同时也会损失一台数据库服务。 - 多台
flask Server
中内嵌的数据库之间的数据该如何做同步? Flask Server
服务器是否过于臃肿
- 如果数据库内嵌在
- 综上问题,我们相出将数据库从
Flask Server
提出来,以分布式的方式实现 - 首先需要对
Flask Server
做一下数据库系统的透明处理,意思就是让无论多少台Flask Server
都只感觉在操作同一个数据库。- 首先想到数据库中间件
MySQL-Proxy
,做一个读写分离 - 配置主从数据库,以此实现容灾以及性能提升。
- 首先想到数据库中间件
- 首先将数据库放在单独的服务器中,配置成 一主多从 的的模式,即一写多读,其中主服务器可读可写
- 这个可以实现的是,当主数据库被写的时候,可以同步更新到从服务器上。
- 这么做的目的就是将读的性能大大提升。
- 其次对前方屏蔽数据库系统的细节,即增加中间件的存在
- 将之前实现的数据库系统,接在
MySQL-Proxy
的后方,让其帮助我们实现读写分离的功能。
- 将之前实现的数据库系统,接在
也可以实现多主多从的效果,远离和一主多从十分类似,就是将两台 主数据库,互相配置成主从。并在主服务器的MySQL配置的
[mysqld]
选项中增加log_slave_update
标志
- 两篇好教程:
实现上的细节
- 当
slave
出现:Connecting to master
且错误码为2003
的时候,你需要看看你的MySQL
配置中是否有bind-address = 127.0.0.1
这个选项,注释掉它 - 配置完以后,你会发现状态变了,这样才算成功。
- 当
万年不变的
MySQL
中文问题- 在所有方法都查证之后,四方编码都为 UTF-8时,却发现还是不行
- 这时候,可以尝试将整个数据库
Drop
,之后重建。这样会使刚才的配置生效在这个数据库上 - 其实数据库可以不必
DROP
,只需要将表删了重建即可。
反思
- 一个最大的问题就是,如果数据已经多到一个
DB
存不下,不得不用多数据库来存储的时候,该怎么办? - 这个是我们这个架构最致命的缺点。
- 如果有更好的解决方案,我再记录