淺談REDIS資料庫的索引值設計

來源:互聯網
上載者:User

標籤:

原文地址:http://www.hoterran.info/redis_kv_design

豐富的資料結構使得redis的設計非常的有趣。不像關係型資料庫那樣,DEV和DBA需要深度溝通,review每行sql語句,也不像memcached那樣,不需要DBA的參與。redis的DBA需要熟悉資料結構,並能瞭解使用情境。

下面舉一些常見適合kv資料庫的例子來談談索引值的設計,並與關係型資料庫做一個對比,發現關係型的不足之處。

使用者登入系統

記錄使用者登入資訊的一個系統, 我們簡化業務後只留下一張表。

關係型資料庫的設計
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表示該使用者的登入次數,每次使用者登入後,login_times會自增,而last_login_time更新為目前時間。

REDIS的設計

關係型資料轉化為KV資料庫,我的方法如下:

key 表名:主索引值:列名

value 列值

一般使用冒號做分割符,這是不成文的規矩。比如在php-admin for redis系統裡,就是預設以冒號分割,於是user:1 user:2等key會分成一組。於是以上的關係資料轉化成kv資料後記錄如下:

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

這樣在已知主鍵的情況下,通過get、set就可以獲得或者修改使用者的登入次數和最後登入時間和姓名。

一般使用者是無法知道自己的id的,只知道自己的使用者名稱,所以還必須有一個從name到id的映射關係,這裡的設計與上面的有所不同。

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

這樣每次使用者登入的時候商務邏輯如下(python版),r是redis對象,name是已經獲知的使用者名稱。

#獲得使用者的iduid = r.get("login:%s:id" % name)#自增使用者的登入次數ret = r.incr("login:%s:login_times" % uid)#更新該使用者的最後登入時間ret = r.set("login:%s:last_login_time" % uid, datetime.datetime.now())

如果需求僅僅是已知id,更新或者擷取某個使用者的最後登入時間,登入次數,關係型和kv資料庫無啥區別。一個通過btree pk,一個通過hash,效果都很好。

假設有如下需求,尋找最近登入的N個使用者。開發人員看看,還是比較簡單的,一個sql搞定。

select * from login order by last_login_time desc limit N

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

過了兩天,又來一個需求,需要知道登入次數最多的人是誰。同樣的關係型如何處理?DEV說簡單

select * from login order by login_times desc limit N

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

關係型資料庫的資料存放區的的不靈活是問題的源頭,資料僅有一種儲存方法,那就是按行排列的堆表。統一的資料結構意味著你必須使用索引來改變sql的訪問路徑來快速存取某個列的,而訪問路徑的增加又意味著你必須使用統計資訊來輔助,於是一大堆的問題就出現了。

沒有索引,沒有統計計劃,沒有執行計畫,這就是kv資料庫。

redis裡如何滿足以上的需求呢? 對於求最新的N條資料的需求,鏈表的後進後出的特點非常適合。我們在上面的登入代碼之後添加一段代碼,維護一個登入的鏈表,控制他的長度,使得裡面永遠儲存的是最近的N個登入使用者。

#把當前登入人添加到鏈表裡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)

另外,求登入次數最多的人,對於排序,積分榜這類需求,sorted set非常的適合,我們把使用者和登入次數統一儲存在一個sorted set裡。

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

這樣假如某個使用者登入,額外維護一個sorted set,代碼如此

#對該使用者的登入次數自增1ret = r.zincrby("login:login_times", 1, uid)

那麼如何獲得登入次數最多的使用者呢,逆序排列取的排名第N的使用者即可

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

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

TAG系統

tag在互連網應用裡尤其多見,如果以傳統的關係型資料庫來設計有點不倫不類。我們以尋找書的例子來看看redis在這方面的優勢。

關係型資料庫的設計

兩張表,一張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方面的書籍,如果以關係型資料庫會怎麼處理?
select b.name, b.author  from tag t1, tag t2, book bwhere t1.tagname = ‘web‘ and t2.tagname = ‘ruby‘ and t1.book_id = t2.book_id and b.id = t1.book_id

tag表自關聯2次再與book關聯,這個sql還是比較複雜的,如果要求即ruby,但不是web方面的書籍呢?

關係型資料其實並不太適合這些集合操作。

REDIS的設計

首先book的資料肯定要儲存的,和上面一樣。

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表我們使用集合來儲存資料,因為集合擅長求交集、並集

sadd tag:ruby 1sadd tag:ruby 2sadd tag:web 2sadd 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個例子可以看出在某些情境裡,關係型資料庫是不太適合的,你可能能夠設計出滿足需求的系統,但總是感覺的怪怪的,有種生搬硬套的感覺。

尤其登入系統這個例子,頻繁的為業務建立索引。放在一個複雜的系統裡,ddl(建立索引)有可能改變執行計畫。導致其它的sql採用不同的執行計畫,業務複雜的老系統,這個問題是很難預估的,sql千奇百怪。要求DBA對這個系統裡所有的sql都瞭解,這點太難了。這個問題在oracle裡尤其嚴重,每個DBA估計都碰到過。對於MySQL這類系統,ddl又不方便(雖然現在有online ddl的方法)。碰到大表,DBA淩晨爬起來在業務低峰期操作,這事我沒少幹過。而這種需求放到redis裡就很好處理,DBA僅僅對容量進行預估即可。

未來的OLTP系統應該是kv和關係型的緊密結合。

淺談REDIS資料庫的索引值設計

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.