Redis 概念學習及5種數據類型

前言

實際項目中使用redis比較多,本文來了解redis概念相關的內容,不聊架構。說一個前提,Redis的高效性和靈活性正是得益於對於同一個對象類型採取不同的底層結構,並在必要的時候對二者進行轉換;以及各種底層結構對內存的合理利用。所以redis的五種結構通常都有兩種實現方式。

什麼是redis

redis是一個開源的、底層使用C語言編寫的、支持網絡交互的、可基於內存也可持久化的高性能Key-Value數據庫,並且支持多語言客戶端、高可用的,似乎高大上的詞給給了它,它也是一個數據庫,不過是內存數據庫,主要解決讀寫慢的問題,在內存中讀取比磁盤快太多了,每秒讀寫次數達到千萬級別,採用單線程處理網絡請求。

key 數據類型

我們都知道redis有5個數據類型,但是key只能是字符串,爲什麼呢?我個人思考key只是表名一個標示,沒必要把數據類型弄的這麼複雜,所以不需要list、set等數據類型,哪爲什麼不用int等數字,首先字符串可以表示爲數字,另一個方面,數字太受約束,字符串就要靈活很多。所以我們一般用字符串作爲key。

  • key不要太長,儘量不要超過1024字節,這不僅消耗內存,而且會降低查找的效率;
  • key也不要太短,太短的話,key的可讀性會降低;
  • 在一個項目中,key最好使用統一的命名模式,例如user:10000:passwd。

value 數據類型

對外對數據類型一共有5種:字符串、list、set、zset、hash。這些都是value的數據類型,是redis對外的數據類型,其實底層都是二進制的字節數組(byte[]),有人嘗試把對象轉換爲二進制然後存到redis中,這裏就不研究了,地址:如何使用節點在redis中存儲二進制對象?,redis還有自己的數據結構,這些數據結構支撐來對外的數據類型,這個之後會講解。

字符串

String在redis內部存儲默認就是一個字符串,被redisObject所引用,當遇到incr,decr等操作時會轉成數值型進行計算,此時redisObject的encoding字段爲int。普通的字符串有兩種,embstr和raw。embstr應該是Redis 3.0新增的數據結構,在2.8中是沒有的。如果字符串對象的長度小於39字節,就用embstr對象。否則用傳統的raw對象。


redis 127.0.0.1:6379> set baidu http://www.baidu
OK
redis 127.0.0.1:6379> append baidu .com
(integer) 20
redis 127.0.0.1:6379> get baidu
"http://www.baidu.com"

//   數字處理

redis 127.0.0.1:6379> set visitors 0
OK
redis 127.0.0.1:6379> incr visitors
(integer) 1
redis 127.0.0.1:6379> incr visitors
(integer) 2
redis 127.0.0.1:6379> get visitors
"2"
redis 127.0.0.1:6379> incrby visitors 100
(integer) 102
redis 127.0.0.1:6379> get visitors
"102"


redis 127.0.0.1:6379> type baidu
string
redis 127.0.0.1:6379> type visitors
string


redis 127.0.0.1:6379> ttl baidu
(integer) -1
redis 127.0.0.1:6379> rename baidu baidu-site
OK
redis 127.0.0.1:6379> get baidu
(nil)
redis 127.0.0.1:6379> get baidu-site
"http://www.baidu.com"

疑問:如果incr 是一個不可以轉換爲數字的字符串會出現什麼?

  • 如果指定的key不存在,那麼在執行incr操作之前,會先將它的值設定爲0。

  • 如果指定的key中存儲的值不是字符串類型(fix:)或者存儲的字符串類型不能表示爲一個整數,
    那麼執行這個命令時服務器會返回一個錯誤(eq:(error) ERR value is not an integer or out of range)。

由於INCR等指令本身就具有原子操作的特性,所以我們完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令來實現原子計數的效果,假如,在某種場景下有3個客戶端同時讀取了mynum的值(值爲2),然後對其同時進行了加1的操作,那麼,最後mynum的值一定是5。不少網站都利用redis的這個特性來實現業務上的統計計數需求。

list

翻譯成中文叫做“列表”,redis中的lists在底層實現上並不是數組,而是鏈表,所以鏈表的優點、缺點它都有,push方法: lpush 從左邊添加數據, rpush從右邊添加數據。pop:lpop 左邊取數據, rpop右邊取數據。

//新建一個list叫做mylist,並在列表頭部插入元素"1"

127.0.0.1:6379> lpush mylist "1"  // //返回當前mylist中的元素個數

(integer) 1

127.0.0.1:6379> rpush mylist "2"  //在mylist右側插入元素"2"

(integer) 2

127.0.0.1:6379> lpush mylist "0"   //在mylist左側插入元素"0"

(integer) 3

127.0.0.1:6379> lrange mylist 0 1  //列出mylist中從編號0到編號1的元素

1) "0"

2) "1"


127.0.0.1:6379> lrange mylist 0 -1  //列出mylist中從編號0到倒數第一個元素

1) "0"

2) "1"

3) "2"

lists的應用相當廣泛,隨便舉幾個應用場景:

  • 1.我們可以利用lists來實現一個消息隊列,而且可以確保先後順序,不必像MySQL那樣還需要通過ORDER BY來進行排序。
  • 2.利用LRANGE還可以很方便的實現分頁的功能。
  • 3.在博客系統中,每片博文的評論也可以存入一個單獨的list中。

