Python學(xué)習(xí)筆記(十二)NoSQL數(shù)據(jù)存儲(chǔ)

有些數(shù)據(jù)庫(kù)并不是關(guān)系型的,不支持 SQL。它們用來(lái)處理龐大的數(shù)據(jù)集、支持更加靈活的 數(shù)據(jù)定義以及定制的數(shù)據(jù)操作。這些被統(tǒng)稱為 NoSQL(not only SQL) 。

dbm family

dbm格式是按照鍵值對(duì)的形式儲(chǔ)存,封裝在應(yīng)用程序(例如網(wǎng)頁(yè)瀏覽器)中,用來(lái)維護(hù)各種各樣的配置。從以下角度看,dbm 數(shù)據(jù)庫(kù)和 Python 字典是類似的:

  • 給一個(gè)鍵賦值,自動(dòng)保存到磁盤中的數(shù)據(jù)庫(kù)
  • 通過(guò)鍵得到對(duì)應(yīng)的值

下面看一個(gè)小栗子:
open() 方法的第二個(gè)參數(shù) 'r' 代表讀;'w' 代表寫;'c' 表示讀和寫, 如果文件不存在則創(chuàng)建.

In [1]: import dbm
In [2]: db = dbm.open('definitions','c') 
同字典一樣創(chuàng)建鍵值對(duì),給一個(gè)鍵賦值:
In [3]: db['mustartd'] = 'yellow' 
In [4]: db['ketchup'] = 'red'  
In [5]: db['pesto'] = 'green'
查看數(shù)據(jù)庫(kù)長(zhǎng)度,并通過(guò)鍵值獲得數(shù)據(jù)
In [6]: len(db) 
Out[6]: 3

In [7]: db['mustartd'] 
Out[7]: b'yellow'
關(guān)掉數(shù)據(jù)庫(kù),然后重新打開驗(yàn)證它是否被完整保存:
In [8]: db.close() 

In [9]: db = dbm.open('definitions','r') 

In [10]: db['mustard'] 
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-10-8228d50ea548> in <module>()
----> 1 db['mustard']

KeyError: 'mustard'

In [11]: db['mustartd'] 
Out[11]: b'yellow'

鍵和值都以字節(jié)保存,因此不能對(duì)數(shù)據(jù)庫(kù)對(duì)象db進(jìn)行迭代,但是可以使用函數(shù)len()得到鍵的數(shù)目。注意哦:get()和setdefault()函數(shù)只能用于字典的方法。


memcached

memcached是一種快速的,內(nèi)存鍵值對(duì)象的緩存服務(wù)器。它一般置于數(shù)據(jù)庫(kù)之前,用于存儲(chǔ)網(wǎng)頁(yè)服務(wù)器會(huì)話數(shù)據(jù)。如果想要使用,需要一個(gè)memcached服務(wù)器和Python的驅(qū)動(dòng)程序。
這樣的驅(qū)動(dòng)程序很多,能夠在Python3中使用的是python3-memcached,可以通過(guò)命令安裝: pip install python-memcached

連接到一個(gè)memcached服務(wù)器之后,可以做以下事項(xiàng):

  • 賦值和取值
  • 其中一個(gè)值的自增或者自減
  • 刪除其中一個(gè)鍵

數(shù)據(jù)在memcached并不是持久化保存的,后面的可能會(huì)覆蓋早些寫入的數(shù)據(jù),這是它的固有特性,因?yàn)樗鳛橐粋€(gè)緩存服務(wù)器,通過(guò)舍棄舊數(shù)據(jù)避免程序運(yùn)行時(shí)內(nèi)存不足的問(wèn)題。

我們可以同事連接到多個(gè)memcached服務(wù)器,下面的栗子是連接一個(gè):
注意一點(diǎn)哦:在連接之前,一點(diǎn)要確保memcached服務(wù)開啟。

In [4]: import memcache

In [5]: db = memcache.Client(['127.0.0.1:11211']) 

In [6]: db.set('marco','polo') 
Out[6]: True

In [7]: db.set('ducks', 0)  
Out[7]: True

In [8]: db.get('ducks') 
Out[8]: 0

In [9]: db.incr('ducks',2)
Out[9]: 2

In [10]: db.get('ducks') 
Out[10]: 2

Redis

