Redis的Python实践,以及四中常用应用场景详解——学习董伟明老师的《Python Web开发实践》

首先,简单介绍:Redis是一个基于内存的键值对存储系统,常用作数据库、缓存和消息代理。

支持:字符串,字典,列表,集合,有序集合,位图(bitmaps),地理位置,HyperLogLog等多种数据结构。

支持事务、分片、主从复之、支持RDB(内存数据保存的文件)和AOF(类似于MySQL的binlog)两种持久化方式。3.0加入订阅分发、Lua脚本、集群等特性。

命令参考:http://doc.redisfans.com

中文官网:http://www.redis.net.cn

安装(都大同小异,安装前建议用先用search搜索一下):

  ubuntu:sudo apt-get install redis-server -yq

  MAC: sudo port install redis-server

  CentOS: yum install redis-server

安装之后已经启动,可以用redis-cli验证,也可以ps -ef | grep redis

安装redis的Python包:

pip install redis

安装之后可以这样基本使用:

In [15]: import redis

In [16]: conn = redis.Redis()

In [17]: conn.rpush(‘a‘,‘2‘)

Out[17]: 1

In [18]: conn = redis.StrictRedis(host=‘localhost‘, port=6379, db=0)

In [19]: conn.lrange(‘a‘,0,-1)

Out[19]: [b‘2‘]

Python调用redis的函数和redis本身的命令都差不多,一般参数个数和类型都相同,可以直接查看Redis命令参考,再去Python的redis模块的client.py中去找

OK,接下来看看Python都可以用Redis来做些什么吧,四种常用使用场景:

1.取最新N个数据操作:

该场景应用于比如每天更新10万条数据,均写入关系型数据库(如MySQL),可以在Redis中保存最新的1万条数据,方便读取和查询,极大提高读取查询效率。

使用Redis的List数据结构,用到常用命令LPUSH,LTRIMLRANGE即可,用Flak来呈现,用SQLAlchemy来连接MySQL:

服务端代码:

# coding=utf-8
import json
from datetime import datetime

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
import redis
import pymysql
pymysql.install_as_MySQLdb()

app = Flask(__name__)
app.config[‘SQLALCHEMY_DATABASE_URI‘] = ‘mysql://web:[email protected]:3306/r‘
app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS‘] = False
db = SQLAlchemy(app)
r = redis.StrictRedis(host=‘localhost‘, port=6379, db=0)
MAX_FILE_COUNT = 50

#数据模型,有id,name和uploadtime三个字段
class PasteFile(db.Model):
    __tablename__ = ‘files‘
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(5000), nullable=False)
    uploadtime = db.Column(db.DateTime, nullable=False)

    def __init__(self, name=‘‘, uploadtime=None):
        self.uploadtime = datetime.now() if uploadtime is None else uploadtime
        self.name = name

db.create_all()

#该函数对POST传入的id和name,写MySQL数据库,LPUSH到Redis中,并LTRIM裁剪保留MAX_FILE_COUNT个
@app.route(‘/upload‘, methods=[‘POST‘])
def upload():
    name = request.form.get(‘name‘)

    pastefile = PasteFile(name)
    db.session.add(pastefile)
    db.session.commit()
    r.lpush(‘latest.files‘, pastefile.id)
    r.ltrim(‘latest.files‘, 0, MAX_FILE_COUNT - 1)

    return jsonify({‘r‘: 0})

#该视图函数截取start-limit个数据,通过json呈现在街面上,默认0-20就是最新插入的20条
@app.route(‘/lastest_files‘)
def get_lastest_files():
    start = request.args.get(‘start‘, default=0, type=int)
    limit = request.args.get(‘limit‘, default=20, type=int)
    ids = r.lrange(‘latest.files‘, start, start + limit - 1)
    files = PasteFile.query.filter(PasteFile.id.in_(ids)).all()
    return json.dumps([{‘id‘: f.id, ‘filename‘: f.name} for f in files])

if __name__ == ‘__main__‘:
    app.run(host=‘0.0.0.0‘, port=9000, debug=True)

随机生成100条数据:

from lastest_files import app, PasteFile, r
import time ,random, string

