Tornado +jinja2+pymysql 与nginx部署+supervisor监控

1 篇文章 0 订阅
1 篇文章 0 订阅
前言

Django本身是同步框架,采用wsgi协议与web服务器进行交互,而web服务器都是基于多线程去处理多个请求,这种情况下如果要处理高并发就很难应对。

Tornado为了解决高并发的性能问题,使用异步非阻塞的处理方式。其原理是tornado的核心io循环模块,底层封装了Linux的I/O复用模型epoll。

概述

tornado三大组件

  • tornado.web

RequestHandler-封装了处理请求的所有方法,get,post等方法监听同名方法的请求,write方法为响应请求内容。

Application-作为服务器的入口组件,初始化参数中配置请求路由地址。

  • tornado.ioloop

current方法返回当前IOLoop实例,start方法打开服务器监听。

  • tornado.httpserver

HTTPServer方法可以传入Application实例,该方法为tornadohttp服务器的实现。

Demo项目

tornado 4.2+jinja2+pymysql/peewee/sqlachemy

部署环境 centos 6.2+nginx

简述:

该项目采用jinja2作为模板引擎(django模板使用方式非常接近),数据库交互方式采用pymysql非orm框架,当然也可以选用peewee,sqlachemy轻量级的orm框架,如果考虑到性能,pymysql作为首选。

一、项目目录结构

handlers目录为处理请求的主目录


1.db

config.py连接数据库配置

# -*- coding:utf-8 -*-
from utils.aesimp import decrypt_aes

# Mysql config
mysql_db_config = {
    'db':'tornado',
    'host':'localhost',
    'port':3306,
    'user':'root',
    # 数据库密码采用非明文的方式
    'password': decrypt_aes('SPivfEfVD9cr9/g9nxY3rw=='),
    'charset': 'utf8'
}

connections.py管理数据库连接,这里采用单连接的方式,如果需要可以拓展成连接池的方式

# -*- coding:utf-8 -*-
import pymysql
from contextlib import contextmanager
from config import mysql_db_config


# 该上下文装饰器用于自动管理数据连接的创建和关闭,不需每次手动去执行
@contextmanager
def connect(cursor_type=pymysql.cursors.DictCursor):
    conn = pymysql.connect(**mysql_db_config)
    cursor = conn.cursor(cursor=cursor_type)
    try:
        yield cursor
    finally:
        conn.commit()
        conn.close()
        cursor.close()

2.handlers

indexhandler.py 处理首页请求,没有业务逻辑,渲染一个页面给前端

# -*- coding:utf-8 -*-
from tornado.web import RequestHandler
import uuid


class IndexHandler(RequestHandler):

    def get(self, *args, **kwargs):
        page_dict = dict()
        page_dict['order_forms'] = [
            {'name':'order_id','value':str(uuid.uuid4()).replace("-", "")[:24],'lable':u'订单号'},
            {'name':'order_amt','value':'','lable':u'订单金额'},
            {'name':'order_curr','value':'156','lable':u'订单币种'},
        ]
        self.render("index.html",**page_dict)

其他业务处理通用,可以自己根据业务需求进行设计

3.static用于存放css,js,image等静态文件

4.template用于存放html模板

ps:jinja2模板与django类似,支持模板的继承和子模块的重写,为了提高复用,这里拆分出来几个模板。

{% block name %}{% endblock %}name不能使用'-'符号骂人,这是一个坑

  • base.html-模板的基本骨架

<!DOCTYPE html>
<html>
<head>

    <meta charset="utf-8">
    <meta name="test" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title>{% block title %}{% endblock %}</title>
    <!-- 引入通用样式-->
    <!-- static_url动态转义成静态文件static目录地址-->
    <link href="{{ static_url('css/bootstrap.min.css') }}" rel="stylesheet">
    <link href="{{ static_url('font-awesome/css/font-awesome.css') }}" rel="stylesheet">
    <link href="{{ static_url('css/style.css') }}" rel="stylesheet">
    <!--子模板中可以引入额外的样式-->
    {% block extrastyle %}{% endblock %}

