解决使用Flask-SQLAlchemy中出现的1366报错

最近在按照这本书学Flask,学到通过ORM方式操作数据库时遇到一个很奇怪的问题:

会报下面这个1366的错

...default.py:470: Warning: (1366, "Incorrect string value: 'xD6xD0xB9xFAxB1xEA...' for column 'VARIABLE_VALUE' at row 479")

未分类

奇怪的地方在于我表格里的数据全部都是英语

因为看到UTF编码,首先就想到是不是编码的问题,于是

  • 检查了自己的MySQL的配置
    没发现配置有问题,都是UTF-8编码

  • 网上搜了下相关资料
    发现除了MySQL中的编码配置之外,Python的编码也要是UTF-8,检查没发现问题 (如下图)
    另外就是在字符串前加上u,变成u’string’的形式,当然这招也没用

未分类

到此我比较郁闷,遂求助于程序员好友,他看/听完描述,马上就找到了最可疑之处 – MySQL驱动

的确,书中在创建数据库连接时,并没提到相关概念,但我之前再根据廖雪峰网站学MySQL操作时,是有这个步骤的

然后根据这个思路进行操作

  • 安装MySQL驱动(我升级过Python,所以要再装一遍)
    本想安装MySQL官方驱动mysql-connector-python的,然而官方目前只支持到3.4
    我又懒,所以就用了另一个驱动mysql-connector,也不知道是谁开发的……

  • 修改代码,把

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:password@localhost/database'

改成

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://root:password@localhost/database'

至此,上述1366报错信息消失!

我推测是因为SQLAlchemy使用了默认的数据库驱动(按官方文档,是mysql-python)有问题,才导致此问题。

还望看到此文章的大神能验证一下我的说法。

Flask服务部署(Nginx Gunicorn Gevent)

Flask 项目部署

我们开发好了一个flask项目, 需要部署到线上服务器, 那我们的线上服务应该如何部署呢

基本的架构如下

未分类

Nginx

在开发环境, 我们一般直接运行Python服务, 启动了某个端口(一般是5000端口), 然后通过该端口进行开发调试

但线上环境一般不会直接这样提供服务, 一般的线上服务需要通过 Nginx 将外部请求转发到Python服务

这样有什么好处

  • 隐藏python服务, 避免直接将python服务暴露出去
  • 提高web服务的连接处理能力(Nginx)
  • 作为反向代理, 提升python整体服务处理能力

我们可以配置的Nginx配置如下

upstream flask_servers {
    server 127.0.0.1:9889;
}

server {
    listen 80;
    server_name dev.simple-cms.com;

    access_log  /data/logs/nginx/simple_cms_access.log main;
    error_log /data/logs/nginx/simple_cms_error.log debug;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
        proxy_pass  http://flask_servers;
    }

    location ^~ /static {
        root /data/www;
    }
}

如果有多个python服务器, 可在upstream下继续添加其他的python服务列表

Gunicorn + Gevent

Gunicorn

Guicorn 是一个python的一个 WSGI 的web sever, 什么是WSGI

Web Server <=====WSGI=====> Python APP

WSGI 其实是一种网关协议, 用以把http请求参数转换为python服务可以读取的内容, 这样在python的框架里能根据请求参数进行不同的业务处理了

Gunicorn 就是这么一种WSGI的实现, 他也是个web server, 可以直接提供对外的web服务, 但在一般的部署中, 他只是作为python服务的运行的容器, 运行和管理python应用程序

通过这个容器, 从Nginx转发的请求就能转发到某个python app了

除此之外, Gunicorn 还提供了多个运行参数, 常用的如下

-w 设置启动`python app` worker进程的数量
-k 运行模式(sync, gevent等等)
-b gunicorn 启动绑定的host和port
--max-requests 最大处理量, 单woker进程如果处理了超过该数量的请求, 该woker会平滑重启

Gevent

单进程直接运行Python app服务时, 当有两个并发请求过来时, 进程只能先处理一个请求, 等第一个请求处理完成后, 才能处理第二个, 势必影响用户的体验

那么单进程的时候, 如何能提高并发处理能力,

大多数情况下, 我们的服务中, 导致性能低下的原因是I/O, 比如对数据库的读写, 发送Http请求等等, 当进程进行I/O的时候, 是不占用CPU时间的, 这个时候, CPU可以被腾出来处理其他请求

Gevent就是完成这个工作的

幸运的是, 我们不需要再代码中自己实现这部分功能, Gunicorn 实现了Gevent模式的运行方式(-k 参数指定), 允许你的Python APP 更高性能的处理业务

通过 Gunicorn + Gevent, 我们可以如下启动

gunicorn --max-requests 300 --pid /tmp/flask.pid --timeout 3 -k gevent -w 2 -b 127.0.0.1:9889 run:app

从Nginx代理的请求, 就可以转发到Gunicorn启动的9889端口上了

supervisor

上面我们通过Gunicorn+Gevent 启动了Python服务, 在某些情况下, 我们的服务会停掉(系统掉电重启, 误杀, 不知道停了 🙁 ),