#随机生成100条name插入MySQl表,id自增
with app.test_client() as client:
    for _ in range(1,101):
        data = ‘‘.join(random.sample(string.ascii_letters,10))+‘_‘+str(_)
        print (‘input data: ‘,data)
        client.post(‘/upload‘,data={‘name‘:data})
        time.sleep(0.5)

测试结果:

2.取TOP N操作(排行榜应用)

 该场景用于游戏或者需要分数排名的地方

主要利用Redis的有序集合(SortedSet)其中:score值递减(从大到小)的次序排列。

用到Redis有序集合的:ZADD,ZREVRANGE,ZCOUNT,ZREVRANGEBYSCORE命令

测试代码:

# coding=utf-8
import string
import random

import redis

r = redis.StrictRedis(host=‘localhost‘, port=6379, db=0)
GAME_BOARD_KEY = ‘game.board‘

for _ in range(1000):
    score = round((random.random() * 100), 2)
    user_id = ‘‘.join(random.sample(string.ascii_letters, 6))
    #随机生成1000个用户,每个用户具有得分和用户名字,插入Redis的有序集合中
    r.zadd(GAME_BOARD_KEY, score, user_id)

# 随机获得一个用户和他的得分
user_id, score = r.zrevrange(GAME_BOARD_KEY, 0, -1,
                             withscores=True)[random.randint(0, 200)]
print (user_id, score)
#用有序集合的ZCOUNT获取0-100的个数也就是所有人的数量,获取0-score分数段的人数,也就是这个用户分数超过了多少人
board_count = r.zcount(GAME_BOARD_KEY, 0, 100)
current_count = r.zcount(GAME_BOARD_KEY, 0, score)

print (current_count, board_count)

print (‘TOP 10‘)
print (‘-‘ * 20)

#用有序集合的ZREVRANGEBYSCORE返回指定区间的元素
#ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
for user_id, score in r.zrevrangebyscore(GAME_BOARD_KEY, 100, 0, start=0,
                                         num=10, withscores=True):
    print (user_id, score)

测试结果:

b‘mgOvfl‘ 83.04
811 1000
TOP 10
--------------------
b‘rbhXNd‘ 99.91
b‘KJFELh‘ 99.88
b‘cyjNrJ‘ 99.81
b‘RXohkG‘ 99.64
b‘SMVFbu‘ 99.51
b‘FMBEgz‘ 99.5
b‘ajxhdp‘ 99.45
b‘QuMSpL‘ 99.33
b‘IFYCOs‘ 99.31
b‘VyWnYC‘ 98.74

3.计数器:

Redis非常适合用来做计数器:

没什么好解释的,就是INCR,DECR,INCRBY

4.实时统计:

Redis的位图提供了二进制操作,非常适合存储布尔类型的值,常见场景就是记录用户登陆状态。

该场景用二进制的方式表示用户是否登录,比如说有10个用户,则0000000000表示无人登录,0010010001表示第3个、第6个、第10个用户登录过,即是活跃的。

用到Redis字符串(String)结构中的:BITCOUNT,GETBIT,BITOP命令

对本月每天的用户登录情况进行统计,会针对每天生成key,例如今天的:account:active:2016:11:23,也会生成月的key:account:active:2016:11和年的key:key:account:active:2016

每个key中的字符串长度就是人数(可能有的key的str没有那么长,那是因为最后一个bit没有set成1,不过没有就相当于是0)

测试代码:

# coding=utf-8
import time
import random
from datetime import datetime

import redis

r = redis.StrictRedis(host=‘localhost‘, port=6379, db=0)
ACCOUNT_ACTIVE_KEY = ‘account:active‘

r.flushall()
# r.delete(ACCOUNT_ACTIVE_KEY)
now = datetime.utcnow()

def record_active(account_id, t=None):
    #第一次t自己生成,后面t接受传入的年月日
    if t is None:
        t = datetime.utcnow()
    #Redis事务开始
    p = r.pipeline()
    key = ACCOUNT_ACTIVE_KEY
    #组合了年月日三种键值,同时将三个键值对应字符串的account_id位置为1
    #符合逻辑:该人在这一天登陆,肯定也在当前月登陆,也在当年登陆
    for arg in (‘year‘, ‘month‘, ‘day‘):
        key = ‘{}:{}‘.format(key, getattr(t, arg))
        p.setbit(key, account_id, 1)
    #Redis事务提交,真正执行
    p.execute()