Redis是一種數(shù)據(jù)結(jié)構(gòu)服務(wù)器(data structure server)。和memcached類似,Redis服務(wù)器的所有數(shù)據(jù)都是基于內(nèi)存的(現(xiàn)在也可以把數(shù)據(jù)放在磁盤)。不同于memcached,Redis可以實(shí)現(xiàn):

  • 存儲(chǔ)數(shù)據(jù)到磁盤,方便斷電重啟和提升可靠性
  • 保存舊數(shù)據(jù)
  • 提供多種數(shù)據(jù)結(jié)構(gòu),不限于簡(jiǎn)單字符串

Redis的數(shù)據(jù)類型和Python很相似,Redis服務(wù)器會(huì)是一個(gè)或多個(gè)Python應(yīng)用程序之間共享數(shù)據(jù)的非常有幫助的中間件。

Python的Redis驅(qū)動(dòng)程序redis-py可以使用命令安裝: pip install redis

Redis 服務(wù)器自身就有好用的文檔。如果在本地計(jì)算機(jī)(網(wǎng)絡(luò)名為 localhost)安裝和啟動(dòng) 了 Redis 服務(wù)器,就可以開始嘗試下面的程序。

1.字符串

具有單一值的一個(gè)鍵被稱作Redis的字符串。簡(jiǎn)單的Python數(shù)據(jù)類型可以自動(dòng)轉(zhuǎn)換成Redis字符串?,F(xiàn)在連接到一些主機(jī)(默認(rèn)localhost)以及端口(默認(rèn)6379)上的服務(wù)器:
同樣在連接之前,一點(diǎn)要確保memcached服務(wù)開啟

In [1]: import redis
In [2]: conn = redis.Redis()
redis.Redis(‘localhost’) 或者 redis.Redis('localhost', 6379) 會(huì)得到同樣的結(jié)果。

列出所有的鍵(目前為空):
In [3]: conn.keys('*') 
Out[3]: []

給鍵 'secret' 賦值一個(gè)字符串;給鍵 'carats' 賦一個(gè)整數(shù);給鍵 'fever' 賦一個(gè)浮點(diǎn)數(shù):

In [4]: conn.set('secret', 'ni!') 
Out[4]: True

In [5]: conn.set('carats', 24) 
Out[5]: True

In [6]: conn.set('fever', '101.5') 
Out[6]: True

通過(guò)鍵反過(guò)來(lái)得到對(duì)應(yīng)的值:

In [7]: conn.get('secret') 
Out[7]: b'ni!'

In [8]: 

In [8]:  conn.get('carats') 
Out[8]: b'24'

In [9]: conn.get('fever') 
Out[9]: b'101.5'

這里的 setnx() 方法只有當(dāng)鍵不存在時(shí)才設(shè)定值:

In [10]: conn.setnx('secret', 'icky-icky-icky-ptang-zoop-boing!') 
Out[10]: False

方法運(yùn)行失敗,因?yàn)橹耙呀?jīng)定義了 'secret':
In [11]: conn.get('fever') 
Out[11]: b'101.5'

In [12]: conn.get('secret') 
Out[12]: b'ni!'

方法 getset() 會(huì)返回舊的值,同時(shí)賦新的值:
In [13]: conn.getset('secret', 'icky-icky-icky-ptang-zoop-boing!') 
Out[13]: b'ni!'

In [14]: conn.get('secret') 
Out[14]: b'icky-icky-icky-ptang-zoop-boing!'

使用函數(shù) getrange() 得到子串(偏移量 offset:0 代表開始,-1 代表結(jié)束):
In [15]: conn.getrange('secret',-6,-1) 
Out[15]: b'boing!'

使用函數(shù) setrange() 替換子串(從開始位置偏移):
In [16]: conn.setrange('secret',0,'ICKY') 
Out[16]: 32

In [17]: conn.get('secret') 
Out[17]: b'ICKY-icky-icky-ptang-zoop-boing!'

接下來(lái)使用函數(shù) mset() 一次設(shè)置多個(gè)鍵值:
In [18]: conn.mset({'pie':'cherry','cordial':'sherry'}) 
Out[18]: True

使用函數(shù) mget() 一次取到多個(gè)鍵的值:
In [19]: conn.mget(['pie','fever']) 
Out[19]: [b'cherry', b'101.5']

使用函數(shù) delete() 刪掉一個(gè)鍵:
In [20]: conn.delete('fever') 
Out[20]: 1

使用函數(shù) incr() 或者 incrbyfloat() 增加值,函數(shù) decr() 減少值:
In [21]: conn.incr('carats')  
Out[21]: 25

In [22]: conn.incr('carats', 10)  
Out[22]: 35

In [23]: conn.decr('carats') 
Out[23]: 34

