Tornado自定义分布式session框架

时间:2021-05-05 20:36:37

Tornado自定义分布式session框架

 

一、session框架处理请求执行的流程:

1、服务器端生成随机的cookie字符串 2、浏览器发送请求,服务器将cookie返回给浏览器。 3、服务器在生成一个字典。字典的key为cookie,value为另一个小字典。小字典就是为用户设置的字典 4、用户再次访问时,发送cookie到服务器端。服务器端收到cookie后,再去字典里查看一下其对应的值是否正确。

二、必备知识点

在Tornado的源码执行流程里,所有我们自定义的请求方法里都会继承一个基类:tornado.web.RequestHandler。这个类里有一个扩展点def initialize()。在tornado执行处理请求方法之前会先执行这里的方法。所以,我们可以利用此扩展点来实现session框架。

 

在对session操作时,需要面向对象特殊成员的一个知识点:

Tornado自定义分布式session框架Tornado自定义分布式session框架
#!/usr/bin/env python
#
-*- coding:utf-8 -*-

class Foo(object):

def __getitem__(self, key):
print '__getitem__',key

def __setitem__(self, key, value):
print '__setitem__',key,value

def __delitem__(self, key):
print '__delitem__',key


obj
= Foo()
result
= obj['k1']
#obj['k2'] = 'wupeiqi'
#
del obj['k1']
面向对象特殊成员

通过这个方法,我们就可以对session进行查找、创建、删除的操作。

三、代码实现

Tornado自定义分布式session框架Tornado自定义分布式session框架
#!/usr/bin/env python
#
-*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web
from hashlib import sha1
import os, time

session_container
= {}

create_session_id
= lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()


class Session(object):

session_id
= "__sessionId__"

def __init__(self, request):
session_value
= request.get_cookie(Session.session_id) # 根据自定义的值获取到客户端请求的cookie
if not session_value: # 如果没有说明是第一次请求,需要生成一个随机字符串当作cookie
self._id = create_session_id()
else:
self._id
= session_value
request.set_cookie(Session.session_id, self._id)
# ?????

def __getitem__(self, key):
return session_container[self._id][key]

def __setitem__(self, key, value):
# user = chenchap pwd = 123.com
if session_container.has_key(self._id):
session_container[self._id][key]
= value
else:
session_container[self._id]
= {key: value}

def __delitem__(self, key):
del session_container[self._id][key]


class BaseHandler(tornado.web.RequestHandler):

def initialize(self):

self.my_session
= Session(self)


class MainHandler(BaseHandler):

def get(self):
print self.my_session['c_user']
print self.my_session['c_card']
self.write(
'index')


class LoginHandler(BaseHandler):

def get(self):
self.render(
'login.html', **{'status': ''})

def post(self, *args, **kwargs):

username
= self.get_argument('name')
password
= self.get_argument('pwd')
if username == 'wupeiqi' and password == '123':

self.my_session[
'c_user'] = 'chenchao'
self.my_session[
'c_card'] = '123.com'

self.redirect(
'/index')
else:
self.render(
'login.html', **{'status': '用户名或密码错误'})

settings
= {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
'login_url': '/login'
}

application
= tornado.web.Application([
(r
"/index", MainHandler),
(r
"/login", LoginHandler),
],
**settings)


if __name__ == "__main__":
application.listen(
8888)
tornado.ioloop.IOLoop.instance().start()
session_farm

四、分布式实现

Tornado自定义分布式session框架

 

在前面的程序代码中,我们用的一个字典session_container = {},来存放客户端session相关的信息。这样做的缺点就是数据容易丢失。基于这个缺点,我们就可以把字典存放的方式改为拿专门的服务器来存放这些数据。如:redis、memcache等。但是如果只拿一台服务器来做这件事又会出现其他的缺点,如:宕机、负载过高等。所以,我们要在找出一个办法解决这个不足。

如上图所示,我们要实现多台机器同时运行来存放用户的session数据,首先生成一个哈希环。在这个环上存在几台机器的IP和权重。

当服务器对用户生成了新的cookie字符串时,我们得到这个字符串,经过一致性哈希算法得出一个值。然后与机器所设置的权重做对比,就可以确定要把这个用户的session信息放到哪一台服务器上。之后用户在次请求时,服务器就会根据用户发来的cookie经过计算后得知去哪一台服务器查找已经保存的session信息。

Tornado自定义分布式session框架Tornado自定义分布式session框架
#!/usr/bin/env python
#
coding:utf-8

import sys
import math
from bisect import bisect


if sys.version_info >= (2, 5):
import hashlib
md5_constructor
= hashlib.md5
else:
import md5
md5_constructor
= md5.new


class HashRing(object):
"""一致性哈希"""

