<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
執行緒A在master獲取鎖之後,master在同步資料到slave時,master突然宕機(此時資料還沒有同步到slave
),然後slave會自動選舉成為新的master,此時執行緒B獲取鎖,結果成功了,這樣會造成多個執行緒獲取同一把鎖
解決方案
RedLock
能解決分散式鎖失效的問題。對於RedLock實現原理是: 超過半數Redis節點加鎖成功之後才能算成功,否則返回false,和Zookeeper的"ZAB"原理很類似,而且與Redis Cluster叢集中解決腦裂問題的方案類似
,但是RedLock
方案有很大的弊端,也就是會造成Redis可用性的延遲,眾所周知,Redis的AP(可用性+分割區容忍性)
機制,假如把Redis變成CP(一致性+分割區容忍性)
,這樣肯定會犧牲一定的可用性,與Redis初衷不符合,也就是說還不如使用Zookeeper。CP
機制以及實現了ZAB
,能夠確保某一個節點宕機,也能保證資料一致性,而且效率會比Redis高很多,更適合做分散式鎖問題:
在高並行場景下,一定要把所有的快取資料一直儲存在快取不讓其失效嗎?
雖然一直快取所有資料沒什麼大問題,但是考慮到如果資料太多,就會一直佔用快取空間(記憶體資源非常寶貴
),並且資料的維護性也是需要耗時的.
解決方案
冷熱分離
。在查詢資料時,我們只需要在查詢程式碼中再次更新過期時間
,這樣就能保證熱點資料一直在快取中,而不經常存取的資料過期了就自動從快取中刪除。流程分析
DCL(雙重檢測鎖)
問題:
冷門資料突然變成了熱門資料,大量的請求突發性的對熱點資料進行快取重建
導致系統壓力暴增
解決方案
加鎖
DCL機制。
先查一次,快取有資料就直接返回,沒有資料,就加鎖,在鎖的程式碼塊中再次先查詢快取。這樣鎖的目的就是為了當第一次快取從資料庫查詢更新到快取中,程式碼塊執行完,其他執行緒再次進來,此時快取中就已經存在資料了,這樣就減少了查詢資料庫的次數public Product get(Long productId) { Product product = null; String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId; //DCL機制:第一次先從快取裡查資料 product = getProductFromCache(productCacheKey); if (product != null) { return product; } //加分散式鎖解決熱點快取並行重建問題 RLock hotCreateCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX + productId); hotCreateCacheLock.lock(); // 這個優化謹慎使用,防止超時導致的大規模並行重建問題 // hotCreateCacheLock.tryLock(1, TimeUnit.SECONDS); try { //DCL機制:在分散式鎖裡面第二次查詢 product = getProductFromCache(productCacheKey); if (product != null) { return product; } //RLock productUpdateLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId); RReadWriteLock productUpdateLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId); RLock rLock = productUpdateLock.readLock(); //加分散式讀鎖解決快取雙寫不一致問題 rLock.lock(); try { product = productDao.get(productId); if (product != null) { redisUtil.set(productCacheKey, JSON.toJSONString(product), genProductCacheTimeout(), TimeUnit.SECONDS); } else { //設定空快取解決快取穿透問題 redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS); } } finally { rLock.unlock(); } } finally { hotCreateCacheLock.unlock(); } return product; }
問題:
假如當前有10w個執行緒沒有拿到鎖正在排隊,這種情況只能等到獲取鎖的執行緒執行完程式碼釋放鎖後,那排隊的10w個執行緒才能再次競爭鎖。這裡需要關注的問題點就是又要再次競爭鎖,意味著執行緒競爭鎖的次數可能最少>1
,頻繁的競爭鎖對Redis效能也是有消耗的,有沒有更好的辦法讓每個執行緒競爭鎖的次數儘可能減少呢?
解決方案
可以通過tryLock(time,TimeUnit)
先讓所有執行緒嘗試獲取鎖
假如獲取鎖的執行緒執行資料庫查詢然後將資料更新到快取所需要的時間為1s,那麼當其他執行緒獲取鎖時間結束後,會解除阻塞狀態直接往下執行,然後再次查詢快取的時候發現快取有資料了就直接返回。
這樣設計的好處就是把分散式鎖在某些特定的場景使其"序列變並行",
不過這個優化需要謹慎使用,防止超時導致的大規模並行重建問題。畢竟沒有任何方案是完全解決問題的,主要是根據公司業務而定.
快取擊穿/快取失效:
可能同一時間熱點資料全部過期而造成快取查不到資料,請求就會從資料庫查詢,高並行情況下會導致資料庫壓力
解決方案
程式碼案例
private Integer genProductCacheTimeout() { //加隨機超時機制解決快取批次失效(擊穿)問題 return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60; }
快取穿透:
如果駭客通過指令碼檔案不停的傳一些不存在的引數刷網站的介面,而這種垃圾引數在快取和資料庫又不存在,這樣就會一直地查資料庫,最終可能導致資料庫並行量過大而卡死宕機。
解決方案
閘道器限流。
Nginx、Sentinel、Hystrix都可以實現程式碼層面。
可以使用多級快取,比如一級快取採用布隆過濾器,二級快取可以使用guava中的Cache,三級快取使用Redis,為什麼一級快取使用布隆過濾器呢,其結構和bitmap
類似,用於儲存資料狀態,能存大量的key布隆過濾器
問題:
這種場景可能是在某個時刻把冷門商品一下子變成了熱門商品。因為冷門的資料可能在快取時間過期就刪除,而此時剛好有大量請求,比如直播期間推播一個商品連線,假如同時有幾十萬人搶購,
而快取沒有的話,意味著所有的請求全部達到了資料庫中查詢,而對於資料庫單節點支撐並行量也就不到1w,此時這麼大的請求量,肯定會把資料庫整宕機(這種場景比較少,但是小概率還是會有
)
解決方案
可以通過tryLock(time,TimeUnit)
先讓所有執行緒嘗試獲取鎖
假如獲取鎖的執行緒執行資料庫查詢然後將資料更新到快取所需要的時間為1s,那麼當其他執行緒獲取鎖時間結束後,會解除阻塞狀態直接往下執行,然後再次查詢快取的時候發現快取有資料了就直接返回。
這樣設計的好處就是把分散式鎖在某些特定的場景使其"序列變並行",
不過這個優化需要謹慎使用,防止超時導致的大規模並行重建問題。畢竟沒有任何方案是完全解決問題的,主要是根據公司業務而定.
解決方案
重入鎖
保證並行安全。通常說在分散式鎖中再加一把鎖,鎖太重,效能不是很好,還有優化空間分散式讀寫鎖(ReadWriteLock)
,實現機制和ReentranReadWriteLock一直,適合讀多寫少的場景
,注意讀寫鎖的key得一致使用canal通過監聽binlog紀錄檔及時去修改快取
,但是引入中介軟體,增加系統的維護度Lua指令碼設定讀寫鎖
local mode = redis.call('hget', KEYS[1], 'mode'); if (mode == false) then redis.call('hset', KEYS[1], 'mode', 'read'); redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('set', KEYS[2] .. ':1', 1); redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); local key = KEYS[2] .. ':' .. ind; redis.call('set', key, 1); redis.call('pexpire', key, ARGV[1]); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);
ReadWriteLock程式碼案例
@Transactional public Product update(Product product) { Product productResult = null; //RLock productUpdateLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId()); RReadWriteLock productUpdateLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId()); // 新增寫鎖 RLock writeLock = productUpdateLock.writeLock(); //加分散式寫鎖解決快取雙寫不一致問題 writeLock.lock(); try { productResult = productDao.update(product); redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult), genProductCacheTimeout(), TimeUnit.SECONDS); } finally { writeLock.unlock(); } return productResult; } public Product get(Long productId) { Product product = null; String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId; //從快取裡查資料 product = getProductFromCache(productCacheKey); if (product != null) { return product; } //加分散式鎖解決熱點快取並行重建問題 RLock hotCreateCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX + productId); hotCreateCacheLock.lock(); // 這個優化謹慎使用,防止超時導致的大規模並行重建問題 // hotCreateCacheLock.tryLock(1, TimeUnit.SECONDS); try { product = getProductFromCache(productCacheKey); if (product != null) { return product; } //RLock productUpdateLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId); RReadWriteLock productUpdateLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId); // 新增讀鎖 RLock rLock = productUpdateLock.readLock(); //加分散式讀鎖解決快取雙寫不一致問題 rLock.lock(); try { product = productDao.get(productId); if (product != null) { redisUtil.set(productCacheKey, JSON.toJSONString(product), genProductCacheTimeout(), TimeUnit.SECONDS); } else { //設定空快取解決快取穿透問題 redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS); } } finally { rLock.unlock(); } } finally { hotCreateCacheLock.unlock(); } return product; }
解決方案
分段鎖
,和JDK7的ConcurrentHashMap的實現原理很類似,將一個鎖,分成多個鎖,比如lock,分成lock_1、lock_2...快取雪崩:
快取支撐不住或者宕機,然後大量請求湧入資料庫。
解決方案
閘道器限流。
Nginx、Sentinel、Hystrix都可以實現程式碼層面。
可以使用多級快取,比如一級快取採用布隆過濾器,二級快取可以使用guava中的Cache,三級快取使用Redis,為什麼一級快取使用布隆過濾器呢,其結構和bitmap
類似,用於儲存資料狀態,能存大量的key問題: 比如微博上某一天某個明星事件成為了熱點新聞,此時很多吃瓜群眾全部湧入這個熱點,如果並行每秒達到幾十萬甚至上百萬的並行量,但是Redis伺服器單節點只能支撐並行10w而已,那麼可能因為這麼高的並行量導致很多請求卡死在那,要知道我們其他業務服務也會用到Redis,一旦Redis卡死,就會影響到其他業務,導致整個業務癱瘓,這就是典型的快取雪崩
問題
解決方案
: 參考場景10
解決方案
場景10
的方案去實現,需要考慮資料一致性問題,這樣就不得不每次對資料進行增加、刪除、更新都要立馬通知其他節點更新資料,能做到及時更新資料的方案可能就是:Redis釋出/訂閱、MQ等
熱點快取系統
來維護,所有的web應用只需要監聽這個系統,只要有熱點時,直接更新快取,這樣既能減少程式碼耦合,還能更好的維護熱點資料。那麼熱點資料來源怎麼獲取呢?
可以在設計查詢的介面使用類似於Spring AOP的方式,每次查詢就把資料傳送到熱點資料,一般大廠都會有資料分析崗位,根據熱點規則將資料分類到此這篇關於淺談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