近期接觸一個框架,架構體系是java(spring boot微服務)、mysql、redis。 聽說這個套框架能支持千萬級別的訪問,具體能支持多少我也沒有詳細的測試過,先說說他這套架構是怎麼存儲的,mysql的做用是作數據永存,全部的查詢全是redis,這速度確定比直接操做mysql,畢竟不是一個級別的,以前也常常用redis,可是都是用的普通的東西,今天就詳細瞭解下redis,補充補充redis知識點。html
開始以前先了解下爲啥redis比mysql快,簡單說一下。。。。。(算了仍是別說了,若是這個都不知道,那你也不必往下看了)java
①Stringmysql
能夠是字符串,整數或者浮點數,對整個字符串或者字符串中的一部分執行操做,對整個整數或者浮點執行自增(increment)或者自減(decrement)操做。git
②list(列表)github
一個鏈表,鏈表上的每一個節點都包含了一個字符串,蟲鏈表的兩端推入或者彈出元素,根據偏移量對鏈表進行修剪(trim),讀取單個或者多個元素,根據值查找或者移除元素。面試
③set(集合)redis
包含字符串的無序收集器(unordered collection)、而且被包含的每一個字符串都是獨一無二的。添加,獲取,移除單個元素,檢查一個元素是否存在於集合中,計算交集,並集,差集,從集合裏面隨機獲取元素。算法
④hash(散列)spring
包含鍵值對無序散列表,添加,獲取,移除當鍵值對,獲取全部鍵值對。sql
⑤zset(有序集合)
字符串成員(member)與浮點數分值(score)之間的有序映射,元素的排列順序由分值的大小決定。添加,獲取,刪除單個元素,根據分值範圍(range)或者成員來獲取元素。
(特別注意:zset是有序集合,可是儘可能少用,這個速度確定不如無序的,具體緣由,本身去百度下吧)
Redis 在 2.8.9 版本添加了 HyperLogLog 結構。Redis HyperLogLog是一種使用隨機化的算法,以少許內存提供集合中惟一元素數量的近似值。HyperLogLog 能夠接受多個元素做爲輸入,並給出輸入元素的基數估算值:
基數:集合中不一樣元素的數量。
好比:
{‘a’, ‘b’, ‘c’, ‘b’, ‘a’} 的基數就是3。(注:a、b、c)
{1, 3, 5, 7, 5, 7, 8} 的基數就是5。(注:一、三、五、七、8)
估算值:算法給出的基數並非精確的,可能會比實際稍微多一些或者稍微少一些,但會控制在合理的範圍以內。
HyperLogLog 的優勢是,即便輸入元素的數量或者體積很是很是大,計算基數所需的空間老是固定的、而且是很小的。
在 Redis 裏面,每一個 HyperLogLog 鍵只須要花費 12 KB 內存,就能夠計算接近 2^64 個不一樣元素的基數。這和計算基數時,元素越多耗費內存就越多的集合造成鮮明對比。
可是,由於 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素自己,因此
HyperLogLog 不能像集合那樣,返回輸入的各個元素。
Redis3.2版本提供了GEO功能,支持存儲地理位置信息用來實現諸如搖一搖,附近位置這類依賴於地理位置信息的功能。
①geoadd:增長某個地理位置的座標;
②geopos:獲取某個地理位置的座標;
③geodist:獲取兩個地理位置的距離;
④georadius:根據給定地理位置座標獲取指定範圍內的地理位置集合;
⑤georadiusbymember:根據給定地理位置獲取指定範圍內的地理位置集合;
⑥geohash:獲取某個地理位置的geohash值。
"發佈/訂閱"在redis中,被設計的很是輕量級和簡潔,它作到了消息的「發佈」和「訂閱」的基本能力;可是還沒有提供關於消息的持久化等各類企業級的特性。
一個Redis client發佈消息,其餘多個redis client訂閱消息,發佈的消息「即發即失」,redis 不會持久保存發佈的消息;消息訂閱者也將只能獲得訂閱以後的消息,通道中此前的消息將無 從得到。
消息發佈者,即publish客戶端,無需獨佔連接,你能夠在publish消息的同時,使用同一個redis-client連接進行其餘操做(例如:INCR等) 消息訂閱者,即subscribe客戶端,須要獨佔連接,即進行subscribe期間,redis-client沒法穿插其餘操做, 此時client以阻塞的方式等待「publish端」的消息;
所以這裏subscribe端須要使用單獨的連接,甚至須要在額外的線程中使用。 Tcp默認鏈接時間固定,若是在這時間內sub端沒有接收到pub端消息,或pub端沒有消息產生,sub端的鏈接都會被強制回收, 這裏就須要使用特殊手段解決,用定時器來模擬pub和sub之間的保活機制,定時器時間不能超過TCP最大鏈接時間,具體根據機器環境來定;
一旦subscribe端斷開連接,將會失去部分消息,即連接失效期間的消息將會丟失,因此這裏就須要考慮到藉助redis的list來持久化; 若是你很是關注每一個消息,那麼你應該基於Redis作一些額外的補充工做,若是你指望訂閱是持久的,那麼以下的設計思路能夠借鑑:
1) subscribe端: 首先向一個Set集合中增長「訂閱者ID」, 此Set集合保存了「活躍訂閱」者, 訂閱者ID標記每一個惟一的訂閱者,此Set爲 "活躍訂閱者集合"
2) subcribe端開啓訂閱操做,並基於Redis建立一個以 "訂閱者ID" 爲KEY的LIST數據結構, 此LIST中存儲了全部的還沒有消費的消息,此List稱爲 "訂閱者消息隊列"
3) publish端: 每發佈一條消息以後,publish端都須要遍歷 "活躍訂閱者集合",並依次 向每一個 "訂閱者消息隊列" 尾部追加這次發佈的消息.
4) 到此爲止,咱們能夠基本保證,發佈的每一條消息,都會持久保存在每一個 "訂閱者消息隊列" 中.
5) subscribe端,每收到一個訂閱消息,在消費以後,必須刪除本身的 "訂閱者消息隊列" 頭部的一條記錄.
6) subscribe端啓動時,若是發現本身的 "訂閱者消息隊列" 有殘存記錄, 那麼將會首先消費這些記錄,而後再去訂閱.
(注意:在消費者下線的狀況下,生產的消息會丟失,得使用專業的消息隊列如rabbitmq等。)
BloomFilter(去重,https://github.com/wxisme/bloomfilter詳細的代碼例子)
RedisSearch(https://my.oschina.net/u/1858920/blog/1862825)
Redis-ML
(注這個幾個我也不是很瞭解,只是知道這幾個點)
實現需求:
首先咱們要經過Maven引入Jedis
開源組件,在pom.xml
文件加入下面的代碼:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
加鎖代碼
public class RedisTool { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 嘗試獲取分佈式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @param expireTime 超期時間 * @return 是否獲取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } }
代碼解釋:
第一個爲key,咱們使用key來當鎖,由於key是惟一的。
第二個爲value,咱們傳的是requestId,不少童鞋可能不明白,有key做爲鎖不就夠了嗎,爲何還要用到value?緣由就是咱們在上面講到可靠性時,分佈式鎖要知足第四個條件解鈴還須繫鈴人,經過給value賦值爲requestId,咱們就知道這把鎖是哪一個請求加的了,在解鎖的時候就能夠有依據。requestId可使用UUID.randomUUID().toString()
方法生成。
第三個爲nxxx,這個參數咱們填的是NX,意思是SET IF NOT EXIST,即當key不存在時,咱們進行set操做;若key已經存在,則不作任何操做;
第四個爲expx,這個參數咱們傳的是PX,意思是咱們要給這個key加一個過時的設置,具體時間由第五個參數決定。
第五個爲time,與第四個參數相呼應,表明key的過時時間。
public class RedisTool { private static final Long RELEASE_SUCCESS = 1L; /** * 釋放分佈式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @return 是否釋放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
能夠看到,咱們解鎖只須要兩行代碼就搞定了!第一行代碼,咱們寫了一個簡單的Lua腳本代碼,上一次見到這個編程語言仍是在《黑客與畫家》裏,沒想到此次竟然用上了。第二行代碼,咱們將Lua代碼傳到jedis.eval()
方法裏,並使參數KEYS[1]賦值爲lockKey,ARGV[1]賦值爲requestId。eval()方法是將Lua代碼交給Redis服務端執行。
那麼這段Lua代碼的功能是什麼呢?其實很簡單,首先獲取鎖對應的value值,檢查是否與requestId相等,若是相等則刪除鎖(解鎖)。那麼爲何要使用Lua語言來實現呢?由於要確保上述操做是原子性的。關於非原子性會帶來什麼問題,能夠閱讀【解鎖代碼-錯誤示例2】 。那麼爲何執行eval()方法能夠確保原子性,源於Redis的特性,下面是官網對eval命令的部分解釋:
簡單來講,就是在eval命令執行Lua代碼的時候,Lua代碼將被當成一個命令去執行,而且直到eval命令執行完成,Redis纔會執行其餘命令。
注:還有簡單的SETNX(能夠設置過時時間)鎖
keys查找全部符合給定模式 pattern
的 key
。
KEYS *
匹配數據庫中全部 key
。
KEYS h?llo
匹配 hello
, hallo
和 hxllo
等。
KEYS h*llo
匹配 hllo
和 heeeeello
等。
KEYS h[ae]llo
匹配 hello
和 hallo
,但不匹配 hillo
。
特殊符號用 \
隔開
KEYS 的速度很是快,但在一個大的數據庫中使用它仍然可能形成性能問題,若是你須要從一個數據集中查找特定的 key
,你最好仍是用 Redis 的集合結構(set)來代替。
可用版本:
>= 1.0.0
時間複雜度:
O(N), N
爲數據庫中 key
的數量。
返回值:
符合給定模式的 key
列表。
SCAN cursor [MATCH pattern] [COUNT count]
SCAN 命令及其相關的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用於增量地迭代(incrementally iterate)一集元素(a collection of elements):
以上列出的四個命令都支持增量式迭代, 它們每次執行都只會返回少許元素, 因此這些命令能夠用於生產環境, 而不會出現像 KEYS命令、 SMEMBERS 命令帶來的問題 —— 當 KEYS 命令被用於處理一個大的數據庫時, 又或者 SMEMBERS 命令被用於處理一個大的集合鍵時, 它們可能會阻塞服務器達數秒之久。
不過, 增量式迭代命令也不是沒有缺點的: 舉個例子, 使用 SMEMBERS 命令能夠返回集合鍵當前包含的全部元素, 可是對於 SCAN 這類增量式迭代命令來講, 由於在對鍵進行增量式迭代的過程當中, 鍵可能會被修改, 因此增量式迭代命令只能對被返回的元素提供有限的保證 (offer limited guarantees about the returned elements)。
由於 SCAN 、 SSCAN 、 HSCAN 和 ZSCAN 四個命令的工做方式都很是類似, 因此這個文檔會一併介紹這四個命令, 可是要記住:
scan 0 默認返回10條數據。
127.0.0.1:6379> scan 0
1) "81920"
2) 1) "CMD:1000004739:4"
2) "CMD:1000010475:2"
3) "CMD:380071400001208:766"
4) "CMD:1000006866:LIST"
5) "CMD:380071400001208:20415"
6) "CMD:380071400001231:21530"
7) "CMD:380071400001208:21780"
8) "CMD:7485630165:LIST"
9) "CMD:1000001545:2"
10) "CMD:380071400001231:4387"
能夠用count 參數指定返回數據量:
127.0.0.1:6379> scan 0 count 100
1) "104448"
2) 1) "CMD:1000004739:4"
2) "CMD:1000010475:2"
3) "CMD:380071400001208:766"
4) "CMD:1000006866:LIST"
5) "CMD:380071400001208:20415"
6) "CMD:380071400001231:21530"
7) "CMD:380071400001208:21780"
8) "CMD:7485630165:LIST"
9) "CMD:1000001545:2"
10) "CMD:380071400001231:4387"
......
94) "CMD:201610200062:6"
95) "CMD:VF3748211006:3"
96) "CMD:1000009121:4"
97) "CMD:380071400001231:6563"
98) "CMD:1000010252:ID"
99) "CMD:1000005261:5"
100) "SERVER:45568_0"
使用match 參數來匹配模式:
127.0.0.1:6379> scan 0 match CMD* count 100
1) "104448"
2) 1) "CMD:1000004739:4"
2) "CMD:1000010475:2"
3) "CMD:380071400001208:766"
4) "CMD:1000006866:LIST"
5) "CMD:380071400001208:20415"
6) "CMD:380071400001231:21530"
7) "CMD:380071400001208:21780"
8) "CMD:7485630165:LIST"
9) "CMD:1000001545:2"
10) "CMD:380071400001231:4387"
......
86) "CMD:201610200062:6"
87) "CMD:VF3748211006:3"
88) "CMD:1000009121:4"
89) "CMD:380071400001231:6563"
90) "CMD:1000010252:ID"
91) "CMD:1000005261:5"
最重要的是scan不會阻塞服務器,現網環境也能夠用,真方便。
①使用list結構做爲隊列,rpush生產消息,lpop消費消息。當lpop沒有消息的時候,要適當sleep一會再重試。
②list還有個指令叫blpop,在沒有消息的時候,它會阻塞住直到消息到來。
③redis如何實現延時隊列:使用sortedset,拿時間戳做爲score,消息內容做爲key調用zadd來生產消息,消費者用zrangebyscore指令獲取N秒以前的數據輪詢進行處理。
④若是大量的key過時時間設置的過於集中,到過時的那個時間點,redis可能會出現短暫的卡頓現象。通常須要在時間上加一個隨機值,使得過時時間分散一些。
⑤bgsave作鏡像全量持久化,aof作增量持久化。由於bgsave會耗費較長時間,不夠實時,在停機的時候會致使大量丟失數據,因此須要aof來配合使用。在redis實例重啓時,優先使用aof來恢復內存的狀態,若是沒有aof日誌,就會使用rdb文件來恢復。
⑥aof文件過大恢復時間過長怎麼辦?你告訴面試官,Redis會按期作aof重寫,壓縮aof文件日誌大小。
⑦Redis4.0以後有了混合持久化的功能,將bgsave的全量和aof的增量作了融合處理,這樣既保證了恢復的效率又兼顧了數據的安全性。
⑧若是忽然機器掉電會怎樣?取決於aof日誌sync屬性的配置,若是不要求性能,在每條寫指令時都sync一下磁盤,就不會丟失數據。可是在高性能的要求下每次都sync是不現實的,通常都使用定時sync,好比1s1次,這個時候最多就會丟失1s的數據。
⑨Pipeline有什麼好處,爲何要用pipeline?
能夠將屢次IO往返的時間縮減爲一次,前提是pipeline執行的指令之間沒有因果相關性。使用redis-benchmark進行壓測的時候能夠發現影響redis的QPS峯值的一個重要因素是pipeline批次指令的數目。
⑩Redis的同步機制
Redis可使用主從同步,從從同步。第一次同步時,主節點作一次bgsave,並同時將後續修改操做記錄到內存buffer,待完成後將rdb文件全量同步到複製節點,複製節點接受完成後將rdb鏡像加載到內存。加載完成後,再通知主節點將期間修改的操做記錄同步到複製節點進行重放就完成了同步過程。
⑪集羣的原理是什麼?
Redis Sentinal着眼於高可用,在master宕機時會自動將slave提高爲master,繼續提供服務。
Redis Cluster着眼於擴展性,在單個redis內存不足時,使用Cluster進行分片存儲。