In [24]: conn.decr('carats', 15)  
Out[24]: 19

In [25]: conn.set('fever', '101.5') 
Out[25]: True

In [26]: conn.incrbyfloat('fever') 
Out[26]: 102.5

In [27]: conn.incrbyfloat('fever', 0.5) 
Out[27]: 103.0

不存在函數(shù) decrbyfloat(),可以用增加負(fù)數(shù)代替:
In [28]: conn.incrbyfloat('fever', -2.0)  
Out[28]: 101.0

2.列表

Redis的列表僅能包含字符串。當(dāng)?shù)谝淮尾迦霐?shù)據(jù)的時(shí)列表被創(chuàng)建。使用函數(shù)lpush()在開始處插入:

In [1]: import redis
In [2]: conn = redis.Redis()

In [3]: conn.lpush('zoo','bear') 
Out[3]: 1

在開始處插入超過(guò)一項(xiàng):
In [4]: conn.lpush('zoo', 'alligator', 'duck') 
Out[4]: 3

使用 linsert() 函數(shù)在一個(gè)值的前或者后插入:
In [5]: conn.linsert('zoo', 'before', 'bear', 'beaver') 
Out[5]: 4

In [6]: conn.linsert('zoo', 'after', 'bear', 'cassowary') 
Out[6]: 5

使用 lset() 函數(shù)在偏移量處插入(列表必須已經(jīng)存在),但是會(huì)將原來(lái)偏移量的值覆蓋:
In [7]: conn.lset('zoo',2,'marmoset') 
Out[7]: True

使用 lrange() 函數(shù)取到給定偏移量范圍(0~-1 代表全部)的所有值:
In [8]: conn.lrange('zoo',0,-1) 
Out[8]: [b'duck', b'alligator', b'marmoset', b'bear', b'cassowary']

使用 rpush() 函數(shù)在結(jié)尾處插入:
In [9]: conn.rpush('zoo', 'yak') 
Out[9]: 6

使用 lindex() 函數(shù)取到給定偏移量處的值:
In [10]: conn.lindex('zoo', 3) 
Out[10]: b'bear'

In [11]: conn.lrange('zoo', 0, 2) 
Out[11]: [b'duck', b'alligator', b'marmoset']

