zoukankan      html  css  js  c++  java
  • Tornado自定义分布式session框架

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

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

    二、必备知识点

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

    在对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进行查找、创建、删除的操作。

    三、代码实现

    #!/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

    四、分布式实现

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

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

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

    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
  • 相关阅读:
    PCB 奥宝LDI 输出正负片转换关系
    PCB拼板之多款矩形排样算法实现--学习
    PCB 3D PCB 后续改进与扩展功能一些想法
    PCB 周期日历
    PCB LDI文件 自动化输出(改造)实现思路
    PCB Windows远程桌面一键登录
    PCB MS SQL 排序应用---SQL相邻数据区间值求解
    PCB MS SQL 排序应用---相邻数据且相同合并处理
    SpringMVC快速入门
    linux安装jdk
  • 原文地址:https://www.cnblogs.com/chenchao1990/p/5481753.html
Copyright © 2011-2022 走看看