随着业务系统访问量的增长,多机部署成了必然,下面来聊聊flask的分布式部署以及原理。
flask默认的session做了什么
flask作为web应用框架若多机部署,第一个问题是需要一个请求接入网关,通常我们使用nginx统一进行流量的分发。
但随之而来会有一个新的问题,即flask的session多机之间会共享吗?带着这个问题,我们看看flask关于session的源码:1
2
3
4
5
6
7class SecureCookieSession(CallbackDict, SessionMixin):
...
class SecureCookieSessionInterface(SessionInterface):
def open_session(self, app, request):
...
def save_session(self, app, session, response):
...
以上是flask.sessions.py实现的主要框架:
- SecureCookieSession即flask的session类,可以简单的理解成一个dict对象。
- SecureCookieSessionInterface即flask的session接口类,open_session方法用于创建session,save_session方法用于将session加密并存放在response的cookie中。所以flask是默认将用户的session存储在客户端的cookie中,这样请求-应答的数据中就有了用户操作的上下文了,至于这么做的优劣将在下文分析。
常见的分布式部署session解决方案
- 服务器间session复制
session复制是早期的企业级的使用比较多的一种服务器集群session管理机制。应用服务器开启web容器的session复制功能,在集群中的几台 服务器之间同步session对象,使得每台服务器上都保存所有的session信息,这样任何一台宕机都不会导致session的数据丢失,服务器使用session时,直接从本地获取。
像java的一些应用服务器,如tomcat等自带次功能。在python-web不常见,缺点
是session同步会暂用内网网络带宽,且服务器水平扩展存在明显上线。 - session与服务器绑定
通过请求网关,如nginx,将负载均衡的策略改成ip-hash的模式,即用户的每次请求都会分发到同一台服务器,那么sesison则能够正常的被解析。优点
:无需修改业务代码缺点
:缺乏高可用性,当其中一台服务器宕机,该机器上用户需要重新登录到其他服务器 - 客户端session存储
即flask默认的session存储方案,可见什么都不需要改动,flask已经支持水平扩展,细心的童鞋想想当flask通过gunicorn启动时多进程为啥能够共享session,即不难想到多个服务间共享应该也问题不大。这里注意的是多服务器间的secret_key必须相同。优点
:无需改造,flask默认支持缺点
:- session数据存储在客户端,即使加密也还是一件存在泄露风险的事情
- session数据占用外网带宽
- 受cookie的大小限制,session能记录的数据有限
- 服务端session统一存储
对session进行统一的存储,所有服务器共享该存储服务上的数据优点
:服务水平扩展性良好,服务端存储,安全缺点
:- 每次请求至少需要一次内部网络请求,占用网络带宽
- 需要侵入业务代码
flask-session服务端session存储
通过比较不难发现,服务端session统一存储是最合适的解决方案。
那么我们来谈谈怎么实现,幸运的是已经有前任实现了flask对应的扩展包flask-session
,我们一起看看它的实现:
代码大概500+行,但我们实际用到的可能就几十行。
- 首先我们需要选择session寄存的服务,flask-session支持
redis
,memcached
,filesystem
,mongodb
,sqlalchemy
作为存储介质 - 以redis举例,再看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class RedisSessionInterface(SessionInterface):
serializer = pickle
session_class = RedisSession
def __init__(self, redis, key_prefix, use_signer=False, permanent=True):
if redis is None:
from redis import Redis
redis = Redis()
self.redis = redis
self.key_prefix = key_prefix
self.use_signer = use_signer
self.permanent = permanent
def open_session(self, app, request):
...
def save_session(self, app, session, response):
...
重写open_session,save_session
,将session(dict)存储在redis并将session_id(key)返回给客户端
flask http请求-应答完整的数据流
客户端http请求
-> 服务端负载均衡至随机服务器
-> 应用上下文入栈(app_ctx)
-> 请求上下文入栈(request_ctx),同时生成session
-> 通过request_ctx中的路由信息找到视图函数(view_func)
-> view_func进行业务处理
-> 应用上下文出栈(app_ctx)
-> 请求上下文出栈(request_ctx)
-> 保存session或sessino_id进cookie
-> 返回应答
-> 数据写入对应的文件描述符并刷新
其实flask的源码阅读起来并不吃力,看下来会发现flask框架代码的思路结构非常的清晰,并惊叹于这个框架的可扩展性,flask的源码非常值得学习和借鉴。