我们不可能时刻都看着线上服务, 在这种情况下, 我们希望系统能自动拉起停掉的服务, 保证线上服务的稳定

supervisor 就是干这个的, supervisor会守护配置好的进程, 在进程停止后重新启动服务进程, 并保留日志

比如Nginx服务的配置可以如下

[program:nginx]
command = /usr/local/bin/nginx -g 'daemon off;' -c /usr/local/etc/nginx/nginx.conf
autostart = true
startsecs = 5
autorestart = true
startretries = 3
user = root

其中command 就是我们nginx服务的启动命令,

同理, gunicorn启动的supervisor 配置如下

[program:gunicorn]
directory=/path/to/flask_app
command = /path/to/gunicorn --max-requests 3000 --pid /tmp/flask.pid --timeout 3 -k gevent -w 2 -b 127.0.0.1:9889 run:app autostart = true
startsecs = 5
autorestart = true
startretries = 3
user = www-data

以上配置好好, 执行

sudo supervisorctl -c /path/to/supervisord.ini update

就可以让supervisor 守护我们的gunicorn 服务了

状态查看

sudo supervisorctl -c /path/to/supervisord.ini status

可以看到

gunicorn             RUNNING   pid 35640, uptime 0:00:14

状态是running

总结

以上, 我们实现了线上python服务的基础架构部署, 主要是针对python服务的部署, 基本遵循了文首的架构图

以上是我们在项目中实践的架构部署, 欢迎各位一起交流

Ubuntu系统使用Nginx uWSGI部署Flask应用

我职业生涯的大部分都在使用微软的架构,最近我决定走出技术的舒适区,步入开源软件世界。我现在日常工作的项目是一个RESTful服务,这个服务需要在主流硬件上运行,且能够按照需要进行水平拓展。为完成这项工作我决定使用Flask和Nginx。Flask是一个轻量级的Python Web框架,Nginx是一个非常稳定的Web服务器,它们在廉价硬件平台上工作良好。

在这篇文章中我将指导你完成使用Nginx服务器托管Flask应用的安装、配置过程。我所使用的操作系统是Ubuntu 13.04。

前提条件

在我们开始安装Nginx及其他所需软件之前先安装一些前提软件。首先,我们需要PIP与virtualenv:

sudo apt-get install python-setuptools
sudo easy_install pip
sudo pip install virtualenv

使用apt-get安装Nginx的话,我们需要添加Nginx库到apt-get source中:

sudo add-apt-repository ppa:nginx/stable

注意:如果“add-apt-repository”命令在你的Ubuntu版本中不存在的话,你需要安装“software-properties-common”包,使用命令:sudo apt-get software-properties-common(感谢get_with_it在评论中提到)

升级已有的包,确保系统上有uWSGI所需的编译器和工具:

sudo apt-get update && sudo apt-get upgrade
sudo apt-get install build-essential python python-dev

Nginx

安装并运行Nginx:

sudo apt-get install nginx
sudo /etc/init.d/nginx start

Nginx是一个提供静态文件访问的web服务,然而,它不能直接执行托管Python应用程序,而uWSGI解决了这个问题。让我们先安装uWSGI,稍候再配置Nginx和uWSGI之间的交互。

sudo pip install uwsgi

里程碑 #1

打开浏览器访问你的服务器,你应该能看到Nginx欢迎页:

示例应用

我们将托管的应用是经典的“Hello, world!”。这个应用只有一个页面,已经猜到页面上将有什么内容了吧。将所有应用相关的文件存放在/var/www/demoapp文件夹中。下面创建这个文件夹并在其中初始化一个虚拟环境:

sudo mkdir /var/www
sudo mkdir /var/www/demoapp

由于我们使用root权限创建了这个文件夹,它目前归root用户所有,让我们更改它的所有权给你登录的用户(我的例子中是ubuntu)

sudo chown -R ubuntu:ubuntu /var/www/demoapp/

创建并激活一个虚拟环境,在其中安装Flask:

cd /var/www/demoapp
virtualenv venv
. venv/bin/activate
pip install flask

使用下面的代码创建hello.py文件:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

里程碑 #2

让我们执行我们刚创建的脚本:

python hello.py

现在你可以通过浏览器访问你服务器的8080端口,看,应用生效了:

注意:因为80端口已被Nginx使用,这里我使用8080端口。

现在应用是由Flask内置的web服务托管的,对于开发和调试这确实是个不错的工具,但不推荐在生产环境中使用。让我们配置Nginx来挑起这个重担吧。

配置Nginx

首先删除掉Nginx的默认配置文件:

sudo rm /etc/nginx/sites-enabled/default

注意:如果你安装了其他版本的Nginx,默认配置文件可能在/etc/nginx/conf.d文件夹下。

创建一个我们应用使用的新配置文件/var/www/demoapp/demoapp_nginx.conf:

server {
    listen      80;
    server_name localhost;
    charset     utf-8;
    client_max_body_size 75M;

    location / { try_files $uri @yourapplication; }
    location @yourapplication {
        include uwsgi_params;
        uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;
    }
}

