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:web@localhost: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條數據:mysql

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)

測試結果:web

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)

測試結果:redis

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'))

測試結果:sql

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操做對應關係:json

最後,用函數calc_memory()單獨測試了一下性能(運行比較慢):
USED_MEMORY: 1.05M
COST: 427.4658901691437
USED_MEMORY: 5.82M
 因此說明存儲200萬活躍用戶技術,總計20天*100萬人,至關於2000萬人,採用此方案,只須要5M多空間。
以上代碼內容均來自董偉明老師的《Python Web開發實踐》,本身實踐了一遍,這裏主要用來學習、記錄和分享。
相關文章
相關標籤/搜索