使用 ltrim() 函數(shù)僅保留列表中給定范圍的值(截?。?In [12]: conn.ltrim('zoo', 1, 4) 
Out[12]: True

In [13]: conn.lrange('zoo',0,-1) 
Out[13]: [b'alligator', b'marmoset', b'bear', b'cassowary']

3.哈希表

Redis的哈希表類似于Python中的字典,但它僅包含字符串,因此只能有一層結(jié)構(gòu),不能 進(jìn)行嵌套。下面的例子創(chuàng)建了一個(gè) Redis 的哈希表 song,并對(duì)它進(jìn)行操作。

使用函數(shù)hmset()在哈希表song設(shè)置字段do和字段re的值
In [16]: conn.hmset('song',{'do': 'a deer', 're': 'about a deer'}) 
Out[16]: True

使用函數(shù)hset()設(shè)置一個(gè)單一字段值
In [17]: conn.hset('song', 'mi', 'a note to follow re') 
Out[17]: 1

使用函數(shù) hget() 取到一個(gè)字段的值:
In [18]: conn.hget('song', 'mi') 
Out[18]: b'a note to follow re'

使用函數(shù) hmget() 取到多個(gè)字段的值:
In [19]: conn.hmget('song', 're', 'do') 
Out[19]: [b'about a deer', b'a deer']

使用函數(shù) hkeys() 取到所有字段的鍵:
In [20]: conn.hkeys('song') 
Out[20]: [b're', b'do', b'mi']

使用函數(shù) hvals() 取到所有字段的值:
In [21]:  conn.hvals('song')
Out[21]: [b'about a deer', b'a deer', b'a note to follow re']

使用函數(shù) hlen() 返回字段的總數(shù):
In [22]: conn.hlen('song')
Out[22]: 3

使用函數(shù) hgetall() 取到所有字段的鍵和值:
In [23]: conn.hgetall('song') 
Out[23]: {b'do': b'a deer', b'mi': b'a note to follow re', b're': b'about a deer'}

使用函數(shù) hsetnx() 對(duì)字段中不存在的鍵賦值:
In [24]: conn.hsetnx('song', 'fa', 'a note that rhymes with la') 
Out[24]: 1

4.集合

Redis的集合和Python的集合是完全類似的。

在集合中添加一個(gè)或多個(gè)值:
In [35]: conn.sadd('zoo1','duck', 'goat', 'turkey') 
Out[35]: 3

取得集合中所有值的數(shù)目:
In [36]: conn.scard('zoo1') 
Out[36]: 3

返回集合中所有值:
In [37]: conn.smembers('zoo1') 
Out[37]: {b'duck', b'goat', b'turkey'}

從集合中刪掉一個(gè)值:
In [38]: conn.srem('zoo1','turkey') 
Out[38]: 1

新建一個(gè)集合以展示一些集合間的操作:
In [39]: conn.sadd('zoo2','tiger', 'wolf', 'duck') 
Out[39]: 3

返回集合zoo1和集合zoo2的交集
In [40]: conn.sinter('zoo1','zoo2') 
Out[40]: {b'duck'}

獲得集合zoo1和集合zoo2的交集,并存儲(chǔ)到新集合zoo3
In [41]: conn.sinterstore('zoo3','zoo1','zoo2') 
Out[41]: 1

In [42]: conn.smembers('zoo3') 
Out[42]: {b'duck'}

返回集合zoo1和集合zoo2的交集
In [43]: conn.sunion('zoo1','zoo2') 
Out[43]: {b'duck', b'goat', b'tiger', b'wolf'}

存儲(chǔ)并集結(jié)果到集合zoo4
In [44]: conn.sunionstore('zoo4','zoo1','zoo2') 
Out[44]: 4

使用函數(shù)sdiff()得到它們的差集,得到zoo1包含而zoo2不包含的項(xiàng):
In [45]: conn.sdiff('zoo1','zoo2') 
Out[45]: {b'goat'}

將差集存儲(chǔ)到集合zoo5:
In [46]: conn.sdiffstore('zoo5','zoo1','zoo2')
Out[46]: 1

In [47]: conn.smembers('zoo5') 
Out[47]: {b'goat'}

有序集合

Redis中功能最強(qiáng)大的數(shù)據(jù)類型之一是有序表(sorted set或者zset)。里面的值都是獨(dú)一無(wú)二的,但是每一個(gè)值都關(guān)聯(lián)對(duì)應(yīng)浮點(diǎn)值分?jǐn)?shù)(score)??梢酝ㄟ^(guò)值或者分?jǐn)?shù)取得每一項(xiàng)。
有序集合有很多用途:

  • 排行榜
  • 二級(jí)索引
  • 時(shí)間序列(把時(shí)間戳作為分?jǐn)?shù))

下面以時(shí)間序列作為一個(gè)栗子,通過(guò)時(shí)間戳跟蹤用戶的登陸。時(shí)間表達(dá)式使用Unix的epoch值,它有python的time()函數(shù)返回:

In [1]: import redis 
In [2]: import time
In [3]: conn = redis.Redis() 
In [4]: now = time.time() 

In [5]: now 
Out[5]: 1484735598.2283144
增加第一個(gè)訪客:
In [6]: conn.zadd('logins', 'smeagol', now) 
Out[6]: 1

五分鐘后又一個(gè)訪客:
In [7]: conn.zadd('logins','sauro',now+(5*60)) 
Out[7]: 1

兩小時(shí)后:
In [8]:  conn.zadd('logins', 'bilbo', now+(2*60*60))  
Out[8]: 1

一天后:
In [9]: conn.zadd('logins', 'treebeard', now+(24*60*60))  
Out[9]: 1

查看bilbo的登陸次序:
In [10]: conn.zrank('logins','bilbo') 
Out[10]: 2

查看登陸時(shí)間:
In [11]: conn.zscore('logins','bilbo') 
Out[11]: 1484742798.2283144

按照登陸的順序查看每一個(gè)訪客:
In [12]: conn.zrange('logins',0,-1) 
Out[12]: [b'smeagol', b'sauro', b'bilbo', b'treebeard']

附帶上登陸的時(shí)間:
In [13]: conn.zrange('logins',0,-1,withscores=True) 
Out[13]: 
[(b'smeagol', 1484735598.2283144),
 (b'sauro', 1484735898.2283144),
 (b'bilbo', 1484742798.2283144),
 (b'treebeard', 1484821998.2283144)]

6.位圖

位圖(bit)是一種非常省空間且快速的處理超大集合數(shù)字的方式。假設(shè)你有一個(gè)很多用戶 注冊(cè)的網(wǎng)站,想要跟蹤用戶的登錄頻率、在某一天用戶的訪問(wèn)量以及同一用戶在固定時(shí)間 內(nèi)的訪問(wèn)頻率,等等。當(dāng)然,你可以使用 Redis 集合,但如果使用遞增的用戶 ID,位圖的 方法更加簡(jiǎn)潔和快速。
首先為每一天創(chuàng)建一個(gè)集合(bitset)。為了測(cè)試,我們僅使用3天和部分用戶ID:

