現在的位置: 首頁 > 資料庫 > 正文

有哪些常見Redis資料庫鍵值的設計

2020年07月02日 資料庫 ⁄ 共 4636字 ⁄ 字型大小 評論關閉

  NoSQL帶給我們的東西很多,高性能,水平擴展性,還有不一樣的思維方式。豐富的數據結構使得redis的設計非常的有趣。不像關係型資料庫那樣,DEV和DBA需要深度溝通,review每行sql語句,也不像memcached那樣,不需要DBA的參與。redis的DBA需要熟悉數據結構,並能了解使用場景。下面學步園小編來講解下有哪些常見Redis資料庫鍵值的設計?

  有哪些常見Redis資料庫鍵值的設計

  用戶登錄系統

  記錄用戶登錄信息的一個系統,我們簡化業務後只留下一張表。

  關係型資料庫的設計

  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:3:login_times2

  Setlogin:1:last_login_time2011-1-1

  Setlogin:2:last_login_time2011-2-1

  Setlogin:3:last_login_time2011-3-1

  setlogin:1: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

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

  #獲得用戶的id

  uid=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資料庫無啥區別。一個通過btreepk,一個通過hash,效果都很好。

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

  select*fromloginorderbylast_login_timedesclimitN

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

  有哪些常見Redis資料庫鍵值的設計

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

  select*fromloginorderbylogin_timesdesclimitN

  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)

  另外,求登錄次數最多的人,對於排序,積分榜這類需求,sortedset非常的適合,我們把用戶和登錄次數統一存儲在一個sortedset里。

  zaddlogin:login_times51

  zaddlogin:login_times12

  zaddlogin:login_times23

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

  #對該用戶的登錄次數自增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|

  +------+-------------------------------+----------------+

  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:1:author」MarkPilgrim」

  Setbook:2:author」DavidFlanagan」

  Setbook:3:author」JoeArmstrong」

  tag表我們使用集合來存儲數據,因為集合擅長求交集、並集

  saddtag:ruby1

  saddtag:ruby2

  saddtag:web2

  saddtag:erlang3

  那麼,即屬於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又不方便(雖然現在有onlineddl的方法)。碰到大表,DBA凌晨爬起來在業務低峰期操作,這事我沒少干過。而這種需求放到redis里就很好處理,DBA僅僅對容量進行預估即可。

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

  以上就是關於「有哪些常見Redis資料庫鍵值的設計」的內容,希望對大家有用。更多資訊請關注學步園。學步園,您學習IT技術的優質平台!

抱歉!評論已關閉.