前言
Flask 是 Python 开发的轻量 Web 框架,有多轻量呢?10 行以内就可以开发一个 Web 服务,不过这只能用来做演示,今天我就用 1 个小时来开发一个用于生产环境的短信微服务。以下是生产环境直接可用的服务代码,绝非示例教程。
为什么要开发短信微服务?
短信服务我们都是依赖公有云的实现,通过公有云的 API 直接调用,那为什么还要自己封装呢?
- 因为微服务环境下我们要减少代码的重复量,如果有多个微服务需要使用短信服务,那就要复制多遍代码,把公有云的 API 包装成我们自己的微服务 API 可以将代码的复制减少为一行 Http 请求。
- 调用 API 的 accesskey 和 secret 不需要复制给多个服务,减少安全风险。
- 可以根据我们的业务需求加入共用的业务逻辑。
多了一层调用有没有性能影响?
多了一层调用是多了一个网络请求,但是影响微乎其微。我们不可能因为面向对象的方式太多调用就写逐行执行的代码吧。
- 公有云短信服务本就是异步调用,错误处理也是异步回调的方式。
- 微服务内部网络的调用应该是非常快的,可以同虚拟机部署或者同机房部署。
开始
首先我们建立项目的骨架。
为什么要建立项目的骨架呢?
因为 Flask 太过于轻量,所以例如配置、路由等规范需要由开发人员自己定义。一般成熟的开发团队都有自己的一套开发骨架,要统一配置,统一开发规范,统一集成相关系统等。我这里就分享一套适用于生产环境的非常简单的开发骨架。
新建一个项目目录,然后在里面建立 app 和 config 两个 Python 目录。app 用于存放业务相关代码,config 用于存放配置相关代码。
配置类
在 config/config.py 中添加如下内容,配置的设计因人而异,Flask 也没有做任何限制。我这里的设计是使用 BaseConfig 作为配置基类,存放所有共用的配置,而不同的环境使用不同的配置子类,子类只需要修改特定的值就可以,便于查看。
如果配置的值需要在运行是注入(如数据库连接等),则可以使用环境变量的方式(如下面的 SECRET_KEY),我同时使用 or 提供了没有环境变量的默认值。
import os
class BaseConfig:
"""
配置基类,用于存放共用的配置
"""
SECRET_KEY = os.environ.get('SECRET_KEY') or os.urandom(16)
DEBUG = False
TESTING = False
class ProductionConfig(BaseConfig):
"""
生产环境配置类,用于存放生产环境的配置
"""
pass
class DevelopmentConfig(BaseConfig):
"""
开发环境配置类,用于存放开发环境的配置
"""
DEBUG = True
class TestingConfig(BaseConfig):
"""
测试环境配置类,用于存放开发环境的配置
"""
DEBUG = True
TESTING = True
registered_app = [
'app'
]
config_map = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig
}
至于后面的 registered_app 和 config_map 有什么用?可以做自动注入,这个我后面会讲。
然后我加一个日志的配置,日志的配置非常重要,不同的开发团队往往有一套规范的日志配置模版,一般不会改变,所以可以直接定义在代码里,也可以用配置文件的方式。
config/logger.py
from logging.config import dictConfig
def config_logger(enable_console_handler=True, enable_file_handler=True, log_file='app.log', log_level='ERROR',
log_file_max_bytes=5000000, log_file_max_count=5):
# 定义输出到控制台的日志处理器
console_handler = {
'class': 'logging.StreamHandler',
'formatter': 'default',
'level': log_level,
'stream': 'ext://flask.logging.wsgi_errors_stream'
}
# 定义输出到文件的日志处理器
file_handler = {
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'detail',
'filename': log_file,
'level': log_level,
'maxBytes': log_file_max_bytes,
'backupCount': log_file_max_count
}
# 定义日志输出格式
default_formatter = {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
}
detail_formatter = {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
}
handlers = []
if enable_console_handler:
handlers.append('console')
if enable_file_handler:
handlers.append('file')
d = {
'version': 1,
'formatters': {
'default': default_formatter,
'detail': detail_formatter
},
'handlers': {
'console': console_handler,
'file': file_handler
},
'root': {
'level': log_level,
'handlers': handlers
}
}
dictConfig(d)
上面就是一个典型的 Python 日志配置方法,把可变的部分定义为参数(日志文件、级别等),定义了两个日志处理器(文件和控制台),使用时只需要调用这个方法即可。
应用类
定义好配置,我们就开始创建我们的 Flask 应用了。用过 Flask 的同学知道,创建 Flask 应用只需要一行代码。
app = Flask(__name__)
但这不是生产可用的方式,为了生产和测试方便&