Tornado自定義分佈式session框架

 

1、session框架處理請求執行的流程:

一、服務器端生成隨機的cookie字符串
二、瀏覽器發送請求,服務器將cookie返回給瀏覽器。
三、服務器在生成一個字典。字典的key爲cookie,value爲另外一個小字典。小字典就是爲用戶設置的字典
四、用戶再次訪問時,發送cookie到服務器端。服務器端收到cookie後,再去字典裏查看一下其對應的值是否正確。

2、必備知識點

在Tornado的源碼執行流程裏,全部咱們自定義的請求方法裏都會繼承一個基類:tornado.web.RequestHandler。這個類裏有一個擴展點def initialize()。在tornado執行處理請求方法以前會先執行這裏的方法。因此,咱們能夠利用此擴展點來實現session框架。html

 

在對session操做時,須要面向對象特殊成員的一個知識點:node

#!/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進行查找、建立、刪除的操做。python

3、代碼實現

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

4、分佈式實現

 

在前面的程序代碼中,咱們用的一個字典session_container = {},來存放客戶端session相關的信息。這樣作的缺點就是數據容易丟失。基於這個缺點,咱們就能夠把字典存放的方式改成拿專門的服務器來存放這些數據。如:redis、memcache等。可是若是隻拿一臺服務器來作這件事又會出現其餘的缺點,如:宕機、負載太高等。因此,咱們要在找出一個辦法解決這個不足。web

如上圖所示,咱們要實現多臺機器同時運行來存放用戶的session數據,首先生成一個哈希環。在這個環上存在幾臺機器的IP和權重。redis

當服務器對用戶生成了新的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
一致性哈希

 咱們能夠經過設置每臺機器的權重大小,來設計每臺機器所承擔的壓力和重要性。api

 

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
相關文章
相關標籤/搜索