In [14]: days = ['2017-01-18','2017-01-19','2019-01-20']

In [15]: big_spender = 1089 

In [16]: tire_kicker = 40459 

In [17]: late_joiner = 550212 

每一天是一個(gè)單獨(dú)的鍵,對(duì)應(yīng)的用戶ID設(shè)置位,例如第一天(2017-01-18)有來(lái)自 big_ spender(ID 1089) 和 tire_kicker(ID 40459) 的訪問(wèn)記錄:

In [18]: conn.setbit(days[0],big_spender, 1) 
Out[18]: 0

In [19]: conn.setbit(days[0], tire_kicker, 1) 
Out[19]: 0

第二天用戶 big_spender 又有訪問(wèn):
In [20]: conn.setbit(days[1], big_spender, 1)  
Out[20]: 0

接下來(lái)的一天,big_spender 再次訪問(wèn),并又有新人 late_joiner 訪問(wèn):
In [21]: conn.setbit(days[2], big_spender, 1)  
Out[21]: 0

In [22]:  conn.setbit(days[2], late_joiner, 1)  
Out[22]: 0

現(xiàn)在統(tǒng)計(jì)得到這三天的日訪客數(shù):
In [23]: for  day in days: 
    ...:     conn.bitcount(day) 
    ...:      

判斷tire_kicker用戶在第二天是否登陸:
In [24]: conn.getbit(day[1],tire_kicker) 
Out[24]: 0
結(jié)果為未登錄

查看有多少訪客每天都訪問(wèn)?
In [25]: conn.bitop('and','everyday',*days) 
Out[25]: 68777

In [26]: conn.bitcount('everyday') 
Out[26]: 1

判斷big_spender是不是每一天都登陸:
In [27]: conn.getbit('everyday',big_spender) 
Out[27]: 1

查看三天總共有多少人登陸(不算重復(fù)項(xiàng))
In [28]: conn.bitop('or','allday',*days) 
Out[28]: 68777

In [29]: conn.bitcount('allday') 
Out[29]: 3

7.緩存和過(guò)期

所有的Redis鍵都有一個(gè)生存期或者過(guò)期時(shí)間(expiration date),默認(rèn)情況下,生存期是永久的。也可以使用expire()函數(shù)構(gòu)造Redis鍵的生存期。下面的設(shè)置是以秒為單位數(shù)的:

In [60]: ks = 'now you see ' 

In [61]: conn.set(ks, 'but not for long') 
Out[61]: True

In [62]: conn.get(ks) 
Out[62]: b'but not for long'

In [63]: conn.expire(ks, 15)  
Out[63]: True

In [64]: conn.ttl(ks) 
Out[64]: 8

In [65]: conn.get(ks) 
Out[65]: b'but not for long'

In [66]: conn.get(ks) 

expireat()命令給一個(gè)鍵設(shè)定過(guò)期時(shí)間,對(duì)于更新緩存是有幫助的,并且可以限制登陸會(huì)話。


其他的NoSQL

NoSQL 服務(wù)器都要處理遠(yuǎn)超過(guò)內(nèi)存的數(shù)據(jù),并且很多服務(wù)器要使用多臺(tái)計(jì)算機(jī)。下面 列 出了值得注意的服務(wù)器和它們的 Python 庫(kù)。

NoSQL數(shù)據(jù)庫(kù)

 Site                                        Python API 
 Cassandra(http://cassandra.apache.org/)   pycassa(https://github.com/pycassa/pycassa) 
 CouchDB(http://couchdb.apache.org/)       couchdb-python(https://github.com/djc/couchdb-python)
 HBase(http://hbase.apache.org/)           happybase(https://github.com/wbolster/happybase) 
 Kyoto Cabinet(http://fallabs.com/kyotocabinet/) kyotocabinet(http://fallabs.com/kyotocabinet/pythondoc/) 
 MongoDB(http://www.mongodb.org/)          mongodb(http://api.mongodb.org/python/current/) 
 Riak(http://basho.com/riak/)              riak-python-client(https://github.com/basho/riak-pythonclient) 

注:本文內(nèi)容來(lái)自《Python語(yǔ)言及其應(yīng)用》歡迎購(gòu)買原書閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容