1、Redis相關網址
Redis 官網:https://redis.io/
Redis 命令參考:http://doc.redisfans.com/
2、Redis優(yōu)缺點
優(yōu)點
? ? ????1、性能極高?– Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
? ????? 2、豐富的數(shù)據類型 – Redis支持 Strings, Lists, Hashes, Sets 及 Ordered Sets 數(shù)據類型操作,以及Bitmaps、HyperLogLogs、GEO(坐標)
? ????? 3、原子 – Redis的所有操作都是原子性的,意思就是要么成功執(zhí)行要么失敗完全不執(zhí)行。單個操作是原子性的。多個操作也支持事務,即原子性,通過MULTI和EXEC指令包起來。
? ????? 4、豐富的特性?– Redis還支持 publish/subscribe, 通知, key 過期等等特性。
缺點
? ? ????1、數(shù)據庫容量受到物理內存的限制,不能用作海量數(shù)據的高性能讀寫,因此Redis適合的場景主要局限在較小數(shù)據量的高性能操作和運算上。
? ????? 2、Redis 不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的IP才能恢復。
? ? ????3、主機宕機,宕機前有部分數(shù)據未能及時同步到從機,切換IP后還會引入數(shù)據不一致的問題,降低了系統(tǒng)的可用性。
3、Redis為什么這么快
????????1、完全基于內存,絕大部分請求是純粹的內存操作,非常快速。數(shù)據存在內存中,類似于 HashMap,查找和操作的時間復雜度都是O(1);
????????2、使用多路 I/O 復用模型,非阻塞 IO;
????????3、采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,不會出現(xiàn)死鎖而導致的性能消耗;(Redis4.0之后并不是單線程,除了主線程外,它也有后臺線程在處理一些較為緩慢的操作,例如清理臟數(shù)據、無用連接的釋放、大 key 的刪除等等)
????????4、數(shù)據結構簡單,對數(shù)據操作也簡單,Redis 中的數(shù)據結構是專門進行設計的;
4、Redis主要數(shù)據類型及其應用場景
????????Redis支持的常用5種數(shù)據類型分別為:字符串String、列表List、哈希Hash、集合Set、有序集合Zset
????????Redis底層的數(shù)據結構包括:簡單動態(tài)數(shù)組SDS、鏈表、字典、跳躍鏈表、整數(shù)集合、壓縮列表(為節(jié)約內存而開發(fā)的經過特殊編碼之后的連續(xù)內存塊順序型數(shù)據結構)、對象。Redis為了平衡空間和時間效率,針對value的具體類型在底層會采用不同的數(shù)據結構來實現(xiàn),其中哈希表和壓縮列表是復用比較多的數(shù)據結構,如下圖展示了對外數(shù)據類型和底層數(shù)據結構之間的映射關系:
①string
????????String 是 Redis 最基本的類型,即一個 Key 對應一個 Value。Value 不僅可以是 String,也可以是數(shù)字。而且String 類型是二進制安全的,意思是 Redis 的 String 類型可以包含任何數(shù)據,比如 jpg 圖片或者序列化的對象。String 類型的值最大能存儲 512M。
常用命令:get、set、incr、decr、mget等。
使用場景:常規(guī)key-value緩存應用。計數(shù)器、緩存、分布式序列號、分布式鎖等等。
示例:
1、單值緩存
????????set key value
????????get key
2、對象緩存
????????1)set user:1 value(json 格式數(shù)據)
????????2)mset user:1name xxx user:1:balance 1222
????????????mget user:1:name user:1:balance
3、分布式鎖
????????setnx product:10001 true //返回1表示獲取鎖成功
????????setnx product:10001 true //返回0表示獲取鎖失敗
????????del product:10001 //釋放鎖
????????set product:10001 true ex 10 nx //防止程序意外終止導致死鎖
4、計數(shù)器(閱讀數(shù)實現(xiàn))
????????incr article:readcount:{文章id} //自增+1
????????get article:readcount:{文章id}
5、web集群session共享
????????Spring session +redis實現(xiàn)session共享?
6、分布式系統(tǒng)全局序列號
????????incrby orderId 1000? //redis 批量生成序列號提升性能
②hash
????????hash?是一個鍵值(key? value)對集合。是一個 string 類型的 field 和 value 的映射表,hash 特別適合用于存儲對象。
常用命令:hget,hset,hgetall 等。
應用場景:存儲部分變更數(shù)據,如用戶信息等,獲取/修改用戶對象某一屬性比較方便。
示例:
1、用戶對象緩存
????????hmset user {userid}:name yoyoshaly? {userid}:balance 1111
????????hmget user {userid}:name? {userid}:balance
2、電商購物車
????????1)以用戶id為key
????????2)商品id為filed
????????3)商品數(shù)量為value
????????購物車具體操作
????????1)添加商品 -》hset cart:1001 10088 1
????????2)增加購物車 -〉hincrby cart:1001 10088 1 //hincrby cart:uid pid x? x增加數(shù)量
????????3)商品總數(shù)-》hlen cart:1001
????????4)刪除商品-〉hdel cart:1001 10088
????????5)獲取購物車所有商品-》hgetall cart:1001
③list
????????List 類似是一個雙向鏈表,既可以支持反向查找和遍歷,更方便操作。
常用命令:
????????1)lpush(添加左邊元素),rpush,
????????2)lpop(移除左邊第一個元素),rpop,
????????3)lrange(獲取列表片段 LRANGE key start stop,
????????4)stack(棧實現(xiàn))LPUSH+LPOP->FILO ,
????????5)queue(隊列實現(xiàn))LPUSH+RPOP? ,
????????6)blocking mq(阻塞隊列)LPUSH+BRPOP .? //BRPOP 從key列表的表尾(right)彈出一個元素,若隊列中沒有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待。
應用場景:List 應用場景非常多,也是 Redis 最重要的數(shù)據結構之一,比如 關注列表,粉絲列表,還可以實現(xiàn)隊列。
示例:
取最新N個數(shù)據的操作
????????1)登陸用戶放入隊列 -》 lpush login yoyo
????????2) 登陸用戶放入隊列 -》lpush login lily
????????3)登陸用戶放入隊列 -》lpush login shatt
????????4) 獲取前10個登陸用戶 -》lrange login 0 10
④set
????????Set 是 String 類型的無序集合。集合是通過 hashtable 實現(xiàn)的。Set 中的元素是沒有順序的,而且是沒有重復的。而且 Set 提供了判斷某個成員是否在一個 Set 集合中。
常用命令:sdd、spop、smembers、sunion 等。
應用場景:抽獎小功能、點贊、收藏、共同好友,交集并集、差集等功能。
示例:
1、微信抽獎小程序
????????1)點擊參與抽獎加入集合
????????sadd key {userid}????
????????2)查看參與抽獎所有用戶
????????smembers key? //獲取集合key中所有元素
????????3)抽取count名中獎者
????????srandmember key [count]? //從集合key中選出count個元素,元素不從key中刪除
????????或spop key [count]? //從集合key中選出count個元素,元素從key中刪除
2、微信微博點贊,收藏,標簽
????????1)點贊
????????sadd like:{消息id} {用戶id}
????????2)取消點贊
????????srem like:{消息id} {用戶id} //從集合key中刪除元素
????????3)檢查用戶是否點過贊
????????sismember like:{消息id} {用戶id}
????????4)獲取點贊用戶列表
????????smembers like:{消息id}
????????5)獲取點贊用戶數(shù)
????????scard like:{消息id}
3、集合操作實現(xiàn)微信微博關注模型
????????set1 a,b,c
????????set2 b,c,d
????????set3 c,d,e
????????1)sinter set1 set2 set3 ->{c} 求交集
????????2)sunion set1 set2 set3 ->{a,b,c,d,e}求并集
????????3)sdiff 求差集 sdiff set1 set2 set3 ->{a} 以set1為基準 set1 -set2-set3
⑤sorted? set
????????Zset 和 Set 一樣是 String 類型元素的集合,且不允許重復的元素。當你需要一個有序的并且不重復的集合列表,那么可以選擇 Sorted Set 結構。和 Set 相比,Sorted Set關聯(lián)了一個 Double 類型權重的參數(shù) Score,使得集合中的元素能夠按照 Score 進行有序排列,Redis 正是通過分數(shù)來為集合中的成員進行從小到大的排序。Redis Sorted Set 的內部使用 HashMap 和跳躍表(skipList)來保證數(shù)據的存儲和有序,HashMap 里放的是成員到 Score 的映射。而跳躍表里存放的是所有的成員,排序依據是 HashMap 里存的 Score,使用跳躍表的結構可以獲得比較高的查找效率,并且在實現(xiàn)上比較簡單。
常用命令:zadd、zrange、zrem、zcard 等。
使用場景:Sorted Set 可以通過用戶額外提供一個優(yōu)先級(score)的參數(shù)來為成員排序,并且是插入有序的,即自動排序。
示例:
返回有序成員列表
????????1)zadd runoob 0 redis
????????2)zadd runoob 0 mongodb
????????3)zadd runoob 0 rabitmq
????????4)zadd runoob 0 rabitmq
????????5) ZRANGEBYSCORE runoob 0 1000
????????????????"mongodb"
????????????????"rabitmq"
????????????????"redis"
數(shù)據類型應用場景總結:
5、Redis線程模型
????????Redis基于Reactor模式開發(fā)了網絡事件處理器,這個處理器被稱為文件事件處理器(file event handler)。它的組成結構為4部分:多個套接字、IO多路復用程序、文件事件分派器、事件處理器。因為文件事件分派器隊列的消費是單線程的,所以Redis才叫單線程模型。
????????文件事件處理器使用 I/O 多路復用(multiplexing)程序來同時監(jiān)聽多個套接字, 并根據套接字目前執(zhí)行的任務來為套接字關聯(lián)不同的事件處理器。
????????當被監(jiān)聽的套接字準備好執(zhí)行連接應答(accept)、讀?。╮ead)、寫入(write)、關閉(close)等操作時, 與操作相對應的文件事件就會產生, 這時文件事件處理器就會調用套接字之前關聯(lián)好的事件處理器來處理這些事件。
????????雖然文件事件處理器以單線程方式運行, 但通過使用 I/O 多路復用程序來監(jiān)聽多個套接字, 文件事件處理器既實現(xiàn)了高性能的網絡通信模型, 又可以很好地與 redis 服務器中其他同樣以單線程方式運行的模塊進行對接, 這保持了 Redis 內部單線程設計的簡單性。
6、淘汰策略以及內存回收機制
????????redis作為內存型數(shù)據庫,內存不可能源源不斷的增加。那么為了安全穩(wěn)定的運行,內存的使用率一定需要保持在一定合理的閾值范圍內。合理的內存回收機制也是很重要的。內存的占用主要是鍵值對存儲的消耗,以及本身的運行消耗。我們的回收主要指的是鍵值對的回收。鍵值對可以分為幾種:帶過期的、不帶過期的、熱點數(shù)據、冷數(shù)據。對于帶過期的鍵值是需要刪除的,如果刪除了所有的過期鍵值對之后內存仍然不足怎么辦?那只能把部分數(shù)據給踢掉了。
redis的內存回收主要有:
1、過期鍵刪除
刪除方式:
? ? ? ? 1、定時刪除(主動刪除):在設置鍵的過期時間的同時,創(chuàng)建定時器,讓定時器在鍵過期時間到來時,即刻執(zhí)行鍵值對的刪除;此方式對內存使用率有優(yōu)勢,但是對CPU不友好
? ? ? ? 2、定期刪除(主動刪除):每隔特定的時間對數(shù)據庫進行一次掃描,檢測并刪除其中的過期鍵值對;此方式對內存不友好,如果某些鍵值對一直不被使用,那么會造成一定量的內存浪費
? ? ? ?3、 惰性刪除(被動刪除):鍵值對過期暫時不進行刪除,至于刪除的時機與鍵值對的使用有關,當獲取鍵時先查看其是否過期,過期就刪除,否則就保留;此方式是定時刪除和惰性刪除的折中
Reids采用的是惰性刪除和定時刪除的結合
2、內存淘汰機制
volatile-lru:從已設置過期時間的數(shù)據集中挑選最近最少使用的數(shù)據淘汰(linkedhashmap)。
volatile-ttl:從已設置過期時間的數(shù)據集中挑選將要過期的數(shù)據淘汰。
volatile-random:從已設置過期時間的數(shù)據集中任意選擇數(shù)據淘汰。
volatile-lfu:從已設置過期時間的數(shù)據集挑選使用頻率最低的數(shù)據淘汰。
allkeys-lru:從數(shù)據集中挑選最近最少使用的數(shù)據淘汰
allkeys-lfu:從數(shù)據集中挑選使用頻率最低的數(shù)據淘汰。
allkeys-random:從數(shù)據集(server.db[i].dict)中任意選擇數(shù)據淘汰
no-enviction(驅逐):禁止驅逐數(shù)據,這也是默認策略。意思是當內存不足以容納新入數(shù)據時,新寫入操作就會報錯,請求可以繼續(xù)進行,線上任務也不能持續(xù)進行,采用no-enviction策略可以保證數(shù)據不被丟失。
7、redis事務
7.1) Redis事務的三個階段
????????事務開始 MULTI
????????命令入隊
????????事務執(zhí)行 EXEC
事務執(zhí)行過程中,如果服務端收到有EXEC、DISCARD、WATCH、MULTI之外的請求,將會把請求放入隊列中排隊
7.2) Redis事務相關命令
????????Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現(xiàn)的
????????Redis會將一個事務中的所有命令序列化,然后按順序執(zhí)行。
????????redis 不支持回滾,“Redis 在事務失敗時不進行回滾,而是繼續(xù)執(zhí)行余下的命令”, 所以 Redis 的內部可以保持簡單且快速。
????????如果在一個事務中的命令出現(xiàn)錯誤,那么所有的命令都不會執(zhí)行;
????????如果在一個事務中出現(xiàn)運行錯誤,那么正確的命令會被執(zhí)行。
????????WATCH 命令是一個樂觀鎖,可以為 Redis 事務提供 check-and-set (CAS)行為??梢员O(jiān)控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之后的事務就不會執(zhí)行,監(jiān)控一直持續(xù)到EXEC命令。
????????MULTI命令用于開啟一個事務,它總是返回OK。MULTI執(zhí)行之后,客戶端可以繼續(xù)向服務器發(fā)送任意多條命令,這些命令不會立即被執(zhí)行,而是被放到一個隊列中,當EXEC命令被調用時,所有隊列中的命令才會被執(zhí)行。
????????EXEC:執(zhí)行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執(zhí)行的先后順序排列。當操作被打斷時,返回空值 nil 。
????????通過調用DISCARD,客戶端可以清空事務隊列,并放棄執(zhí)行事務, 并且客戶端會從事務狀態(tài)中退出。
????????UNWATCH命令可以取消watch對所有key的監(jiān)控。
8、Redis實現(xiàn)分布式鎖
????????Redis為單進程單線程模式,采用隊列模式將并發(fā)訪問變成串行訪問,我們可以使用setnx+lua,或者使用set key value px milliseconds nx 來實現(xiàn)分布式鎖。
使用SETNX完成同步鎖的流程及事項如下:
????????使用SETNX命令獲取鎖,若返回0(key已存在,鎖已存在)則獲取失敗,反之獲取成功
為了防止獲取鎖后程序出現(xiàn)異常,導致其他線程/進程調用SETNX命令總是返回0而進入死鎖狀態(tài),需要為該key設置一個“合理”的過期時間。
????????釋放鎖,使用DEL命令將鎖數(shù)據刪除。
如何解決 Redis 的并發(fā)競爭 Key 問題
????????所謂 Redis 的并發(fā)競爭 Key 的問題也就是多個系統(tǒng)同時對一個 key 進行操作,但是最后執(zhí)行的順序和我們期望的順序不同,這樣也就導致了結果的不同!
????????推薦一種方案:分布式鎖(zookeeper 和 redis 都可以實現(xiàn)分布式鎖)。(如果不存在 Redis 的并發(fā)競爭 Key 問題,不要使用分布式鎖,這樣會影響性能)
????????基于zookeeper臨時有序節(jié)點可以實現(xiàn)的分布式鎖。大致思想為:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節(jié)點的目錄下,生成一個唯一的瞬時有序節(jié)點。判斷是否獲取鎖的方式很簡單,只需要判斷有序節(jié)點中序號最小的一個。當釋放鎖的時候,只需將這個瞬時節(jié)點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。完成業(yè)務流程后,刪除對應的子節(jié)點釋放鎖。
????????在實踐中,當然是從以可靠性為主。所以首推Zookeeper。
redisson分布式鎖實現(xiàn)原理
????????redisson有對redlock算法的封裝。其大致的工作原理如下
9、Spring Boot 監(jiān)聽 Redis Key 失效事件實現(xiàn)定時任務
????????原理:通過監(jiān)聽 Redis 提供的過期隊列來實現(xiàn),監(jiān)聽過期隊列后,如果 Redis 中某一個 KV 鍵值對過期了,那么將向監(jiān)聽者發(fā)送消息,監(jiān)聽者可以獲取到該鍵值對的 K。因為是獲取不到 V 的,所以key的設定必須能夠精準定位。
1、開啟 Redis key 過期提醒
redis.conf
? 設置notify-keyspace-events Ex
2、引入依賴
<dependency>
? ? <groupId>org.springframework.boot</groupId>
? ? <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3、相關配置
@Configuration
public class RedisListenerConfig {
? ? @Bean
? ? RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
? ? ? ? RedisMessageListenerContainer container = new RedisMessageListenerContainer();
? ? ? ? container.setConnectionFactory(connectionFactory);
? ? ? ? return container;
? ? }
}
4、定義監(jiān)聽器 RedisKeyExpirationListener,實現(xiàn)KeyExpirationEventMessageListener?接口,
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
? ? public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
? ? ? ? super(listenerContainer);
? ? }
? ? /**
? ? * 針對 redis 數(shù)據失效事件,進行數(shù)據處理
? ? * @param message
? ? * @param pattern
? ? */
? ? @Override
? ? public void onMessage(Message message, byte[] pattern) {
? ? ? ? // 獲取到失效的 key,進行取消訂單業(yè)務處理
? ? ? ? String expiredKey = message.toString();
? ? ? ? System.out.println("redisKey過期"+expiredKey);
? ? }
5、test查看效果
@Test
public? void redisTest() throws InterruptedException {
? ? stringRedisTemplate.opsForValue().set("bike", "100", 10, TimeUnit.MILLISECONDS);
? ? Thread.sleep(10000);
}
6、效果