def __init__(self, nodes):
'''
初始化
nodes : 初始化的节点,其中包含节点以及节点对应的权重
默认每一个节点有32个虚拟节点
对于权重,通过多创建虚拟节点来实现
如:nodes = [
{'host':'127.0.0.1:8000','weight':1},
{'host':'127.0.0.1:8001','weight':2},
{'host':'127.0.0.1:8002','weight':1},
]
'''

self.ring
= dict()
self._sorted_keys
= []
self.total_weight
= 0
self.
__generate_circle(nodes)

def __generate_circle(self,nodes):
for node_info in nodes:
self.total_weight
+= node_info.get('weight', 1) # 计算出总的权重

for node_info in nodes:
weight
= node_info.get('weight',1) # 获取每个节点的权重
node = node_info.get('host',None) # 获取每个节点的host

virtual_node_count
= math.floor((32*len(nodes)*weight) / self.total_weight)
for i in xrange(0,int(virtual_node_count)):
key
= self.gen_key_thirty_two( '%s-%s' % (node, i) )

if self._sorted_keys.__contains__(key):
raise Exception('该节点已经存在.')
self.ring[key]
= node

self._sorted_keys.append(key)

def add_node(self,node):
''' 新建节点
node : 要添加的节点,格式为:{'host':'127.0.0.1:8002','weight':1},其中第一个元素表示节点,第二个元素表示该节点的权重。
'''
node
= node.get('host',None)
if not node:
raise Exception('节点的地址不能为空.')

weight
= node.get('weight',1)

self.total_weight
+= weight
nodes_count
= len(self._sorted_keys) + 1

virtual_node_count
= math.floor((32 * nodes_count * weight) / self.total_weight)
for i in xrange(0,int(virtual_node_count)):
key
= self.gen_key_thirty_two( '%s-%s' % (node, i) )
if self._sorted_keys.__contains__(key):
raise Exception('该节点已经存在.')
self.ring[key]
= node
self._sorted_keys.append(key)

def remove_node(self,node):
''' 移除节点
node : 要移除的节点 '127.0.0.1:8000'
'''
for key,value in self.ring.items():
if value == node:
del self.ring[key]
self._sorted_keys.remove(key)

def get_node(self,string_key):
'''获取 string_key 所在的节点'''
pos
= self.get_node_pos(string_key)
if pos is None:
return None

return self.ring[self._sorted_keys[pos]].split(':')

def get_node_pos(self,string_key):
'''获取 string_key 所在的节点的索引'''
if not self.ring:
return None

key
= self.gen_key_thirty_two(string_key)
nodes
= self._sorted_keys
pos
= bisect(nodes, key) # 根据一个列表和加密的字符串计算出一个数值

return pos

def gen_key_thirty_two(self, key):

m
= md5_constructor() # md5加密
m.update(key)

return long(m.hexdigest(), 16)

def gen_key_sixteen(self, key):

b_key
= self.__hash_digest(key)
return self.__hash_val(b_key, lambda x: x)

def __hash_val(self, b_key, entry_fn):

return (( b_key[entry_fn(3)] << 24)|(b_key[entry_fn(2)] << 16)|(b_key[entry_fn(1)] << 8)| b_key[entry_fn(0)] )

def __hash_digest(self, key):
m
= md5_constructor()
m.update(key)
return map(ord, m.digest())


nodes
= [
{
'host':'127.0.0.1:8000','weight':15},
{
'host':'127.0.0.1:8001','weight':20},
{
'host':'127.0.0.1:8002','weight':10},
]

ring
= HashRing(nodes)
result
= ring.get_node('sdgsdg1s56g156gge56rgerg4')
print result
一致性哈希

 我们可以通过设置每台机器的权重大小,来设计每台机器所承担的压力和重要性。

 

so.一开始的那段代码可以这么修改:

Tornado自定义分布式session框架Tornado自定义分布式session框架
from hashlib import sha1
import os, time


create_session_id
= lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()


class Session(object):

session_id
= "__sessionId__"

def __init__(self, request):
session_value
= request.get_cookie(Session.session_id)
if not session_value:
self._id
= create_session_id()
else:
self._id
= session_value
request.set_cookie(Session.session_id, self._id)

def __getitem__(self, key):
# 根据 self._id ,在一致性哈西中找到其对应的服务器IP
# 找到相对应的redis服务器,如: r = redis.StrictRedis(host='localhost', port=6379, db=0)
# 使用python redis api 链接
# 获取数据,即:
# return self._redis.hget(self._id, name)

def __setitem__(self, key, value):
# 根据 self._id ,在一致性哈西中找到其对应的服务器IP
# 使用python redis api 链接
# 设置session
# self._redis.hset(self._id, name, value)


def __delitem__(self, key):
# 根据 self._id 找到相对应的redis服务器
# 使用python redis api 链接
# 删除,即:
return self._redis.hdel(self._id, name)
session