</head>

<body>
    <div id="wrapper">
        <!--左侧导航栏,这里直接引入了模板,可以继承或者重载-->
        {% block leftsidebar %}
            {% include 'left-nav.html' %}
        {% endblock %}
        <div id="page-wrapper" class="gray-bg dashbard-1">
            {% block breadcrumb %}
                {% include 'breadcrumb.html' %}
            {% endblock %}
            <!--显示内容的区域-->
            {% block content %}{% endblock %}
        </div>
    </div>
    <!-- Mainly scripts -->
    <!-- js放置页面底部引入可以加速页面渲染 -->
    <script src="/static/js/jquery-2.1.1.js"></script>
    <script src="/static/js/bootstrap.min.js"></script>
    {% block extrascript %}{% endblock %}
</body>
</html>
  • index.html
{# 支持继承通用模板 #}
{% extends 'base.html' %}
{% block content %}
    {# macro为自定义函数,支持传递参数 #}
    {% macro input(name,value,label) %}
        <div class="col-sm-4">
            <div class="form-group">
                <label class="control-label" for="{{ label | replace(' ','_') | lower }}">{{ label }}</label>
                <input type="text" id="{{ name }}" name="{{ name }}" value="{{ value }}" class="form-control"  >
            </div>
        </div>
    {% endmacro %}
    <div class="wrapper wrapper-content animated fadeInRight ecommerce">
        <div class="ibox-content m-b-sm border-bottom">
            <div class="row">
                {# 该标签支持server端响应内容 #}
                {% for feild in order_forms %}
                    {{ input(feild.name,feild.value,feild.lable) }}
                {% endfor %}
            </div>
            <div class="row">
                <div class="ibox-content">
                    <button id="submit" class="btn btn-primary pull-right">
                        <i class="fa fa-shopping-cart"></i>
                        提交订单
                    </button>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

5.application.py

控制handlers的一个集合容器,初始化时还可以以dict形式配置httpserver的参数

# -*- coding:utf-8 -*-
import tornado.web
import os
from urls import *
from utils.jinjaloader import JinjaLoader

settings = {
    "cookie_secret": "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
    "xsrf_cookies": True,
    # 这里为引入jinja2模板引擎的关键配置
    "template_loader": JinjaLoader(os.path.join(os.path.dirname(__file__), 'templates/')),
    "template_path": os.path.join(os.path.dirname(__file__), "templates"),
    "static_path": os.path.join(os.path.dirname(__file__), "static"),
    "debug": True,
    "autoreload": True  # debug为True模式下,允许自动重载
}

application = tornado.web.Application(handlers=urls, **settings)

6.urls.py


映射路由地址与handler

# -*- coding:utf-8 -*-
from handlers import *
from tornado.web import url, RedirectHandler

urls = [
    # 支持使用url进行配置别名name,Application.reverse_url可以转义出实际路由地址
    url(r"/test/order/", OrderHandler, name='order'),
    url(r"^/$", IndexHandler, name='index'),
    # 支持路由地址参数化,这里使用了tornado自带重定向handler,需要传递url这个参数
    (r"/redirect/P?<url>.*", RedirectHandler, dict(url='/test/order/')),
]

7.server.py


项目启动程序,ioloop启动服务监听

# -*- coding:utf-8 -*-
import tornado.ioloop
import tornado.options
import tornado.httpserver
from application import application
from tornado.options import define,options

define("port",default=8123,help="run on th given port",type=int)


def main():

    tornado.options.parse_command_line()
    http_server = tornado.httpserver.HTTPServer(application)
    # http_server.listen(options.port)
    http_server.bind(options.port)
    # start()传入参数可以启动子进程数目,如果<=0,启动数量和cpu核数
    http_server.start(0)
    print('Development server is running at http://127.0.0.1:%s/' % options.port)
    print('Quit the server with Control-C')
    tornado.ioloop.IOLoop.current().start()

if __name__ == "__main__":
    main()

二、tornado引入Jinja2模板

方法一、重写basehandler的render方法,目前大多数都是采用这种方式,但该方法侵入性太强。

# -*- coding:utf-8 -*-
import tornado.web
from jinja2 import Environment, FileSystemLoader, TemplateNotFound

class TemplateRender(object):
    """
    A simple class to hold methods for rendering templates.
    """
    def render_template(self, template_name, **kwargs):
        template_dirs = []
        if self.settings.get('template_path', ''):
            template_dirs.append(self.settings['template_path'])
        env = Environment(loader=FileSystemLoader(template_dirs))
        try:
            template = env.get_template(template_name)
        except TemplateNotFound:
            raise TemplateNotFound(template_name)
        content = template.render(kwargs)
        return content


class BaseHandler(tornado.web.RequestHandler, TemplateRender):
    """
    Tornado RequestHandler subclass.
    """
    def initialize(self):
        pass

    def get_current_user(self):
        user = self.get_secure_cookie('user')
        return user if user else None

    def render_html(self, template_name, **kwargs):
        kwargs.update({
          'settings': self.settings,
          'static_path': self.settings.get('static_url_prefix', '/static/'),
          'request': self.request,
          'current_user': self.current_user,
          'xsrf_token': self.xsrf_token,
          'xsrf_form_html': self.xsrf_form_html,
        })
        content = self.render_template(template_name, **kwargs)
        self.write(content)

方法二、在tornado的application中配置模板引擎,前面已经引入,下面为jinjia2的模板加载器

# -*- coding:utf-8 -*-
import threading
from tornado import template, web
import jinja2


class TTemplate(object):
    def __init__(self, template_instance):
        self.template_instance = template_instance

    def generate(self, **kwargs):
        return self.template_instance.render(**kwargs)


class JinjaLoader(template.BaseLoader):

    def __init__(self, root_directory, **kwargs):
        super(JinjaLoader,self).__init__(root_directory, **kwargs)
        self.jinja_env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(root_directory), extensions=['jinja2.ext.i18n'], **kwargs
        )
        self.templates = {}
        self.lock = threading.RLock()

    def resolve_path(self, name, parent_path=None):
        return name

    def _create_template(self, name):
        template_instance = TTemplate(self.jinja_env.get_template(name))
        return template_instance

三、tornado.ioloop原理图


四、nginx部署+supervisor监控

简述:nginx作为高效的负载均衡服务器,作为反向代理可以转发到不同的tornado服务器中处理。

Supervisor是基于Python实现的监控进程的工具。

nginx配置

upstream tornados{
    server 127.0.0.1:8001; # 分发到不同端口的tornado服务器中
    server 127.0.0.1:8002;
    server 127.0.0.1:8003;
}
proxy_next_upstream error;
server {
    listen 80; #还可以支持ssl方式
    server_name www.tornado.com;

    # 静态文件直接由Nginx处理
    location /static/{
        alias /project_path/static/;
        expires 24h;
    }
    location /{
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        # 把请求方向代理传给tornado服务器,负载均衡
        proxy_pass http://tornados;
    }
}

Supervisor配置

# 为了方便管理,增加一个tornado组
[group:tornados]
programs=tornado-0,tornado-1,tornado-2

# 分别定义三个tornado的进程配置
[program:tornado-0]
# 进程要执行的命令
command=python /project_path/server.py --port=8020
directory=/project_path/
user=tornado
# 自动重启
autorestart=true
redirect_stderr=true
# 日志路径
stdout_logfile=/home/user/tornado0.log
loglevel=info

[program:tornado-1]
command=python /project_path/server.py --port=8021
directory=/project_path/
user=tornado
autorestart=true
redirect_stderr=true
stdout_logfile=/home/user/tornado1.log
loglevel=info

[program:tornado-2]
command=python /project_path/server.py --port=8022
directory=/project_path/
user=tornado
autorestart=true
redirect_stderr=true
stdout_logfile=/home/user/tornado2.log
loglevel=info

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值