上一篇文章: Python--Redis实战:第一章:初识Redis:第三节:你好Redis-文章投票试炼
下一篇文章: Python--Redis实战:第二章:使用Redis构建Web应用:第二节:使用Redis实现购物车
从高层次的角度来看,Web应用就是经过HTTP协议对网页浏览器发送的请求进行响应的服务器或者服务【service】。一个Web服务器对请求进行响应的典型步骤以下:数据库
以上列举的5个步骤从高层次的角度展现了典型Web服务器的运做方式,这种状况下的Web请求被认为是无状态的【stateless】,也就是说,服务器自己不会记录与过往有关的任何信息,这使得失效【fail】的服务器能够很容易地被替换掉。有很多书籍专门介绍了如何优化响应过程的各个步骤,本章要作的事情也相似,不一样之处是,咱们将介绍如何使用更快的Redis查询来替代传统的关系数据库查询,已经如何使用Redis来完场一些使用关系数据库没有办法高效完场的任务。编程
本章的全部内容都是围绕着发现并解决【Fake Web Retailer】这个虚构的大型网上商店来展开的,这个商店天天都会有大约500万名不一样的用户,这些用户会给网站带来一亿次点击,并从网站购买超过10万件商品。咱们之因此将这几个数据量设置的特别大,是考虑【若是能够在大数据背景下顺利解决问题,那么解决小数据量和中等数据量引起的问题就更不在话下】。segmentfault
每当咱们登陆互联网服务的时候,这些服务都会使用cookie来记录咱们的身份。cookie由少许数据组成,网站会要求咱们的浏览器存储这些数据,并在每次服务器请求发送时将这些数据传回给服务器。对于用来登陆的cookie,有两种常见的方式能够将登陆信息存储在cookie里面:浏览器
签名cookie一般会存储用户名,可能还有用户ID,用户最后一次登陆成功的时间,以及网站以为有用的其余任何信息。除了用户的相关信息以外,签名cookie还包含了一个签名,服务器可使用这个签名来验证浏览器发送的消息是否未经改动(好比将cookie中的登陆用户名改为另外一个用户)。缓存
令牌cookie会在cookie里面存储一串随机字节做为令牌,服务器能够根据令牌在数据库中查询令牌的拥有者。随着时间的推移,旧令牌会被新令牌去掉。安全
cookie类型 | 优势 | 缺点 |
---|---|---|
签名cookie | 验证cookie所需的一切信息都存储在cookie里面,cookie能够包含额外的信息,而且对这些信息进行签名也很容易。 | 正确的处理签名很难,很容易忘记对数据进行签名,或者忘记验证数据的签名,从而形成安全漏洞。 |
令牌cookie | 添加信息很是容易,cookie的体积很是小,所以移动终端和速度较慢的客户端能够更快地发送请求。 | 须要在服务中存储更多信息,若是使用的是关系数据库,那么载入和存储的cookie的代价可能会很高。 |
此次咱们使用令牌cookie来引用关系数据库表中负责存储用户登陆信息的条目【entry】。除了用户登陆信息以外,咱们还能够将用户的访问时长和已浏览商品的数量等信息存储到数据库里面,这样便于未来经过分析这些信息来学习若是更好得向用户推销商品。服务器
通常来讲,用户在决定购买某个或某些商品以前,一般都会先浏览多个不一样商品,而记录用户浏览过的全部商品以及用户最后一次访问页面的时间等信息,一般会致使大量的数据库写入。从长远来看,用户的这些浏览数据的确很是有用,但问题是,即使通过优化,大多数关系数据库在每台数据库服务器上每秒也只能插入、更细或者删除200~2000个数据行。尽可能批量插入、批量更新和批量删除等操做能够更快地速度执行,但由于客户端每次浏览网页都只更新少数几行,因此高速的批量插入在这里并不适用。cookie
咱们假设咱们的网站天天的负载量都比较大:平均每秒大约1200次写入,高峰时期每秒接近6000次写入,因此它必须部署10台关系数据服务器才能应对高峰时期的负载量。而咱们要作的就是适用Redis从新实现登陆cookie功能,取代由关系数据库实现的登陆cookie功能。网络
首先,咱们将使用一个散列来存储登陆cookie令牌和已登陆用户之间的映射。要检查一个用户是否已经登陆,须要根据给定的令牌来查找与之对应的用户,并在用户已经登陆的状况下,返回该用户的ID。session
def check_token(conn,token): #尝试获取并返回令牌对应的用户 return conn.hget('login:',token)
对令牌进行检查并不困难,由于大部分复杂的工做都是在更新令牌时完成的:用户每次浏览页面时,程序都会对用户存储在登陆散列里面的信息进行更新,并将用户的令牌和当前时间戳添加到记录最近登陆用户的有序集合里面;若是用户正在浏览的是一个商品页面,那么程序还会将这个商品添加到记录这个用户最近浏览过的商品的有序集合里面,并在被记录商品的数据超过25个时,对这个有序集合进行修建。
#更新令牌 import time def update_token(conn,token,user,item=None): timestamp=time.time() #h获取当前时间戳 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()
函数,咱们能够记录用户最后一次浏览商品的时间以及用户最近浏览了哪些商品。在一台最近几年生产的服务器上面,使用update_token()
函数每秒至少记录20000件商品,这比咱们预估的网站高峰期所需的6000次写入要高3倍有余。不只如此,经过后面介绍的一些方法,咱们还能够进一步优化update_token()
函数的运行速度。但在优化前,性能也比原有的关系数据库性能提高了10~100倍。
由于存储会话数据所需的内存会随着时间的推移而不断增长,因此咱们须要按期清理旧的会话数据,为了限制会话数据的数量,咱们决定只保留最新的1000万个会话。清理旧会话的程序由一个循环构成,这个循环每次执行的时候,都会检查存储最新登陆令牌的有序集合大小,若是有序集合的大小超过了限制,那么程序就会从有序集合里面移除最多100个最旧的令牌,并从记录用户登陆页面的散列表里面,移除被删除令牌对应的用户的信息,并对存储了这些用户最近浏览商品记录的有序集合进行清理。若是令牌的数量未超过限制,那么程序会休眠1秒,以后再从新进行检查。
#清理旧会话 import time QUIT=False LIMIT=10,000,000 def clean_sessions(conn): while not QUIT: #目前已有令牌的数量 size=conn.zcard('recent:') if size<=LIMIT: #令牌数量未超过限制,休眠1秒后再从新检查 time.sleep(1) continue end_index=min(size-LIMIT,100) tokens=conn.zrange('recent:',0,end_index-1) session_keys=[] #为那些将要删除的令牌构建键名 for token in tokens: session_keys.append('viewed:'+token) #移除最旧的那些令牌 conn.delete(*session_keys) conn.hdel('login:',*tokens) conn.zrem('recent:',*tokens)
让咱们经过计算来了解一下,这段简短的代码为何可以妥善地处理天天500万人次的访问:假设网站天天有500万用户访问,而且天天的用户都和以前的不同,那么只须要两天,令牌的数量就会达到1000万上限,并将网站的内存空间销毁殆尽,由于一天有:24*3600=86400秒,而网站平均每秒产生5 000 000/86400<58个新会话,若是清理函数以每秒的频率运行,那么它每秒须要清理将近60个令牌,才能防止令牌的数量过多的问题发生。可是实际上,咱们定义的令牌清理函数在经过网络来运行时,每秒可以清理10 000多个令牌,在本地运行时,每秒可以清理60 000多个令牌,这比所需的清理速度快乐150~1000倍,因此由于旧令牌过多而致使网站空间耗尽的问题不会出现。
熟悉多线程编程或者并发编程的读者可能会发现上面的清理函数包含了一个竞争条件【race condition】:若是清理函数正在删除某个用户的信息,而这个用户又在同一时间访问网站的话,那么竞争条件就会致使用户的信息被错误的删除。目前来看,这个竞争条件除了会使得用户须要从新登陆一次以外,并不会对程序记录的数据产生明显的影响,因此咱们暂时搁置这个问题,以后会讲解防止相似的竞争条件发生的方法。
经过使用Redis来记录用户信息,咱们成功地将天天要对数据库执行的行写入操做减小了数百万次。虽然这很是的了不得,但这只是咱们使用Redis构建Web应用程序的第一步,接下来咱们将展现如何使用Redis来处理另外一种类型的cookie。
上一篇文章: Python--Redis实战:第一章:初识Redis:第三节:你好Redis-文章投票试炼
下一篇文章: Python--Redis实战:第二章:使用Redis构建Web应用:第二节:使用Redis实现购物车