<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
SpringBoot整合Redis實現常用功能建議大小夥們,在寫業務的時候,提前畫好流程圖,思路會清晰很多。文末有解決快取穿透和擊穿的通用工具類。
我想,登陸功能
是每個專案必備的功能吧,但是想設計好,卻是很難!下面介紹兩種登陸功能的解決方式:
功能流程:
傳送驗證碼:
使用者在提交手機號後,會校驗手機號是否合法,如果不合法,則要求使用者重新輸入手機號
如果手機號合法,後臺此時生成對應的驗證碼,同時將驗證碼進行儲存,然後再通過簡訊的方式將驗證碼傳送給使用者
簡訊驗證碼登入、註冊:
校驗登入狀態:
基於session方式實現登陸功能
,最大的缺點就是在多臺
tomcat下session無法共用,就會下出現下面問題。
核心思路分析:
每個tomcat中都有一份屬於自己的session,假設使用者第一次存取第一臺tomcat,並且把自己的資訊存放到第一臺伺服器的session中,但是第二次這個使用者存取到了第二臺tomcat,那麼在第二臺伺服器上,肯定沒有第一臺伺服器存放的session,所以此時 整個登入攔截功能就會出現問題,我們能如何解決這個問題呢?早期的方案是session拷貝
,就是說雖然每個tomcat上都有不同的session,但是每當任意一臺伺服器的session修改時,都會同步給其他的Tomcat伺服器的session,這樣的話,就可以實現session的共用了
但是這種方案具有兩個大問題
1、每臺伺服器中都有完整的一份session資料
,伺服器壓力過大。
2、session拷貝資料時,可能會出現延遲
所以咱們後來採用的方案都是基於redis來完成,我們把session換成redis,redis資料本身就是共用的,就可以避免session共用的問題了
首先我們要思考一下利用redis來儲存資料,那麼到底使用哪種結構呢?由於存入的資料比較簡單,我們可以考慮使用String,或者是使用雜湊,如下圖,如果使用String,同學們注意他的value,用多佔用一點空間,如果使用雜湊,則他的value中只會儲存他資料本身,如果不是特別在意記憶體,其實使用String就可以啦。
所以我們可以使用String結構,就是一個簡單的key,value鍵值對的方式,但是關於key的處理,session他是每個使用者都有自己的session,但是redis的key是共用的,咱們就不能使用code了
在設計這個key的時候,我們之前講過需要滿足兩點:
1、key要具有唯一性2、key要方便攜帶
如果我們採用phone
:手機號這個的資料來儲存當然是可以的,但是如果把這樣的敏感資料儲存到redis中並且從頁面中帶過來畢竟不太合適,所以我們在後臺生成一個隨機串token
,然後讓前端帶來這個token就能完成我們的整體邏輯了.
當註冊完成後,使用者去登入會去校驗使用者提交的手機號和驗證碼
,是否一致,如果一致,則根據手機號查詢使用者資訊,不存在則新建
,最後將使用者資料儲存到redis,並且生成token作為redis的key,當我們校驗使用者是否登入時,會去攜帶著token進行存取,從redis中取出token對應的value,判斷是否存在這個資料,如果沒有則攔截,如果存在則將其儲存到threadLocal中,並且放行。
快取(Cache),就是資料交換的緩衝區,俗稱的快取就是緩衝區內的資料,一般從資料庫中獲取,儲存於原生程式碼(例如:
例1:Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 本地用於高並行 例2:static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 用於redis等快取 例3:Static final Map<K,V> map = new HashMap(); 本地快取
由於其被Static修飾,所以隨著類的載入而被載入到記憶體之中,作為本地快取,由於其又被final修飾,所以其參照(例3:map)和物件(例3:new HashMap())之間的關係是固定的,不能改變,因此不用擔心賦值(=)導致快取失效;
一句話:因為速度快,好用
快取資料儲存於程式碼中,而程式碼執行在記憶體中,記憶體的讀寫效能遠高於磁碟,快取可以大大降低使用者存取並行量帶來的
伺服器讀寫壓力
實際開發過程中,企業的資料量,少則幾十萬,多則幾千萬,這麼巨量資料量,如果沒有快取來作為"避震器",系統是幾乎撐不住的,所以企業會大量運用到快取技術;
但是快取也會增加程式碼複雜度和運營的成本:
實際開發中,會構築多級快取來使系統執行速度進一步提升,例如:本地快取與redis中的快取並行使用
瀏覽器快取:主要是存在於瀏覽器端的快取
應用層快取:可以分為tomcat本地快取,比如之前提到的map,或者是使用redis作為快取
資料庫快取:在資料庫中有一片空間是 buffer pool,增改查資料都會先載入到mysql的快取中
CPU快取:當代計算機最大的問題是 cpu效能提升了,但記憶體讀寫速度沒有跟上,所以為了適應當下的情況,增加了cpu的L1,L2,L3級的快取
標準的操作方式就是查詢資料庫之前先查詢快取
,如果快取資料存在,則直接從快取中返回,如果快取資料不存在,再查詢資料庫,然後將資料存入redis
快取更新是redis為了節約記憶體而設計出來的一個東西,主要是因為記憶體資料寶貴,當我們向redis插入太多資料,此時就可能會導致快取中的資料過多,所以redis會對部分資料進行更新
,或者把他叫為淘汰更合適。
記憶體淘汰:redis自動進行,當redis記憶體達到咱們設定的max-memery的時候,會自動觸發淘汰機制
,淘汰掉一些不重要的資料(可以自己設定策略方式)
超時剔除:當我們給redis設定了過期時間ttl之後,redis會將超時的資料進行刪除,方便咱們繼續使用快取
主動更新:我們可以手動呼叫方法把快取刪掉,通常用於解決快取和資料庫不一致問題
由於我們的快取的資料來源來自於資料庫
,而資料庫的資料是會發生變化的
,因此,如果當資料庫中資料發生變化,而快取卻沒有同步
,此時就會有一致性問題存在
,其後果是:
使用者使用快取中的過時資料,就會產生類似多執行緒資料安全問題,從而影響業務,產品口碑等;怎麼解決呢?有如下幾種方案
Cache Aside Pattern 人工編碼方式:快取呼叫者在更新完資料庫後再去更新快取,也稱之為雙寫方案(一般採用
)
Read/Write Through Pattern : 由系統本身完成,資料庫與快取的問題交由系統本身去處理
Write Behind Caching Pattern :呼叫者只操作快取,其他執行緒去非同步處理資料庫,實現最終一致
綜合考慮使用方案一,但是方案一呼叫者如何處理呢?這裡有幾個問題
操作快取和資料庫時有三個問題需要考慮:
如果採用第一個方案,那麼假設我們每次運算元據庫後,都操作快取,但是中間如果沒有人查詢,那麼這個更新動作實際上只有最後一次生效,中間的更新動作意義並不大,我們可以把快取刪除,等待再次查詢時,將快取中的資料載入出來
應該具體操作快取還是運算元據庫,我們應當是先運算元據庫,再刪除快取
,原因在於,如果你選擇第一種方案,在兩個執行緒並行來存取時,假設執行緒1先來,他先把快取刪了,此時執行緒2過來,他查詢快取資料並不存在,此時他寫入快取,當他寫入快取後,執行緒1再執行更新動作時,實際上寫入的就是舊的資料,新的資料被舊資料覆蓋了。
存線上程安全問題
)快取穿透 :快取穿透是指使用者端請求的資料在快取中和資料庫中都不存在,這樣快取永遠不會生效,這些請求都會打到資料庫。
常見的解決方案有兩種:
快取空物件思路分析:當我們使用者端存取不存在的資料時,先請求redis,但是此時redis中沒有資料,此時會存取到資料庫,但是資料庫中也沒有資料,這個資料穿透了快取,直擊資料庫,我們都知道資料庫能夠承載的並行不如redis這麼高,如果大量的請求同時過來存取這種不存在的資料,這些請求就都會存取到資料庫,簡單的解決方案就是哪怕這個資料在資料庫中也不存在,我們也把這個資料存入到redis中去,這樣,下次使用者過來存取這個不存在的資料,那麼在redis中也能找到這個資料就不會進入到快取了.
布隆過濾:布隆過濾器其實採用的是雜湊思想來解決這個問題,通過一個龐大的二進位制陣列,走雜湊思想去判斷當前這個要查詢的這個資料是否存在,如果布隆過濾器判斷存在,則放行,這個請求會去存取redis,哪怕此時redis中的資料過期了,但是資料庫中一定存在這個資料,在資料庫中查詢出來這個資料後,再將其放入到redis中
假設布隆過濾器判斷這個資料不存在,則直接返回
這種方式優點在於節約記憶體空間,存在誤判,誤判原因在於:布隆過濾器走的是雜湊思想,只要雜湊思想,就可能存在雜湊衝突
小總結:
快取穿透產生的原因是什麼?
快取穿透的解決方案有哪些?
此工具類已經對快取穿透,和快取擊穿實現了通用功能。
可以對比上敘的流程圖查閱
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL; /** * @author : look-word * 2022-08-19 17:02 **/ @Component public class CacheClient { @Resource private StringRedisTemplate stringRedisTemplate; public void set(String key, Object value, Long time, TimeUnit unit) { stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit); } /** * 設定邏輯過期時間 */ public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) { // .封裝邏輯時間 RedisData redisData = new RedisData(); redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); redisData.setData(value); String redisDataJson = JSONUtil.toJsonStr(redisData); // 寫入Redis stringRedisTemplate.opsForValue().set(key, redisDataJson); } /** * 解決快取穿透 對未存在的資料 設定為null */ public <R, ID> R queryWithPassThrough (String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long cacheTime, TimeUnit cacheUnit) { // 快取key String key = keyPrefix + id; // 1 查詢快取中是否命中 String json = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(json)) { R r = JSONUtil.toBean(json, type); return r; } // 解決快取穿透 資料庫不存在的資料 快取 也不存在 惡意請求 if (json != null) { return null; } // 2 查詢資料庫 存在 存入快取 返回給前端 R r = dbFallback.apply(id); if (r == null) { // 解決快取穿透 stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); return null; } // 2.1 轉換成json 存入快取中 stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(r), cacheTime, cacheUnit); return r; } // 執行緒池 public static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10); /** * 解決快取擊穿 邏輯過期時間方式 */ public <R, ID> R queryWithLogicalExpire (String keyPrefix, ID id, Class<R> type, String lockKeyPrefix, Function<ID, R> dbFallback, Long expiredTime, TimeUnit expiredUnit) { // 快取key String key = keyPrefix + id; // 1 查詢快取中是否命中 String redisDataJson = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isBlank(redisDataJson)) { return null; } // 2.命中 檢視是否過期, // 2.1 未過期 直接返回舊資料 // 2.2 過期 獲取鎖 查詢資料寫入Redis設定新的過期時間 // 2.3 過期 未獲取鎖 返回 舊資料 RedisData redisData = JSONUtil.toBean(redisDataJson, RedisData.class); LocalDateTime expireTime = redisData.getExpireTime(); R r = JSONUtil.toBean((JSONObject) redisData.getData(), type); if (LocalDateTime.now().isBefore(expireTime)) { return r; } String lockKey = lockKeyPrefix + id; // 獲取鎖 boolean isLock = tryLock(lockKey); if (isLock) { CACHE_REBUILD_EXECUTOR.submit(() -> { try { // 查詢資料庫 R r1 = dbFallback.apply(id); // 儲存Redis 設定邏輯過期 過期時間 setWithLogicalExpire(key, r1, expiredTime, expiredUnit); } catch (Exception e) { throw new RuntimeException(e); } finally { // 釋放鎖 unlock(lockKey); } }); } // 未獲取到鎖 return r; } /** * 獲取鎖 */ public boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 100, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } /** * 釋放鎖 */ public void unlock(String key) { stringRedisTemplate.delete(key); } }
到此這篇關於SpringBoot整合Redis實現常用功能超詳細過程的文章就介紹到這了,更多相關SpringBoot整合Redis內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45