电影订票系统后台开发(6)Redis 实现 session 共享

电影订票系统后台开发(6)Redis 实现 session 共享
完整项目请戳 猿眼电影订票系统

Let’s go

项目开发环境

  • Deepin-Linux 15.4
  • Python 2.7.12
  • PyCharm
  • Flask
  • Redis

Redis

前面已经使用 Nginx 实现了反向代理和负载均衡,将客户端的请求分发到不同的服务器上去,那么就会产生一个问题,用户的 session 是存储在服务器上的,多台服务器上用户的 session 要怎样实现共享呢?
Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis 与其他 key-value 缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis 的优势主要体现在:

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子性 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

安装

1
2
3
sudo apt-get install redis-server   # Ubuntu 下
sudo yum install redis # CentOS 下
sudo service redis-server start # 启动 Redis 服务

Redis 的默认端口是6379,使用命令redis-cli进入Redis命令行模式,就可以执行Redis 命令了,Redis有默认配置文件,具体配置请看Redis 配置

数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

  • String(字符串)

string是redis最基本的类型,一个key对应一个value。string类型是二进制安全的,意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象,一个键最大能存储512MB。

  • Hash(哈希)

Redis hash 是一个键名对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。每个 hash 可以存储 $2^{32}-1$ 个键值对(40多亿)。

  • List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序,是双向列表,可以添加一个元素到列表的头部或者尾部,列表最多可存储 $2^{32}-1$ 个元素 (4294967295, 每个列表可存储40多亿)。

  • Set(集合)

Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 $2^{32}-1$。

  • zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数却可以重复。

session 共享

Redis 提供了多种语言的API,其中就包括Python,基本每个Redis 命令都有API可以调用,而且函数名与命令相同,可以很方便地在Python项目中连接和操作Redis。

1
2
(venv) $ pip install redis
(venv) $ pip freeze > requiremens.txt # 更新需求文件

接着我们只要重写 Flask 应用的 session 接口就可以使用 Redis 共享 session。注意需要在Flask私密配置文件中添加 Redis 服务器的ip和端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# *-* coding: utf-8 *-*
import pickle
from utils import UUID
from redis import Redis
from flask import Flask
from instance.config import REDIS
from werkzeug.datastructures import CallbackDict
from flask.sessions import SessionInterface, SessionMixin

class RedisSession(CallbackDict, SessionMixin):
def __init__(self, initial=None, sid=None, new=False):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.sid = sid
self.new = new
self.modified = False

class RedisSessionInterface(SessionInterface):
serializer = pickle
session_class = RedisSession

def __init__(self, redis=None, prefix='session:'):
if redis is None:
self.redis = Redis(host=REDIS[0], password=REDIS[1])
else:
self.redis = redis
self.prefix = prefix

def generate_sid(self):
return UUID()

def get_redis_expiration_time(self, app, session, permanent=False):
if permanent:
return timedelta(minutes=10)
return app.permanent_session_lifetime

def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
return self.session_class(sid=self.generate_sid(), new=True)
val = self.redis.get(self.prefix + sid)
if val is not None:
data = self.serializer.loads(val)
return self.session_class(data, sid=sid)
return self.session_class(sid=sid, new=True)

def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
self.redis.delete(self.prefix + session.sid)
if session.modified:
response.delete_cookie(app.session_cookie_name, domain=domain)
return
redis_exp = self.get_redis_expiration_time(app, session)
cookie_exp = self.get_expiration_time(app, session)
val = self.serializer.dumps(dict(session))
self.redis.setex(self.prefix + session.sid, val,
int(redis_exp.total_seconds()))
response.set_cookie(app.session_cookie_name, session.sid,
expires=cookie_exp, httponly=True, domain=domain)

app = Flask(__name__, instance_relative_config=True)
app.session_interface = RedisSessionInterface()

参考链接

文章目录
  1. Let’s go
    1. 项目开发环境
    2. Redis
      1. 安装
      2. 数据类型
      3. session 共享
  2. 参考链接
|
-->