Redis 的由來
Redis(全稱:Remote Dictionary Server 遠程字典服務)是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。從2010年3月15日起,Redis的開發工作由VMware主持。從2013年5月開始,Redis的開發由Pivotal贊助。
Redis 非關係型數據庫,是互聯網技術領域最爲廣泛使用的存儲中間件 是 Remote Dictionary Service 的縮寫, 超高的性能,完美的文檔,和簡潔的源碼和豐富的(多語言)客戶端庫支持,在開源中間件領域受好評,還有目前發行版本是單線程 所以是安全的所以減少了,許多需要考慮的地方。
使用的公司:
本人在國內Top3的支付公司 也是使用的 Redis,但是redis 也有許多的漏洞,需要運維同學注意。
一、爲什麼使用 Redis
-
解決應用服務器的cpu和內存壓力
-
減少io的讀操作,減輕io的壓力
-
關係型數據庫的擴展性不強,難以改變表結構
二、優點:
-
nosql數據庫沒有關聯關係,數據結構簡單,拓展表比較容易
-
nosql讀取速度快,對較大數據處理快
三、適用場景:
-
數據高併發的讀寫
-
海量數據的讀寫
-
對擴展性要求高的數據
-
防止刷接口
-
秒殺
-
字典值
-
控制開關
-
臨時數據
-
計算附近的距離
-
記錄點贊數 (可以增加和減少)
-
快速顯示用戶的列表
-
緩存用戶的歷史數據
-
...... 還有許多,根據業務場景可以不同的擴展
四、不適場景:
-
需要事務支持(非關係型數據庫)
-
基於sql結構化查詢儲存,關係複雜
-
單線程的能力有限,不能同時處理大量的數據的任務 (好像是redis 6版本中加入了多線程)
安裝redis
環境centos7
百度雲
$ wget http://download.redis.io/releases/redis-5.0.7.tar.gz
$ tar xzf redis-5.0.7.tar.gz
$ cd redis-5.0.7
$ make
$ wget http://download.redis.io/releases/redis-5.0.7.tar.gz
內容有這些文件
- 第一步下載
- 解壓
- 進入目標文件進行make 這裏注意一下 我的這個服務器是新的什麼內容都沒有這有一個報錯
- 4步驟中沒有出現可以執行6,有出現4的情況執行5 下載安裝 gcc :直接通過命令: 原因redis 是c語言寫的 編譯需要使用到gcc
-
yum install gcc-c++ -y
- 進入文件夾 執行命令 make 有可能會有報錯
- 如果有7這樣的錯誤執行 8,同理沒有執行9 make MALLOC=libc
- 修改 redis.conf 文件 改爲yes
daemonize yes
- 注意版本不同進行啓動的地方也不一樣 我的是最新的 5.0.7 版本 redis-server 是在 src / 下的
- 啓動命令
./redis-server ./redis.conf &
- 啓動好的土星是這樣的
Redis 的安裝還是非常的簡單的。
後期有時間寫一篇博客來具體說一下 redis 的集羣是如何搭建的
Redis 數據結構:
redis 有5種數據結構 分別是 String(字符串),List(集合),set(集合),zset(有序集合),hash(字典)是redis 的基礎,也是最重要的地方。
Redis 的所有的數據結構都是這樣的一個唯一的key作爲名稱,通過這個key來獲取對應的數據(value) 不同的是value的數據結構是不一樣的。
String(字符串) :String 是redis 中最簡單的,字符串結構非常廣泛,一般保存用戶的信息,商品的信息,配置信息,key是設定的+id號, value是通過數據庫的信息在json 轉換一下保存到redis中來緩存,(注意DO對象需要序列化,通過實現Serializable)在通過redis 反序列化讀取出來。Redis 的字符串是動態字符串,是可以進行修改的字符串,內部的實現類似java的ArrayList,採取的是預分配空間和動態擴容,目的是爲了減少內存的頻繁的分配。擴容的原理是小於1MBdouble空間,大於1MB加1MB,注意最大空間爲512M。
舉例通過命令的增加 key就是name 對應的value是xuxiaoguan 這是簡單的 一個key value的加入
127.0.0.1:6379> set name xuxiaoguan
OK
127.0.0.1:6379> get name
"xuxiaoguan"
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
現在是批量的加入數據 模板格式 mset mget
127.0.0.1:6379> mset name1 boy1 name2 boy2
OK
127.0.0.1:6379> mget name1 name2
1) "boy1"
2) "boy2"
127.0.0.1:6379>
對鍵值對進行設置過期時間 這樣利用空間,過期自動進行刪除,一般用來控制緩存的過期的時間
127.0.0.1:6379> set name xuxiaoguan
OK
127.0.0.1:6379> get name
"xuxiaoguan"
// 這裏開始設置過期的時間爲3秒需要指定的key
127.0.0.1:6379> expire name 3
(integer) 1
127.0.0.1:6379> get name
(nil)
計數
value 是一個整數,可以進行自增的,但是有範圍的,(signed long的最大值和最小值之間)超過了會報錯的
127.0.0.1:6379> set SetAgeValue 60
OK
127.0.0.1:6379> incr SetAgeValue
(integer) 61
127.0.0.1:6379> incrby SetAgeValue 9
(integer) 70
127.0.0.1:6379> incrby SetAgeValue -20
(integer) 50
List (列表)
Redis 的list和Java的Arraylist 差不多,這裏的list 是鏈表(java是數組)插入和刪除的速度非常快時間複雜度是o(1) ,但是查找和指定插入就會浪費時間o(n),這會讓許多人感到意外。 list使用的是雙向指針。 列表中沒有了元素了,會進行自動的刪除,節省空間。
redis 的隊列結構通常用來做異步對列,先進先出,將需要處理的結構體序列化後放入對列中,另外一個線程進行讀取(消費)
127.0.0.1:6379> rpush book python java go
(integer) 3
127.0.0.1:6379> llen book
(integer) 3
127.0.0.1:6379> lpop book
"python"
127.0.0.1:6379> lpop book
"java"
127.0.0.1:6379> lpop book
"go"
127.0.0.1:6379> lpop book
(nil)
數據結構中有一種類型是 棧:格式是先進後出的,和隊列是向反的
127.0.0.1:6379> rpush book python java go
(integer) 3
127.0.0.1:6379> rpop book
"go"
127.0.0.1:6379> rpop book
"java"
127.0.0.1:6379> rpop book
"python"
127.0.0.1:6379> rpop book
(nil)
Redis list 其實還不像linkedList 往下看 更是一個zipList ,將所有的數據雙向指針精密連接儲存的,分配的是一塊連續的內存。當數據量比較大的時候改爲quickList, 所以說Redis的List是zipList+quickList組成的。 quickList 滿足了快速的插入和刪除的,
hash(字典)
Redis 的字典和java(1.7版本以前)的hashmap區別不大的,都是數組+鏈表,hash碰撞的使用鏈表串起來的。
Redis和java中的區別點:
- java 中的key是可以任意類型支持自定義的,但是redis hash的key只可以是String類型的。
- java 中的rehash 當hash很小或很大時,都是一次性的rehash 會很耗時,但是Redis因爲是單線程的,爲了高性成的,爲了低耗時,不阻塞,所以採用了,漸進式rehash。
漸進式rehash:同時分配出新的空間出來,保持原有的,新加入的數據指向新的hash中,(查詢的時候也會指向這兩個hash),後續通過定時任務來合併這兩個hash,一點點的遷移,這裏不是用的單線程,使用的是子線程。數據遷移完成後,會自動的刪除原來的hash,指向新的hash。
hash 可以保存數據類型挺多的,比如用戶的信息,不同的字段對應不同的值。獲取某一個值可以直接獲取,不需要像String一樣全部的獲取到,浪費網絡的流量。
但是hash也是有缺點的:hash 的結構存儲高於單個字符串,所以我們在開發中使用hash還是String 我們自己需要衡量一下。
127.0.0.1:6379> hset javaSet java "think in java"
(integer) 1
127.0.0.1:6379> hset javaSet python CodePython
(integer) 1
127.0.0.1:6379> hset javaSet go CodeGo
(integer) 1
127.0.0.1:6379> hgetall javaSet ## 注意這裏key 和 value是隔行展示的
1) "java"
2) "think in java"
3) "python"
4) "CodePython"
5) "go"
6) "CodeGo"
127.0.0.1:6379> hlen javaSet
(integer) 3
127.0.0.1:6379> hget javaSet go
"CodeGo"
127.0.0.1:6379> hset javaSet go "happy code"
(integer) 0
127.0.0.1:6379> hget javaSet go
"happy code"
hash 的操作命令有這些
Set(集合)
Redis 的set和 java中的hashSet 區別不大的,內部的鍵值對都是無序的。不同的是Redis 的set對應的value值都是Null。
當集合中最後一個元素被移除完,數據結構被自動的刪除,內存收回。
127.0.0.1:6379> sadd books java
(integer) 1
127.0.0.1:6379> sadd books go
(integer) 1
127.0.0.1:6379> sadd books python
(integer) 1
127.0.0.1:6379> smembers books
1) "go"
2) "python"
3) "java"
## 從這裏就可以看的出來set
127.0.0.1:6379> scard books ## 數據的長度
(integer) 3
127.0.0.1:6379> spop books ## 彈出一個數據
"go"
127.0.0.1:6379> sismember books java ## 數據中是否包含
(integer) 1
127.0.0.1:6379> sismember books python
(integer) 1
127.0.0.1:6379> sismember books gos
(integer) 0
Zset(有序列表)
類似java中的SortedSet 和 HashMap 的結合體。
- 保證了內部value 的唯一性,同時還給每個value 加入了Score,代表這個value 的權重(我個人認爲和優先級的定義差不多的)
- zset最後一個數據被刪除,數據結構被刪除,內存會自動的收回。
- zset可以用來保存用戶的收藏更具時間的順序加入,對應文章#活動#商品的id號。
- 可以保存學生的數據比如學生的分數可以直接進行排序。
127.0.0.1:6379> zadd javaBook 9.0 "think code in java One"
(integer) 1
127.0.0.1:6379> zadd javaBook 8.9 "think code in java Two"
(integer) 1
127.0.0.1:6379> zadd javaBook 8.6 "think code in java Three"
(integer) 1
127.0.0.1:6379> zrange javaBook 0 -1 ## 正序的展示的 從小到大
1) "think code in java Three"
2) "think code in java Two"
3) "think code in java One"
127.0.0.1:6379> zrevrange javaBook 0 -1 ## 倒敘的展示 從大到小
1) "think code in java One"
2) "think code in java Two"
3) "think code in java Three"
Zset的內部使用的是 “跳躍列表”來實現的,比較特殊所以也就比較複雜。 可以和世界地圖差不多的,世界->中國->江蘇->南通->某市->某街道->門牌號 是一個道理的。 應爲這些元素可能身間數職。
定位插入數據的時候,先從頂層定位,然後潛伏到下一級,一直到最合適的層級然後將數據插入進去。
而Redis的索引被提取爲多層。如圖:
所有的元素都會在L0層的鏈表中,根據分數進行排序,同時會有一部分節點有機會被抽取到L1層中,作爲一個稀疏索引,同樣L1層中的索引也有一定機會被抽取到L2層中,組成一個更稀疏的索引列表。
下面用圖來演示一下在對快速鏈表進行插入、刪除、查詢時,是如何定位到L0層中的具體位置的。
首先,假定有這麼一個鏈表,注意這裏只展示分數,而不展示具體的值了:
如果要查找分數爲66的元素,首先在L2層的索引找。很明顯,66位於25和85中間,這時就縮小了查找區間:
然後根據獲得的區間,去L1對應的區間中查找,得到一個更精確的區間:
最終,根據這個更精確的區間,去L0層順序遍歷,即可得到要查找的元素:
上述即是對Redis的跳躍表的原理的一個簡述。
這種跳躍表的實現,其實和二分查找的思路有點接近,只是一方面因爲二分查找只能適用於數組,而無法適用於鏈表,所以爲了讓鏈表有二分查找類似的效率,就以空間換時間來達到目的。
跳躍表因爲是一個根據分數權重進行排序的列表,可以再很多場景中進行應用,比如排行榜,搜索排序等等。
容器型數據結構: list hash set zset 都是容器型數據結構,都遵守2大規定。
- create if not exists 如果容器不在,創建一個,在進行操作。
- 數據結構中沒有數據,會自動刪除數據,釋放內存
過期時間
Redis 的所有數據結構都可以進行設置過期時間的,
注意點
- hash 需要注意到了過期時間自動刪除的,不是某一個子key 的刪除。
- 如果進行設置了值同時設置了過期時間,後面進行修改了原來的過期時間是作廢了。