国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > 綜合 > 正文

淺談Redis數(shù)據(jù)庫的鍵值設(shè)計

2024-07-21 02:51:40
字體:
供稿:網(wǎng)友

豐富的數(shù)據(jù)結(jié)構(gòu)使得redis的設(shè)計非常的有趣。不像關(guān)系型數(shù)據(jù)庫那樣,DEV和DBA需要深度溝通,review每行sql語句,也不像memcached那樣,不需要DBA的參與。redis的DBA需要熟悉數(shù)據(jù)結(jié)構(gòu),并能了解使用場景。

  下面舉一些常見適合kv數(shù)據(jù)庫的例子來談?wù)勬I值的設(shè)計,并與關(guān)系型數(shù)據(jù)庫做一個對比,發(fā)現(xiàn)關(guān)系型的不足之處。

  用戶登錄系統(tǒng)

  記錄用戶登錄信息的一個系統(tǒng), 我們簡化業(yè)務(wù)后只留下一張表。

  關(guān)系型數(shù)據(jù)庫的設(shè)計

MySQL> select * from login; +---------+----------------+-------------+---------------------+ | user_id | name           | login_times | last_login_time     | +---------+----------------+-------------+---------------------+ |       1 | ken thompson   |           5 | 2011-01-01 00:00:00 | |       2 | dennis ritchie |           1 | 2011-02-01 00:00:00 | |       3 | Joe Armstrong  |           2 | 2011-03-01 00:00:00 | +---------+----------------+-------------+---------------------+

  user_id表的主鍵,name表示用戶名,login_times表示該用戶的登錄次數(shù),每次用戶登錄后,login_times會自增,而last_login_time更新為當(dāng)前時間。

  redis的設(shè)計

  關(guān)系型數(shù)據(jù)轉(zhuǎn)化為KV數(shù)據(jù)庫,我的方法如下:

  key 表名:主鍵值:列名

  value 列值

  一般使用冒號做分割符,這是不成文的規(guī)矩。比如在php-admin for redis系統(tǒng)里,就是默認以冒號分割,于是user:1user:2等key會分成一組。于是以上的關(guān)系數(shù)據(jù)轉(zhuǎn)化成kv數(shù)據(jù)后記錄如下:

  Set login:1:login_times 5   Set login:2:login_times 1   Set login:3:login_times 2   Set login:1:last_login_time 2011-1-1   Set login:2:last_login_time 2011-2-1   Set login:3:last_login_time 2011-3-1   set login:1:name ”ken thompson“   set login:2:name “dennis ritchie”   set login:3:name ”Joe Armstrong“

  這樣在已知主鍵的情況下,通過get、set就可以獲得或者修改用戶的登錄次數(shù)和最后登錄時間和姓名。

  一般用戶是無法知道自己的id的,只知道自己的用戶名,所以還必須有一個從name到id的映射關(guān)系,這里的設(shè)計與上面的有所不同。

  set "login:ken thompson:id" 1   set "login:dennis ritchie:id" 2   set "login: Joe Armstrong:id" 3

  這樣每次用戶登錄的時候業(yè)務(wù)邏輯如下(python版),r是redis對象,name是已經(jīng)獲知的用戶名。

  #獲得用戶的id   uid = r.get("login:%s:id" % name)   #自增用戶的登錄次數(shù)   ret = r.incr("login:%s:login_times" % uid)   #更新該用戶的最后登錄時間   ret = r.set("login:%s:last_login_time" % uid, datetime.datetime.now())

  如果需求僅僅是已知id,更新或者獲取某個用戶的最后登錄時間,登錄次數(shù),關(guān)系型和kv數(shù)據(jù)庫無啥區(qū)別。一個通過btree pk,一個通過hash,效果都很好。

  假設(shè)有如下需求,查找最近登錄的N個用戶。開發(fā)人員看看,還是比較簡單的,一個sql搞定。

select * from login order by last_login_time desc limit N

  DBA了解需求后,考慮到以后表如果比較大,所以在last_login_time上建個索引。執(zhí)行計劃從索引leafblock 的最右邊開始訪問N條記錄,再回表N次,效果很好。

  過了兩天,又來一個需求,需要知道登錄次數(shù)最多的人是誰。同樣的關(guān)系型如何處理?DEV說簡單

