一、web服务器对请求响应的步骤
(1)服务器对客户端发来的请求(request)进行解析。
(2)请求被转发给一个预定义的处理器(handler)。
(3)处理器可能会从数据库中取出数据。
(4)处理器根据取出的数据对模板(template)进行渲染(render)。
(5)处理器向客户端返回渲染后的内容作为请求的响应(response)。
二、通过将数据库的一部分数据处理任务以及存储任务转交给Redis来完成,可以提升网页的载入速度,并降低资源的利用率。
三、使用Redis管理用户登录会话(session)
登录互联网服务时,服务会使用cookie记录我们的身份,cookie由少量数据组成,网站会要求我们的游览器存储这些数据,并在每次服务发送请求时将这些数据传回给服务。对于登录的cookie,有两种方法将登录信息存储到cookie中:一种是签名(signed)cookie,另一种是令牌(token)cookie。某大型网上商店一天的负载量比较大,平均每秒大约1200次写入,高峰期每秒近6000次写入,所以它必须部署10台关系数据库服务器才能应对高峰时期的负载量。而我们要做的是使用Redis重新实现登录cookie功能,取代目前由关系数据库实现的登录cookie功能。
首先用一个散列存储登录cookie令牌与已登录用户之间的映射,可根据给定的令牌查找与之对应的用户,检查登录cookie的代码如下。
def check_token(conn,token):
return conn.hget('login:',token)//尝试获取并返回令牌对应的用户
程序更新令牌的方法打的代码如下。
def update_token(conn,token,user,item=None):
timestamp=time.time() //获取当前时间
conn.hset('login:',token,user)//维持令牌与已登录用户的映射
conn.zadd('recent:',token,timestamp)//记录令牌最后一次出现的时间
if item:
conn.zadd('viewed:'+token,item,timestamp)//记录用户游览过的商品
conn.zremrangebyrank('viewed:'+token,0,-26)//移除旧的记录,只保留用户最近游览过的25个商品
通过update_token()函数,记录用户最后一次游览商品的时间以及用户最近游览了哪些商品。比起原来的关系数据库,性能提升了10~100倍。
存储会话数据所需内存随时间推移不断增加,故我们需要定期清理旧的会话数据。清理旧会话程序的具体代码如下。
QUIT=FALSE
LIMIT=10000000
def clean_sessions(conn):
while not QUIT:
size=conn.zcard('recent')//找出目前已有令牌数量
if size<=LIMIT:
time.sleep(1)//令牌数量未超过限制,休眠并在之后重新检查
continue
end_index=min(size-LIMIT,100)
tokens=conn.zrange('recent:',0,end_index-1)//获取需要移除的令牌ID
sessions_keys=[] //为那些将要被删除的令牌构建键名
for token in tokens:
session_keys.append('viewed:'+token)
conn.delete(*session_keys) //移除最旧的那些令牌
conn.hdel('login:',*tokens)
conn.zrem('recent:',*tokens)
四、使用Redis实现购物车
每个用户的购物车都是一个散列,这个散列存储了商品ID与商品订购数量之间的映射。对商品数量验证由web应用程序负责,我们要做的则是在商品订购数量发生变化时,对购物车进行更新。add_to_cart()函数展示了程序更新购物车的流程。
def add_to_cart(conn,session,item,count):
if count<=0:
conn.hrem('cart:'+session,item)//移除指定商品
else:
conn.hset('cart:'+session,item,count)//将指定商品添加至购物车
更新会话清理函数,使其清理旧会话的同时,将旧会话对应用户的购物车也一并删除,更新后的函数代码如下。
def clean_full_sessions(conn):
while not QUIT:
size=conn.zcard('recent')//找出目前已有令牌数量
if size<=LIMIT:
time.sleep(1)//令牌数量未超过限制,休眠并在之后重新检查
continue
end_index=min(size-LIMIT,100)
tokens=conn.zrange('recent:',0,end_index-1)//获取需要移除的令牌ID
sessions_keys=[] //为那些将要被删除的令牌构建键名
for sess in tokens:
session_keys.append('viewed:'+sess)
session_keys.append('cart:'+sess)//新增加的这行代码用于删除旧会话对应用户的购物车
conn.delete(*session_keys) //移除最旧的那些令牌
conn.hdel('login:',*sessions)
conn.zrem('recent:',*sessions)
从而将会话cookie和购物车cookie都存储在Redis中,得到进行数据分析的两个重要数据来源。
五、网页缓存
通过对游览数据分析发现,大型web应用所处理的95%的web页面每天最多只会改变一次,即这些页面不需要动态地生成,进行网页缓存可降低网站处理相同负载所需的服务器数量,减少数据库和web前端的负载,加快网站速度。所有标准Python应用框架都提供了在处理请求之前或之后添加层的能力,这些层称为中间件(middleware)或者插件(plugin)。我们将创建这样的一个层调用Redis缓存函数。
def cache_request(conn,request,callback):
if not can_cache(conn,request):
return callback(request) //对于不能被缓存的请求,直接调用回调函数
page_key='cache:'+hash_request(request) //将请求转换成一个简单的字符串键,方便以后进行查找
content=conn.get(page_key) //尝试查找被缓存的页面
if not content:
content=callback(request) //若页面还没有被缓存,那么生成页面
conn.setex(page_key,content,300) //将新生成的页面放到缓存里面
return content //返回页面
这个缓存函数可让网站在5分钟内无需再动态生成视图页面,这一改动可以将包含大量数据的页面的延迟值从20~50毫秒降至查询一次Redis所需时间(查询本地Redis的延迟通常低于1毫秒)。
六、数据行缓存和页面分析
数据行缓存可以减少经常发生变化页面的载入时间。网页分析可以只缓存一部分页面来减少实现页面缓存所需的内存数量。
七、总结
主要介绍了几种用于降低web应用的数据库负载和web服务器负载的几种方法,在为应用程序创建新构件时,不要惧怕重构已有构件。