使用者登入系統
記錄使用者登入資訊的一個系統,我們簡化業務後只留下一張表。
關係型資料庫的設計
mysql>select*fromlogin;
--------- ------------ ---- ------------- ---------------------
|user_id|name|login_times |last_login_time|
--------- ---------------- ------------- --- ------------------
|1|kenthompson|5|2011-01-0100:00:00|
# |2| dennisritchie|1|2011-02-0100:00:00|
|3|JoeArmstrong|2|2011-03-0100:00:00|
# ------- -- ---------------- ------------- ------------------- --
user_id表的主鍵,name表示使用者名,login_times表示該使用者的登入次數,每次使用者登入後,login_times會自增,而last_login_time更新為目前時間。
REDIS的設計
關係型資料轉換為KV資料庫,我的方法如下:
key表名:主鍵值:列名
value列值
一般使用冒號做成分割符,這是不成文的規矩。例如在php-adminforredis系統裡,就是預設以冒號分割,於是user:1user:2等key會分成一組。於是以上的關聯式資料轉換成kv資料後記錄如下:
Setlogin:1:login_times5
Setlogin:2:login_times1
## Setlogin:1:last_login_time2011-1-1
Setlogin:2:last_login_time2011-2-1
Setlogin:3:last_login_2-1
Setlogin:3:last_login_time201-
Setlogin:3: :name”kenthompson「 setlogin:2:name「dennisritchie」 setlogin:3:name」JoeArmstrong「#如果已知主鍵,可以使用get和set方法取得或修改使用者的姓名、登入次數和最後登入時間。 一般用戶是無法知道自己的id的,只知道自己的用戶名,所以還必須有一個從name到id的映射關係,這裡的設計與上面的有所不同。 set"login:kenthompson:id"1 set"login:dennisritchie:id"2 set"login:JoeArmstrong:id"3# set"login:JoeArmstrong:id"3## set"login:JoeArmstrong:id"3## set"login:JoeArmstrong:id"3 這樣每次使用者登入的時候業務邏輯如下(python版),r是redis對象,name是已經獲知的使用者名稱。 #取得使用者的id uid=r.get("login:%s:id"%name) #自增使用者的登入次數The following is a possible rephrased sentence: ret = r.incr("login:%s:login_times" % uid)# #更新該使用者的最後登入時間 ret=r.set("login:%s:last_login_time "%uid,datetime.datetime.now()) 如果需求只是已知id,更新或取得某個使用者的最後登入時間,登入次數,關係型和kv資料庫無啥差異。一個透過btreepk,一個透過hash,效果都很好。 假設有下列需求,找出最近登入的N個使用者。開發人員看看,還是比較簡單的,一個sql搞定。 請執行從「login」表中選擇所有列,按「last_login_time」列進行降序排序,並限制結果集大小為N DBA了解需求後,考慮到以後表如果比較大,所以在last_login_time上建立索引。透過從索引的最右側開始存取N筆記錄,然後進行N次回表操作,執行計劃產生了顯著的效果。 有哪些常見Redis資料庫鍵值的設計 過了兩天,又來一個需求,需要知道登入次數最多的人是誰。同樣的關係型如何處理?DEV說簡單 select*fromloginorderbylogin_timesdesclimitN DBA一看,又要在login_time上建立一個索引。有沒有覺得有點問題呢,表上每個欄位都有素引。 問題來源在於關係型資料庫的資料儲存不夠靈活,資料只能使用一種按行排列的堆表方式進行儲存。統一的資料結構意味著你必須使用索引來改變sql的存取路徑來快速存取某個列的,而存取路徑的增加又意味著你必須使用統計資料來輔助,於是一大堆的問題就出現了。 沒有索引,沒有統計計劃,沒有執行計劃,這就是kv資料庫。 針對取得最新的N條資料的需求,在Redis中,鍊錶的後進先出的特性非常適合。我們在上面的登入程式碼之後會加入一段程式碼,維護一個登入的鍊錶,控制他的長度,使得裡面永遠保存的是最近的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)###### 另外,求登入次數最多的人,對於排序,積分榜這類需求,sortedset非常的適合,我們把使用者和登入次數統一儲存在一個sortedset裡。 ###
zaddlogin:login_times51
zaddlogin:login_times12
zaddlogin:login_times23
##」 #對該用戶的登入次數自增1
ret=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*frombook;
------ -------------------------- ----- ----------------
|id|name|author|
------ ---- --------------------------- ----------------
| 1|TheRubyProgrammingLanguage|MarkPilgrim|
|1|Rubyonrail|DavidFlanagan|
|1|ProgrammingErlang|JoeArmstrong|
# ------ ------ ------ 1|ProgrammingErlang|JoeArmstrong|
# ------ ------ ------ -----------------------------------------
mysql>select *fromtag;
--------- ---------
|tagname|book_id|
------ --- ---------
|ruby|1|
|ruby|2|
|web|2|
# |erlang|3|
--------- ---------
假如有如此需求,查找即是ruby又是web方面的書籍,如果以關係型資料庫會怎麼處理?
selectb.name,b.authorfromtagt1,tagt2,bookb
wheret1.tagname='web'andt2.tagname='ruby'andt1. book_id=t2.book_idandb.id=t1.book_id
tag表自關聯2次再與book關聯,這個sql還是比較複雜的,如果要求即ruby,但不是web方面的書呢?
關係型資料其實並不太適合這些集合運算。
REDIS的設計
首先book的資料肯定要儲存的,和上面一樣。
setbook:1:name”TheRubyProgrammingLanguage”
Setbook:2:name”Rubyonrail」
Setbook:3:name」ProgrammingErlang」
# Setbook:3:name」ProgrammingErlang」
#」 1:author”MarkPilgrim”
Setbook:2:author”DavidFlanagan”
Setbook:3:author”JoeArmstrong”
# tag表我們使用集合來存儲數據,因為集合擅長求交集、並集
saddtag:ruby1
saddtag:ruby2
saddtag:web2
saddtag:erlang saddtag:web2
saddtag:erlang3
##erlang ,即屬於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估計都碰到過。雖然現在有線上DDL方法,但對於像MySQL這樣的系統來說,DDL仍然不太方便。碰到大表,DBA凌晨爬起來在業務低高峰期操作,這事我沒少做。使用Redis來處理這種需求非常方便,只需要DBA預估容量即可。 ###以上是Redis資料庫常見的鍵值設計有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!