<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
快取有啥用?
快取有啥缺點?
上圖可以清晰的瞭解Redis在專案中所處的位置,是資料庫與使用者端之間的一箇中介軟體,也是資料庫的保護傘。有了Redis可以幫助資料庫進行請求的阻擋,阻止請求直接打入資料庫,提高響應速率,極大的提升了系統的穩定性。
下面將根據查詢商鋪資訊來作為背景進行程式碼書寫,具體的流程圖如上所示。
public static final String SHOPCACHEPREFIX = "cache:shop:"; @Autowired private StringRedisTemplate stringRedisTemplate; // JSON工具 ObjectMapper objectMapper = new ObjectMapper(); @Override public Result queryById(Long id) { //從Redis查詢商鋪快取 String cacheShop = stringRedisTemplate.opsForValue().get(SHOPCACHEPREFIX + id); //判斷快取中資料是否存在 if (!StringUtil.isNullOrEmpty(cacheShop)) { //快取中存在則直接返回 try { // 將子字串轉換為物件 Shop shop = objectMapper.readValue(cacheShop, Shop.class); return Result.ok(shop); } catch (JsonProcessingException e) { e.printStackTrace(); } } //快取中不存在,則從資料庫裡進行資料查詢 Shop shop = getById(id); //資料庫裡不存在,返回404 if (null==shop){ return Result.fail("資訊不存在"); } //資料庫裡存在,則將資訊寫入Redis try { String shopJSon = objectMapper.writeValueAsString(shop); stringRedisTemplate.opsForValue().set(SHOPCACHEPREFIX+id,shopJSon,30,TimeUnit.MINUTES); } catch (JsonProcessingException e) { e.printStackTrace(); } //返回 return Result.ok(shop); }
資料庫與快取資料一致性問題,當資料庫資訊修改後,快取的資訊應該如何處理?
記憶體淘汰 | 超時剔除 | 主動更新 | |
---|---|---|---|
說明 | 不需要自己進行維護,利用Redis的淘汰機制進行資料淘汰 | 給快取資料新增TTL | 編寫業務邏輯,在修改資料庫的同時更新快取 |
一致性 | 差勁 | 一般 | 好 |
維護成本 | 無 | 低 | 高 |
這裡其實是需要根據業務場景來進行選擇
此時需要實現資料庫與快取一致性問題,在這個問題之中還有多個問題值得深思
刪除快取還是更新快取?
當資料庫發生變化時,我們如何處理快取中無效的資料,是刪除它還是更新它?
更新快取:每次更新資料庫都更新快取,無效寫操作較多
刪除快取:更新資料庫時刪除快取,查詢時再新增快取
由此可見,選擇刪除快取是高效的。
如何保證快取與資料庫的操作的同時成功或失敗?
單體架構:單體架構中採用事務解決
分散式架構:利用分散式方案進行解決
先刪除快取還是先運算元據庫?
在並行情況下,上述情況是極大可能會發生的,這樣子會導致快取與資料庫資料庫不一致。
先運算元據庫,在操作快取這種情況,在快取資料TTL剛好過期時,出現一個A執行緒查詢快取,由於快取中沒有資料,則向資料庫中查詢,在這期間內有另一個B執行緒進行資料庫更新操作和刪除快取操作,當B的操作在A的兩個操作間完成時,也會導致資料庫與快取資料不一致問題。
完蛋!!!兩種方案都會造成資料庫與快取一致性問題的發生,那麼應該如何來進行選擇呢?
雖然兩者方案都會造成問題的發生,但是概率上來說還是先運算元據庫,再刪除快取發生問題的概率低一些,所以可以選擇先運算元據庫,再刪除快取的方案。
個人見解:
如果說我們在先運算元據庫,再刪除快取方案中執行緒B刪除快取時,我們利用java來刪除快取會有Boolean返回值,如果是false,則說明快取已經不存在了,快取不存在了,則會出現上圖的情況,那麼我們是否可以根據刪除快取的Boolean值來進行判斷是否需要執行緒B來進行快取的新增(因為之前是需要查詢的執行緒來新增快取,這裡考慮執行緒B來新增快取,執行緒B是運算元據庫的快取),如果執行緒B的新增也線上程A的寫入快取之前完成也會造成資料庫與快取的一致性問題發生。那麼是否可以延時一段時間(例如5s,10s)再進行資料的新增,這樣子雖然最終會統一資料庫與快取的一致性,但是若是在這5s,10s內又有執行緒C,D等等來進行快取的存取呢?C,D執行緒的存取還是存取到了無效的快取資訊。
所以在資料庫與快取的一致性問題上,除非在寫入正確快取之前拒絕相關請求進行伺服器來進行存取才能避免使用者存取到錯誤資訊,但是拒絕請求對使用者來說是致命的,極大可能會導致使用者直接放棄使用應用,所以我們只能儘可能的減少問題可能性的發生。(個人理解,有問題可以在評論區留言賜教)
@Override @Transactional public Result updateShop(Shop shop) { Long id = shop.getId(); if (null==id){ return Result.fail("店鋪id不能為空"); } //更新資料庫 boolean b = updateById(shop); //刪除快取 stringRedisTemplate.delete(SHOPCACHEPREFIX+shop.getId()); return Result.ok(); }
快取穿透是指使用者端請求的資料在快取中和資料庫中都不存在,這樣快取永遠不會生效,這些請求都會打到資料庫。
解決方案:
快取空物件
缺點:
布隆過濾器
優點:
記憶體佔用少
缺點:
public static final String SHOPCACHEPREFIX = "cache:shop:"; @Autowired private StringRedisTemplate stringRedisTemplate; // JSON工具 ObjectMapper objectMapper = new ObjectMapper(); @Override public Result queryById(Long id) { //從Redis查詢商鋪快取 String cacheShop = stringRedisTemplate.opsForValue().get(SHOPCACHEPREFIX + id); //判斷快取中資料是否存在 if (!StringUtil.isNullOrEmpty(cacheShop)) { //快取中存在則直接返回 try { // 將子字串轉換為物件 Shop shop = objectMapper.readValue(cacheShop, Shop.class); return Result.ok(shop); } catch (JsonProcessingException e) { e.printStackTrace(); } } // 因為上面判斷了cacheShop是否為空,如果進到這個方法裡面則一定是空,直接過濾,不打到資料庫 if (null != cacheShop){ return Result.fail("資訊不存在"); } //快取中不存在,則從資料庫裡進行資料查詢 Shop shop = getById(id); //資料庫裡不存在,返回404 if (null==shop){ // 快取空物件 stringRedisTemplate.opsForValue().set(SHOPCACHEPREFIX+id,"",2,TimeUnit.MINUTES); return Result.fail("資訊不存在"); } //資料庫裡存在,則將資訊寫入Redis try { String shopJSon = objectMapper.writeValueAsString(shop); stringRedisTemplate.opsForValue().set(SHOPCACHEPREFIX+id,shopJSon,30,TimeUnit.MINUTES); } catch (JsonProcessingException e) { e.printStackTrace(); } //返回 return Result.ok(shop); }
上述方案終究是被動方案,我們可以採取一些主動方案,例如
快取雪崩是指在同一時段大量的快取key同時失效或者Redis服務宕機,導致大量請求到達資料庫,帶來巨大壓力。
解決方案:
快取擊穿問題也叫熱點Key問題,就是一個被高並行存取並且快取重建業務較複雜的key突然失效了,無數的請求存取會在瞬間給資料庫帶來巨大的衝擊。
常見的解決方案:
互斥鎖:
即採用鎖的方式來保證只有一個執行緒去重建快取資料,其餘拿不到鎖的執行緒休眠一段時間再重新重頭去執行查詢快取的步驟
優點:
缺點:
邏輯過期:
邏輯過期是在快取資料中額外新增一個屬性,這個屬性就是邏輯過期的屬性,為什麼要使用這個來判斷是否過期而不使用TTL呢?因為使用TTL的話,一旦過期,就獲取不到快取中的資料了,沒有拿到鎖的執行緒就沒有舊的資料可以返回。
它與互斥鎖最大的區別就是沒有執行緒的等待了,誰先獲取到鎖就去重建快取,其餘執行緒沒有獲取到鎖就返回舊資料,不去做休眠,輪詢去獲取鎖。
重建快取會新開一個執行緒去執行重建快取,目的是減少搶到鎖的執行緒的響應時間。
優點:
執行緒無需等待,效能好
缺點:
兩個方案各有優缺點:一個保證了一致性,一個保證了可用性,選擇與否主要看業務的需求是什麼,側重於可用性還是一致性。
互斥鎖的鎖用什麼?
使用Redis命令的setnx命令。
首先實現獲取鎖和釋放鎖的程式碼
/** * 嘗試獲取鎖 * * @param key * @return */ private boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } /** * 刪除鎖 * * @param key */ private void unLock(String key) { stringRedisTemplate.delete(key); }
程式碼實現
public Shop queryWithMutex(Long id) throws InterruptedException { //從Redis查詢商鋪快取 String cacheShop = stringRedisTemplate.opsForValue().get(SHOPCACHEPREFIX + id); //判斷快取中資料是否存在 if (!StringUtil.isNullOrEmpty(cacheShop)) { //快取中存在則直接返回 try { // 將子字串轉換為物件 Shop shop = objectMapper.readValue(cacheShop, Shop.class); return shop; } catch (JsonProcessingException e) { e.printStackTrace(); } } // 因為上面判斷了cacheShop是否為空,如果進到這個方法裡面則一定是空,直接過濾,不打到資料庫 if (null != cacheShop) { return null; } Shop shop = new Shop(); // 快取擊穿,獲取鎖 String lockKey = "lock:shop:" + id; try{ boolean b = tryLock(lockKey); if (!b) { // 獲取鎖失敗了 Thread.sleep(50); return queryWithMutex(id); } //快取中不存在,則從資料庫裡進行資料查詢 shop = getById(id); //資料庫裡不存在,返回404 if (null == shop) { // 快取空物件 stringRedisTemplate.opsForValue().set(SHOPCACHEPREFIX + id, "", 2, TimeUnit.MINUTES); return null; } //資料庫裡存在,則將資訊寫入Redis try { String shopJSon = objectMapper.writeValueAsString(shop); stringRedisTemplate.opsForValue().set(SHOPCACHEPREFIX + id, shopJSon, 30, TimeUnit.MINUTES); } catch (JsonProcessingException e) { e.printStackTrace(); } }catch (Exception e){ }finally { // 釋放互斥鎖 unLock(lockKey); } //返回 return shop; }
邏輯過期不設定TTL
程式碼實現
@Data public class RedisData { private LocalDateTime expireTime; private Object data; }
由於是熱點key,所以key基本都是手動匯入到快取,程式碼如下
/** * 邏輯過期時間物件寫入快取 * @param id * @param expireSeconds */ public void saveShopToRedis(Long id,Long expireSeconds){ // 查詢店鋪資料 Shop shop = getById(id); // 封裝為邏輯過期 RedisData redisData = new RedisData(); redisData.setData(shop); redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds)); // 寫入Redis stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id, JSONUtil.toJsonStr(redisData)); }
邏輯過期程式碼實現
/** * 快取擊穿:邏輯過期解決 * @param id * @return * @throws InterruptedException */ public Shop queryWithPassLogicalExpire(Long id) throws InterruptedException { //1. 從Redis查詢商鋪快取 String cacheShop = stringRedisTemplate.opsForValue().get(SHOPCACHEPREFIX + id); //2. 判斷快取中資料是否存在 if (StringUtil.isNullOrEmpty(cacheShop)) { // 3. 不存在 return null; } // 4. 存在,判斷是否過期 RedisData redisData = JSONUtil.toBean(cacheShop, RedisData.class); JSONObject jsonObject = (JSONObject) redisData.getData(); Shop shop = JSONUtil.toBean(jsonObject, Shop.class); LocalDateTime expireTime = redisData.getExpireTime(); // 5. 判斷是否過期 if (expireTime.isAfter(LocalDateTime.now())){ // 5.1 未過期 return shop; } // 5.2 已過期 String lockKey = "lock:shop:"+id; boolean flag = tryLock(lockKey); if (flag){ // TODO 獲取鎖成功,開啟獨立執行緒,實現快取重建,建議使用執行緒池去做 CACHE_REBUILD_EXECUTOR.submit(()->{ try { // 重建快取 this.saveShopToRedis(id,1800L); }catch (Exception e){ }finally { // 釋放鎖 unLock(lockKey); } }); } // 獲取鎖失敗,返回過期的資訊 return shop; } /** * 執行緒池 */ private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
到此這篇關於利用Redis進行資料快取的專案實踐的文章就介紹到這了,更多相關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