def gen_records(max_days, population, k):
    #循环每天的情况,从1-max_days天
    for day in range(1, max_days):
        time_ = datetime(now.year, now.month, day)
        #每天随机生成k个数字,表示k个人活跃
        accounts = random.sample(range(population), k)
        #将这k个人对应在当天的字符串中修改,对应位置的bit置为1,表明这个天他有登陆过
        for account_id in accounts:
            record_active(account_id, time_)

#查看记录100万数据中随机选择10万活跃用户时的内存占用
def calc_memory():
    r.flushall()
    #执行前先看当前的内存占用
    print (‘USED_MEMORY: {}‘.format(r.info()[‘used_memory_human‘]))

    start = time.time()
    #100万种选择10万,20天
    gen_records(21, 1000000, 100000)
    #记录话费时间
    print (‘COST: {}‘.format(time.time() - start))
    #添加记录后的内存占用
    print (‘USED_MEMORY: {}‘.format(r.info()[‘used_memory_human‘]))

gen_records(29, 10000, 2000)
#这个月总的活跃用户数,直接查询记录月的key:bitcount "account:active:2016:11"
print (r.bitcount(‘{}:{}:{}‘.format(ACCOUNT_ACTIVE_KEY, now.year, now.month)))
#今天的活跃用户数:bitcount "account:active:2016:11:23"
print (r.bitcount(‘{}:{}:{}:{}‘.format(ACCOUNT_ACTIVE_KEY, now.year,
                                      now.month, now.day)))
#随机找一个account_id为1200的用户,查看他是否登陆过:getbit "account:active:2016:11" 1200
account_id = 1200
print (r.getbit(‘{}:{}:{}‘.format(ACCOUNT_ACTIVE_KEY, now.year, now.month),
               account_id))
#getbit "account:active:2016:11" 10001
print (r.getbit(‘{}:{}:{}‘.format(ACCOUNT_ACTIVE_KEY, now.year, now.month),
               10001))
#获取当月1号和2号的建
keys = [‘{}:{}:{}:{}‘.format(ACCOUNT_ACTIVE_KEY, now.year, now.month, day)
        for day in range(1, 3)]
#获取1号和2号的活跃的用户总数
r.bitop(‘or‘, ‘destkey:or‘, *keys)
print (r.bitcount(‘destkey:or‘))
#获取在1号和2号都活跃的用户数
r.bitop(‘and‘, ‘destkey:and‘, *keys)
print (r.bitcount(‘destkey:and‘))

测试结果:

9974
2000
1
0
3593
407

对应的Redis操作结果:

127.0.0.1:6379> bitcount "account:active:2016:11"
(integer) 9974
127.0.0.1:6379> bitcount "account:active:2016:11:23"
(integer) 2000
127.0.0.1:6379> getbit "account:active:2016:11" 1200
(integer) 1
127.0.0.1:6379> getbit "account:active:2016:11" 10001
(integer) 0
127.0.0.1:6379> bitop ‘or‘ ‘destkey:or‘ "account:active:2016:11:1","account:active:2016:11:2"
Invalid argument(s)
127.0.0.1:6379> bitop ‘or‘ ‘destkey:or‘ "account:active:2016:11:1" "account:active:2016:11:2"
(integer) 1250
127.0.0.1:6379> bitcount destkey:or
(integer) 3593
127.0.0.1:6379> bitop ‘and‘ ‘destkey:and‘ "account:active:2016:11:1" "account:active:2016:11:2"
(integer) 1250
127.0.0.1:6379> bitcount destkey:and
(integer) 407

 Python代码中输出与Redis操作对应关系:

最后,用函数calc_memory()单独测试了一下性能(运行比较慢):

USED_MEMORY: 1.05M
COST: 427.4658901691437
USED_MEMORY: 5.82M

所以说明存储200万活跃用户技术,总计20天*100万人,相当于2000万人,采用此方案,只需要5M多空间。

以上代码内容均来自董伟明老师的《Python Web开发实践》,自己实践了一遍,这里主要用来学习、记录和分享。

时间: 2024-12-08 20:16:48

Redis的Python实践,以及四中常用应用场景详解——学习董伟明老师的《Python Web开发实践》的相关文章