select * from login order by login_times desc limit N

  DBA一看,又要在login_time上建立一個索引。有沒有覺得有點問題呢,表上每個字段上都有素引。

  關(guān)系型數(shù)據(jù)庫的數(shù)據(jù)存儲的的不靈活是問題的源頭,數(shù)據(jù)僅有一種儲存方法,那就是按行排列的堆表。統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)意味著你必須使用索引來改變sql的訪問路徑來快速訪問某個列的,而訪問路徑的增加又意味著你必須使用統(tǒng)計信息來輔助,于是一大堆的問題就出現(xiàn)了。

  沒有索引,沒有統(tǒng)計計劃,沒有執(zhí)行計劃,這就是kv數(shù)據(jù)庫。

  redis里如何滿足以上的需求呢? 對于求最新的N條數(shù)據(jù)的需求,鏈表的后進后出的特點非常適合。我們在上面的登錄代碼之后添加一段代碼,維護一個登錄的鏈表,控制他的長度,使得里面永遠保存的是最近的N個登錄用戶。

  #把當(dāng)前登錄人添加到鏈表里   ret = r.lpush("login:last_login_times", uid)   #保持鏈表只有N位   ret = redis.ltrim("login:last_login_times", 0, N-1)

  這樣需要獲得最新登錄人的id,如下的代碼即可

  last_login_list = r.lrange("login:last_login_times", 0, N-1)

  另外,求登錄次數(shù)最多的人,對于排序,積分榜這類需求,sorted set非常的適合,我們把用戶和登錄次數(shù)統(tǒng)一存儲在一個sorted set里。

  zadd login:login_times 5 1   zadd login:login_times 1 2   zadd login:login_times 2 3

  這樣假如某個用戶登錄,額外維護一個sortedset,代碼如此

  #對該用戶的登錄次數(shù)自增1   ret = r.zincrby("login:login_times", 1, uid)

  那么如何獲得登錄次數(shù)最多的用戶呢,逆序排列取的排名第N的用戶即可

  ret = r.zrevrange("login:login_times", 0, N-1)

  可以看出,DEV需要添加2行代碼,而DBA不需要考慮索引什么的。

  TAG系統(tǒng)

  tag在互聯(lián)網(wǎng)應(yīng)用里尤其多見,如果以傳統(tǒng)的關(guān)系型數(shù)據(jù)庫來設(shè)計有點不倫不類。我們以查找書的例子來看看redis在這方面的優(yōu)勢。

  關(guān)系型數(shù)據(jù)庫的設(shè)計

  兩張表,一張book的明細,一張tag表,表示每本的tag,一本書存在多個tag。

mysql> select * from book; +------+-------------------------------+----------------+ | id   | name                          | author         | +------+-------------------------------+----------------+ |    1 | The Ruby PRogramming Language | Mark Pilgrim   | |    1 | Ruby on rail                  | David Flanagan | |    1 | Programming Erlang            | Joe Armstrong  | +------+-------------------------------+----------------+ mysql> select * from tag; +---------+---------+ | tagname | book_id | +---------+---------+ | ruby    |       1 | | ruby    |       2 | | web     |       2 | | erlang  |       3 | +---------+---------+

  假如有如此需求,查找即是ruby又是web方面的書籍,如果以關(guān)系型數(shù)據(jù)庫會怎么處理?

  select b.name, b.author from tag t1, tag t2, book b   where t1.tagname = 'web' and t2.tagname = 'ruby' and t1.book_id = t2.book_id and b.id = t1.book_id

  tag表自關(guān)聯(lián)2次再與book關(guān)聯(lián),這個sql還是比較復(fù)雜的,如果要求即ruby,但不是web方面的書籍呢?

  關(guān)系型數(shù)據(jù)其實并不太適合這些集合操作。

  redis的設(shè)計

  首先book的數(shù)據(jù)肯定要存儲的,和上面一樣。

  set book:1:name ”The Ruby Programming Language”   Set book:2:name ”Ruby on rail”   Set book:3:name ”Programming Erlang”   set book:1:author ”Mark Pilgrim”   Set book:2:author ”David Flanagan”   Set book:3:author ”Joe Armstrong”

  tag表我們使用集合來存儲數(shù)據(jù),因為集合擅長求交集、并集

  sadd tag:ruby 1   sadd tag:ruby 2   sadd tag:web 2   sadd tag:erlang 3

  那么,即屬于ruby又屬于web的書?

  inter_list = redis.sinter("tag.web", "tag:ruby")

  即屬于ruby,但不屬于web的書?

  inter_list = redis.sdiff("tag.ruby", "tag:web")

  屬于ruby和屬于web的書的合集?

  inter_list = redis.sunion("tag.ruby", "tag:web")

  簡單到不行阿。

  從以上2個例子可以看出在某些場景里,關(guān)系型數(shù)據(jù)庫是不太適合的,你可能能夠設(shè)計出滿足需求的系統(tǒng),但總是感覺的怪怪的,有種生搬硬套的感覺。

  尤其登錄系統(tǒng)這個例子,頻繁的為業(yè)務(wù)建立索引。放在一個復(fù)雜的系統(tǒng)里,ddl(創(chuàng)建索引)有可能改變執(zhí)行計劃。導(dǎo)致其它的sql采用不同的執(zhí)行計劃,業(yè)務(wù)復(fù)雜的老系統(tǒng),這個問題是很難預(yù)估的,sql千奇百怪。要求DBA對這個系統(tǒng)里所有的sql都了解,這點太難了。這個問題在Oracle里尤其嚴重,每個DBA估計都碰到過。對于MySQL這類系統(tǒng),ddl又不方便(雖然現(xiàn)在有online ddl的方法)。碰到大表,DBA凌晨爬起來在業(yè)務(wù)低峰期操作,這事我沒少干過。而這種需求放到redis里就很好處理,DBA僅僅對容量進行預(yù)估即可。

  未來的OLTP系統(tǒng)應(yīng)該是kv和關(guān)系型的緊密結(jié)合。


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 洪洞县| 宁城县| 绵竹市| 托克逊县| 土默特左旗| 兴仁县| 扶沟县| 榆社县| 嘉禾县| 通山县| 永州市| 绥芬河市| 太仓市| 子洲县| 浦城县| 集安市| 巴塘县| 淮北市| 尼木县| 湛江市| 吴堡县| 文化| 正安县| 弥勒县| 临潭县| 塔城市| 正宁县| 百色市| 秦皇岛市| 河间市| 大邑县| 凤阳县| 庆城县| 宁德市| 平利县| 同心县| 时尚| 克拉玛依市| 施秉县| 中山市| 庆元县|