将刚建立的配置文件使用符号链接到Nginx配置文件文件夹中,重启Nginx:

sudo ln -s /var/www/demoapp/demoapp_nginx.conf /etc/nginx/conf.d/
sudo /etc/init.d/nginx restart

里程碑 #3

访问服务器的公共ip地址,你会看到一个错误:

别担心,这个错误是正常的,它代表Nginx已经使用了我们新创建的配置文件,但在链接到我们的Python应用网关uWSGI时遇到了问题。到uWSGI的链接在Nginx配置文件的第10行定义:

uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;

这代表Nginx和uWSGI之间的链接是通过一个socket文件,这个文件位于/var/www/demoapp/demoapp_uwsgi.sock。因为我们还没有配置uWSGI,所以这个文件还不存在,因此Nginx返回“bad gateway”错误,让我们马上修正它吧。

配置uWSGI

创建一个新的uWSGI配置文件/var/www/demoapp/demoapp_uwsgi.ini:

[uwsgi]
#application's base folder
base = /var/www/demoapp

#python module to import
app = hello
module = %(app)

home = %(base)/venv
pythonpath = %(base)

#socket file's location
socket = /var/www/demoapp/%n.sock

#permissions for the socket file
chmod-socket    = 666

#the variable that holds a flask application inside the module imported at line #6
callable = app

#location of log files
logto = /var/log/uwsgi/%n.log

创建一个新文件夹存放uWSGI日志,更改文件夹的所有权:

sudo mkdir -p /var/log/uwsgi
sudo chown -R ubuntu:ubuntu /var/log/uwsgi

里程碑 #4

执行uWSGI,用新创建的配置文件作为参数:

uwsgi --ini /var/www/demoapp/demoapp_uwsgi.ini

接下来访问你的服务器,现在Nginx可以连接到uWSGI进程了:

我们现在基本完成了,唯一剩下的事情是配置uWSGI在后台运行,这是uWSGI Emperor的职责。

uWSGI Emperor

uWSGI Emperor (很拉风的名字,是不?) 负责读取配置文件并且生成uWSGI进程来执行它们。创建一个初始配置来运行emperor – /etc/init/uwsgi.conf:

description "uWSGI"
start on runlevel [2345]
stop on runlevel [06]
respawn

env UWSGI=/usr/local/bin/uwsgi
env LOGTO=/var/log/uwsgi/emperor.log

exec $UWSGI --master --emperor /etc/uwsgi/vassals --die-on-term --uid www-data --gid www-data --logto $LOGTO

最后一行运行uWSGI守护进程并让它到/etc/uwsgi/vassals文件夹查找配置文件。创建这个文件夹,在其中建立一个到链到我们刚创建配置文件的符号链接。

sudo mkdir /etc/uwsgi && sudo mkdir /etc/uwsgi/vassals
sudo ln -s /var/www/demoapp/demoapp_uwsgi.ini /etc/uwsgi/vassals

同时,最后一行说明用来运行守护进程的用户是www-data。为简单起见,将这个用户设置成应用和日志文件夹的所有者。

sudo chown -R www-data:www-data /var/www/demoapp/
sudo chown -R www-data:www-data /var/log/uwsgi/

注意:我们先前安装的Nginx版本使用“www-data”这个用户来运行Nginx,其他Nginx版本的可能使用“Nginx”这个替代用户。

由于Nginx和uWSGI都由同一个用户运行,我们可以在uWSGI配置中添加一个安全提升项。打开uWSGI配置文件,将chmod-socket值由666更改为644:

...
#permissions for the socket file
chmod-socket    = 644

现在我们可以运行uWSGI了:

sudo start uwsgi

最后,Nginx和uWSGI被配置成启动后立即对外提供我们的应用服务。

问题解决

如果出现错误的话,第一个检查的地方是日志文件。Nginx默认将错误信息写到/var/log/nginx/errors.log文件。

我们已经配置了uWSGI emperor将日志写到/var/log/uwsgi/emperor.log。这个文件夹还包含着每个配置应用的单独日志。我们的例子是 – /var/log/uwsgi/demoapp_uwsgi.log。

静态文件

如果你的应用提供静态文件的话,将下面的规则添加到demoapp_nginx.conf文件:

location /static {
    root /var/www/demoapp/;
}

上面配置的结果就是所有在/var/www/demoapp/static文件夹中的文件将由提供Nginx对外服务(谢谢Bastianh指出)

托管多个应用

如果你想在一台服务器上托管多个Flask应用,为每个应用创建一个单独的文件夹,像我们前面所做的一样,创建Nginx及uWSGI配置文件到应用文件夹的符号链接。

使用Distribute部署应用

使用distribute部署Flask应用的话,首先,按照Flask文档里的步骤将应用转化成package,然后复制distribute通用安装包到服务器上,使用虚拟环境中的Python来安装它。如下:

python setup.py install

最后且同样重要的是,uwsgi配置里应用属性的值要设置成包含Flask应用的包的名称。