Redis鍵值設計
豐富的資料結構使得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 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就可以獲得或者修改使用者的登入次數和最後登入時間和姓名。
一般使用者是無法知道自己的id的,只知道自己的使用者名稱,所以還必須有一個從name到id的對映關係,這裡的設計與上面的有所不同。
set "login:ken thompson:id" 1 set "login:dennis ritchie:id" 2 set "login: Joe Armstrong:id" 3
這樣每次使用者登入的時候業務邏輯如下(python版),r是redis物件,name是已經獲知的使用者名稱。
1 |
#獲得使用者的id |
2 |
uid = r.get( "login:%s:id" % name) |
3 |
#自增使用者的登入次數 |
4 |
ret = r.incr( "login:%s:login_times" % uid) |
5 |
#更新該使用者的最後登入時間 |
6 |
ret = r. set ( "login:%s:last_login_time" % uid,
datetime.datetime.now()) |
如果需求僅僅是已知id,更新或者獲取某個使用者的最後登入時間,登入次數,關係型和kv資料庫無啥區別。一個通過btree pk,一個通過hash,效果都很好。
假設有如下需求,查詢最近登入的N個使用者。開發人員看看,還是比較簡單的,一個sql搞定。
1 |
select * from login order by last_login_time desc limit
N |
DBA瞭解需求後,考慮到以後表如果比較大,所以在last_login_time上建個索引。執行計劃從索引leafblock 的最右邊開始訪問N條記錄,再回表N次,效果很好。
過了兩天,又來一個需求,需要知道登入次數最多的人是誰。同樣的關係型如何處理?DEV說簡單
1 |
select * from login order by login_times desc limit
N |
DBA一看,又要在login_time上建立一個索引。有沒有覺得有點問題呢,表上每個欄位上都有素引。
關係型資料庫的資料儲存的的不靈活是問題的源頭,資料僅有一種儲存方法,那就是按行排列的堆表。統一的資料結構意味著你必須使用索引來改變sql的訪問路徑來快速訪問某個列的,而訪問路徑的增加又意味著你必須使用統計資訊來輔助,於是一大堆的問題就出現了。
沒有索引,沒有統計計劃,沒有執行計劃,這就是kv資料庫。
redis裡如何滿足以上的需求呢? 對於求最新的N條資料的需求,連結串列的後進後出的特點非常適合。我們在上面的登入程式碼之後新增一段程式碼,維護一個登入的連結串列,控制他的長度,使得裡面永遠儲存的是最近的N個登入使用者。
1 |
#把當前登入人新增到連結串列裡 |
2 |
ret = r.lpush( "login:last_login_times" ,
uid) |
3 |
#保持連結串列只有N位 |
4 |
ret = redis.ltrim( "login:last_login_times" , 0 ,
N - 1 ) |
這樣需要獲得最新登入人的id,如下的程式碼即可
1 |
last_login_list = r.lrange( "login:last_login_times" , 0 ,
N - 1 ) |
另外,求登入次數最多的人,對於排序,積分榜這類需求,sorted set非常的適合,我們把使用者和登入次數統一儲存在一個sorted set裡。
zadd login:login_times 5 1 zadd login:login_times 1 2 zadd login:login_times 2 3
這樣假如某個使用者登入,額外維護一個sorted set,程式碼如此
1 |
#對該使用者的登入次數自增1 |
2 |
ret = r.zincrby( "login:login_times" , 1 ,
uid) |
那麼如何獲得登入次數最多的使用者呢,逆序排列取的排名第N的使用者即可
1 |
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方面的書籍,如果以關係型資料庫會怎麼處理?
1 |
select b. name ,
b.author from tag
t1, tag t2, book b |
2 |
where 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 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個例子可以看出在某些場景裡,關係型資料庫是不太適合的,你可能能夠設計出滿足需求的系統,但總是感覺的怪怪的,有種生搬硬套的感覺。
尤其登入系統這個例子,頻繁的為業務建立索引。放在一個複雜的系統裡,ddl(建立索引)有可能改變執行計劃。導致其它的sql採用不同的執行計 劃,業務複雜的老系統,這個問題是很難預估的,sql千奇百怪。要求DBA對這個系統裡所有的sql都瞭解,這點太難了。這個問題在oracle裡尤其嚴 重,每個DBA估計都碰到過。對於MySQL這類系統,ddl又不方便(雖然現在有online ddl的方法)。碰到大表,DBA凌晨爬起來在業務低峰期操作,這事我沒少幹過。而這種需求放到redis裡就很好處理,DBA僅僅對容量進行預估即可。
未來的OLTP系統應該是kv和關係型的緊密結合。
相關文章
- redis自學(46)鍵值設計Redis
- 【系統設計】分散式鍵值資料庫分散式資料庫
- 實現鍵值對儲存(四):API設計API
- 已計算的關鍵值和限制的關鍵值
- redis安全性很高,但是如何訪問redis中其他專案的鍵值?Redis
- Redis key 設計技巧Redis
- php redis 集合sadd同時寫入多個鍵值的方法PHPRedis
- XamarinEssentials教程移除鍵值首選項的鍵值
- greenplum分佈鍵的hash值計算分析
- Redis的持久化設計Redis持久化
- Redis設計與實現Redis
- 《redis設計與實現》Redis
- Redis命令——鍵(key)Redis
- Redis的鍵(key)Redis
- Redis鍵遷移Redis
- Redis | 第7章 Redis 伺服器《Redis設計與實現》Redis伺服器
- iOS 之鍵值編碼(KVC)與鍵值監聽(KVO)iOS
- 資料庫模型設計——主鍵的設計資料庫模型
- Redis系列(六)-SortedSets設計技巧Redis
- 探索Redis設計與實現9:資料庫redisDb與鍵過期刪除策略Redis資料庫
- Redis鍵空間通知Redis
- XamarinEssentials教程清空鍵值
- 程式設計師的鍵盤程式設計師
- Redis Scan演算法設計思想Redis演算法
- Redis設計於實現之字典Redis
- Redis 設計與實現 4:字典Redis
- Redis 設計與實現 (九)--LuaRedis
- Redis 實戰 —— 14. Redis 的 Lua 指令碼程式設計Redis指令碼程式設計
- Redis | 第12章 Sentinel 哨兵模式《Redis設計與實現》Redis模式
- Redis學習筆記(三)redis 的鍵管理Redis筆記
- 程式設計師真正的價值程式設計師
- Shell 程式設計 : 數值,字元,字串程式設計字元字串
- Python 設定 session 或 cookie 增、刪、改、查鍵值PythonSessionCookie
- redis設計統計使用者訪問量Redis
- Redis | 第5章 Redis 中的持久化技術《Redis設計與實現》Redis持久化
- 【redis】使用redis RedisAtomicLong生成自增的ID值Redis
- 對設計與設計師“價值”的一些思考
- MapReduce程式設計基礎(二)——數值概要(計算最大值、最小值、平均值)程式設計