maven常用插件配置详解

常用插件配置详解Java代码    <!-- 全局属性配置 --> <properties> <project.build.name>tools</project.build.name> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> project.build.name:  用来定义war包名称  proje

Mobile Web开发实践

移动设备的快速发展给用户带来了很大的便利.用户使用Android.iPhone和其它移动设备很容易接入互联网.移动设备对网页的性能要求比较高,下面就说说Mobile Web开发的一些心得. Viewport 当你用iPhone访问一个没有面向移动设备优化过的网站时(最好选取960px宽度的网站),你会发现iPhone上面刚好可以把整个页面显示出来,但是页面被缩小了非常多,字体非常小.这其实是Sarari默认把自己当成980px宽度来处理的,而所有iPhone(竖屏)的真实宽度为320px,事实上

Oracle 建表常用数据类型的详解

创建表时,必须为表的各个列指定数据类型.如果实际的数据与该列的数据类型不相匹配,则数据库会拒绝保存.如为学生指定出生日期为"1980-13-31". 在Oracle中,常见的数据类型有: 字符串:字符串分为定长类型char和变长类型varchar2. 数字:整数 number(整数位),小数 number(总长度,小数位),只写number,表示无限制. 日期:date类型,可以保存年月日时分秒. 问题:Oracle中为什么字符串类型为varchar2,它与varchar有什么关系?

七牛云存储Python SDK使用教程 - 上传策略详解

文 七牛云存储Python SDK使用教程 - 上传策略详解 七牛云存储 python-sdk 七牛云存储教程 jemygraw 2015年01月04日发布 推荐 1 推荐 收藏 2 收藏,2.7k 浏览 本教程旨在介绍如何使用七牛的Python SDK来快速地进行文件上传,下载,处理,管理等工作. 前言 我们在上面的两节中了解到,客户端上传文件时,需要从业务服务器申请一个上传凭证(Upload Token),而这个上传凭证是业务服务器根据上传策略(PutPolicy)来生成的,而这个生成过程中

Python调用中科院NLPIR(ICTCLAS2015)详解 刘超([email&#160;protected])

Python调用中科院NLPIR(ICTCLAS2015)详解 南京理工大学 计算机科学与工程学院 RuiXia_NUSTM课题组 ChaoLiu([email protected]) ------------------------- 主要讲解内容: 1. NLPIR版本及下载 2. 代码问题 3. userdict的使用问题 ------------------------- 友情提示:如果是swig问题,自己处理.首先下载swig,swig可以帮助我们将C或者C++编写的DLL或者SO文件

Spring Data Redis简介以及项目Demo,RedisTemplate和 Serializer详解

一.概念简介: Redis: Redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写,详细的信息在Redis官网上面有,因为我自己通过google等各种渠道去学习Redis,走了不少弯路,所以总结一条我认为不错的学习路径给大家: 1.<The Little Redis Book> 是一本开源PDF,只有29页的英文文档,看完后对Redis的基本概念应该差不多熟悉了,剩下的可以去Redis官网熟悉相关的命令. 2.<Redis设计与实现> 如果想继续深入,推

MySQL的数据类型及其常用修饰符详解

MySQL的数据类型及其常用修饰符详解 ================================================================================ 概述: ========================================================================================== Mysql的数据类型     在mysql数据库当中,每一个库都是有多张表来组成的,每一个表都是由行和列来组

Eclipse或Myeclipse常用快捷键组合详解

Eclipse 是一个开放源代码的.基于Java的可扩展开发平台,就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境.. Eclipse(Myeclipse)中有很多便于开发的快捷键组合,从而节省时间提高开发效率.本文介绍了常用的一些个人认为有用而且实用的快捷组合, 从而使开发者更加容易的浏览源代码或个人写的代码,使得整体的开发效率代码质量得到提升. 1.ctrl+shift+R  打开资源此 组快捷键可以打开工程中任意一个文件,而本人只需按文件名或者mask名的字母顺序输入就

Python - 定制语法的string模板(template) 详解

定制语法的string模板(template) 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/28614491 string模板(template)参考: http://blog.csdn.net/caroline_wendy/article/details/27054263 string.Template()内添加替换的字符, 使用"$"符号, 或 在字符串内, 使用"${}"; 调用时使