redis是什么?
是一種以鍵值對形式存儲的數據庫,特點是基于內存存儲,讀寫快,性能高,常用于緩存、消息隊列等應用情境
redis的五種數據類型是什么?
分別是String、Hash、List、Set和Zset(操作命令很多這里只寫部分關鍵的,其他查一查即可)
①String類型
redis中最基本的數據結構,key是String類型,value可以存儲字符串、整型或浮點數
SET <KEY> <VALUE> //寫入
GET <KEY> //讀取
②Hash類型?
在value部分提供了一個field作為value的鍵,因此一個Hash可以存儲多個字段和對應的值,適合用來存儲對象
HSET <KEY> <FIELD> <VALUE> //寫入
HGET <KEY> <FIELD> //讀取
③List類型
有序可重復,相當于LinkedList,可以從列表的兩端進行插入或刪除
LPUSH <KEY> <ELEMENT> //從列表左邊插入
LPOP <KEY> //移除左側第一個元素
右側同理,用RPUSH和RPOP
④Set類型
無序不重復
SADD <KEY> <MEMBER>... //添加一個或多個
SREM <KEY> <MEMBER>... //刪除一個或多個
SCARD <KEY> //返回元素個數
⑤Zset類型
有序集合,每個元素都帶有一個score屬性,用score來排序
ZADD <KEY> <SCORE> <MEMBER>... //添加一個或多個
ZREM <KEY> <MEMBER>... //刪除一個或多個
ZSCORE <KEY> <MEMBER> //獲取指定元素的score值
在Java中怎么使用redis?
我們會用RedisTemplate
使用方法:
①導入依賴
<!--Redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--連接池依賴-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
②在application.yml中配置redis
spring:redis:host:127.0.0.1 # Redis服務器地址port:6379 # Redis服務器連接端口password: # Redis服務器連接密碼(默認為空)lettuce:pool:max-active:8 #最大連接max-idle:8 #最大空閑連接min-idle:0 #最小空閑連接max-wait:100 #連接等待時間
③在你要的文件中注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate
?④代碼實例
redisTemplate.opsForValue.set(key,value); //存
String id = redisTemplate.opsForValue.get(key); //取
redisTemplate.delete(key); //刪除
可以在存數據的時候設置超時時間,避免因為只存出現內存不足的情況(如果不引入超時刪除,就會出現redis只存不刪的情況,時間長了內存會爆)
redisTemplate.opsForValue.set(key,value,超時時間,超時單位); //存
可以給已有的鍵值對設置過期時間
redisTemplate.expire(key,超時時間,超時單位);
提示:key可以用 : 來分隔,這樣可以清楚層級,比如 login:user:code
?
Redis作用
一、redis+session登錄校驗
二、緩存數據?
優點:降低后端負端,提高讀寫效率和性能,更好應對高并發
以前的項目中我們是直接訪問數據庫,眾所周知mysql的性能很差,那我們就可以引入redis作為緩存,當用戶發起請求時,我們可以先去redis看看有沒有對應的數據,如果有我們就直接取用,不用訪問數據庫了;如果沒有我們才去數據庫取,同時把讀取到的數據寫入redis,方便下一次取用
但是會有一個問題:如果redis或者mysql其中一個地方的數據發生更改,沒有通知另一個,就會出現redis中的數據和數據庫中的數據不一致,也就是redis和數據庫的緩存一致性問題
為了解決緩存一致性問題,我們可以使用緩存更新策略?
第一種是內存淘汰,就是redis中存太多了它會自動殺掉
第二種是超時剔除,就是前面的給緩存設置超時時間
第三種是主動更新,就是我們手動添加邏輯,一致性最好,但是也最麻煩
因此我們常常采用主動更新策略,里面又有三種
第一種是旁路緩存,也是最常用的方法:讀操作先找緩存,沒有再找數據庫,然后把數據保存到緩存;寫操作就是在更新數據庫后刪除緩存。這樣操作相對最少出問題
第二種是讀寫穿透,將緩存和數據庫整合成一個服務。調用者只需與緩存交互,讀操作緩存未命中時,服務從數據庫加載數據并寫入緩存;寫操作時,服務先更新緩存,再同步更新數據庫,但是實現難一點,而且對數據庫要求高
第三種是異步寫回,調用者只操作緩存,然后緩存再異步更新到數據庫中,有可能出現一致性問題
三、緩存穿透等問題
(1)緩存穿透
緩存穿透就是指用戶請求的數據在緩存和數據庫中都不存在(比如亂傳一個id),那么因為緩存沒有這個數據,每次請求都會打到數據庫,然后數據庫返回一個null;如果有人故意大量制造這樣的請求,就會有大量的請求直接打到數據庫,導致數據庫崩潰,這就是緩存穿透問題
解決緩存穿透有主要兩種方法:
① 緩存空對象,如果用戶請求一個在緩存和數據庫中都不存在的數據,那就緩存null值,當下一次再請求,就直接返回null。但我覺得沒什么鳥用因為只要請求一直換他就要在redis一直新增緩存直接爆炸
② 布隆過濾 在請求到達緩存前,先用布隆過濾器進行判斷,存在就放行,不存在就拒絕
其余方法有加強權限校驗,設置限流等
(2)緩存雪崩
緩存雪崩是指同一時間大量的key失效或者redis直接宕機導致大量的請求直接打到數據庫
解決方法:
① 給不同的key添加不同的TTL隨機值
② 用redis集群 主從機制 哨兵檢測 主掛了就換一個從上
③ 使用多級緩存
④ 做限流
(3)緩存擊穿
緩存擊穿是指某些被大量訪問且緩存重建復雜的熱點key失效了,導致大量的請求直接打到數據庫
解決方法:
①互斥鎖
當大量請求來到時,只讓第一個到達的線程進行緩存的重建,此時其余請求會被卡住,直到這個緩存重建完成,優點是返回的數據是準確的,缺點是性能差
對于第一個線程:獲得互斥鎖,進行緩存重建
對于后面的線程:獲取不到互斥鎖,休眠,過段時間重試
②邏輯過期
熱點key永不過期,但是要設置一個過期時間的字段(不是設置過期時間)。當請求到達時會根據過期時間的字段進行判斷,如果過期了,那就用互斥鎖,創建一個新的線程單獨負責緩存的重建,而它和其余線程直接返回當前key的數據(也就是錯誤的舊數據),優點是線程無需等待性能好,缺點是數據一致性差
對于第一個線程:判斷緩存是否過期,如果過期,獲得互斥鎖,新建一個進程進行緩存重建,自己先返回舊的數據
對于后面的線程:獲取不到互斥鎖,直接先返回舊的數據
提示:在獲取到鎖后最好再進行一次判斷
(4)超賣問題
超賣問題就是比如有一個秒殺活動,此時大量的線程發起購買請求,線程a查到庫存為1,線程b查到庫存也為1,然后線程a調用請求會讓庫存 -1,而線程b不知道(因為查到的庫存為1),所以也會讓庫存 -1,本來庫存只有1,現在卻觸發了兩次扣減庫存,讓庫存為 -1,這就是超賣問題
解決方法:加鎖
①悲觀鎖
悲觀指認為線程安全問題一定會發生。因此在操作數據之前需要先獲取鎖,讓這些線程串行執行,優點是不出錯,缺點是性能差
synchronized、lock都屬于悲觀鎖
②樂觀鎖
樂觀指認為線程安全問題不一定發生,不加鎖,只在數據進行更新操作時檢查在此期間數據是否被更改
樂觀鎖的方法通常會給數據額外引入一個標識(比如版本號或者時間戳)來標記數據是否被更改
對于一個線程來說,去更新時發現如果數據已被更改就更新失敗;否則更新成功
但是這些只在單一服務下好用,多集群就失效了,要用分布式鎖