redis-基礎及常用功能

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、效果

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容