set

set 是一種無序的集合,集合中的元素沒有先後順序,可以理解爲一堆值不重複的列表。set 的內部實現是一個 value永遠爲null的哈希表,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的原因。

其實我比較奇怪爲什麼會有哈希表?


127.0.0.1:6379> sadd myset "one"   //向集合myset中加入一個新元素"one"

(integer) 1

127.0.0.1:6379> sadd myset "two"

(integer) 1

127.0.0.1:6379> smembers myset  //列出集合myset中的所有元素

1) "one"

2) "two"

127.0.0.1:6379> sismember myset "one"   //判斷元素1是否在集合myset中,返回1表示存在

(integer) 1

127.0.0.1:6379> sismember myset "three"  //判斷元素3是否在集合myset中,返回0表示不存在

(integer) 0

127.0.0.1:6379> sadd yourset "1"   //新建一個新的集合yourset

(integer) 1

127.0.0.1:6379> sadd yourset "2"

(integer) 1

127.0.0.1:6379> smembers yourset

1) "1"

2) "2"

127.0.0.1:6379> sunion myset yourset  //對兩個集合求並集

1) "1"

2) "one"

3) "2"

4) "two"

業務場景主要利用set的特點:判斷是否存在包含、兩個集合求並集、交集等。比如兩個人的共同好友數,非共同好友數。

zset:有序集合

有序集合中的每個元素都關聯一個序號(score),這便是排序的依據。很多時候,我們都將redis中的有序集合叫做zsets,這是因爲在redis中,有序集合相關的操作指令都是以z開頭的,比如zrange、zadd、zrevrange、zrangebyscore等等。Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap裏放的是成員到score的映射,而跳躍表裏存放的是所有的成員,排序依據是HashMap裏存的score,使用跳躍表的結構可以獲得比較高的查找效率,並且在實現上比較簡單。有序集合的編碼可能兩種,一種是ziplist,另一種是skiplist與dict的結合。

Redis有序集合添加、刪除和測試的時間複雜度均爲O(1)(固定時間,無論裏面包含的元素集合的數量)。列表的最大長度爲2^32- 1元素(4294967295,超過40億每個元素的集合)。

redis 127.0.0.1:6379> zadd dbs 100 redis
(integer) 1
redis 127.0.0.1:6379> zadd dbs 98 memcached
(integer) 1
redis 127.0.0.1:6379> zadd dbs 99 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd dbs 99 leveldb
(integer) 1
redis 127.0.0.1:6379> zcard dbs     //   ZCARD key 得到的有序集合成員的數量
(integer) 4
redis 127.0.0.1:6379> zcount dbs 10 99      //   ZCOUNT key min max 計算一個有序集合成員與給定值範圍內的分數
(integer) 3
redis 127.0.0.1:6379> zrank dbs leveldb      //  ZRANK key member 確定成員的索引中有序集合
(integer) 1
redis 127.0.0.1:6379> zrank dbs other
(nil)
redis 127.0.0.1:6379> zrangebyscore dbs 98 100   // ZREMRANGEBYSCORE key min max 在給定的分數之內刪除所有成員的有序集合
1) "memcached"
2) "leveldb"
3) "mongodb"
4) "redis"

應用場景:

hash

哈希是從redis-2.0.0版本之後纔有的數據結構。Redis Hash對應Value內部實際就是一個HashMap,實際這裏會有2種不同實現,這個Hash的成員比較少時Redis爲了節省內存會採用類似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht。hashes存的是字符串和字符串值之間的映射,比如一個用戶要存儲其全名、姓氏、年齡等等,就很適合使用哈希。

127.0.0.1:6379> hset person name jack
(integer) 1
127.0.0.1:6379> hset person age 20
(integer) 1
127.0.0.1:6379> hset person sex famale
(integer) 1
127.0.0.1:6379> hgetall person   //   HGETALL key 獲取對象的所有屬性域和值
1) "name"
2) "jack"
3) "age"
4) "20"
5) "sex"
6) "famale"
127.0.0.1:6379> hkeys person  //  HVALS key 獲取對象的所有屬性值
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> hvals person   / /  HVALS key 獲取對象的所有屬性值
1) "jack"
2) "20"
3) "famale"

hash 可以存儲一個對象。應用場景:hash 類型十分適合存儲對象類數據,理由如下:

  • 相對於在 string 中介紹的把對象轉化爲 json 字符串存儲,hash 的結構可以任意添加或刪除‘字段名’,更加高效靈活。
  • 主要是對對象中的字段也做了hash,通過key直接訪問對象,不需要每次訪問整個對象,減少網絡IO。

redis持久化 – 兩種方式

redis提供了兩種持久化的方式,分別是RDB(Redis DataBase)和AOF(Append Only File)。
我比較奇怪的是爲什麼還要持久化?很多時候

參考博客

Redis中key的數據類型有哪些?(不是value)
如何使用節點在redis中存儲二進制對象?
超詳細Redis入門教程
深入瞭解Redis底層數據結構
深入淺出Redis-redis底層數據結構(上)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章