redis筆記02實(shí)戰(zhàn)篇講義02企業(yè)_第1頁
redis筆記02實(shí)戰(zhàn)篇講義02企業(yè)_第2頁
redis筆記02實(shí)戰(zhàn)篇講義02企業(yè)_第3頁
redis筆記02實(shí)戰(zhàn)篇講義02企業(yè)_第4頁
redis筆記02實(shí)戰(zhàn)篇講義02企業(yè)_第5頁
已閱讀5頁,還剩202頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

Redis企業(yè)實(shí)戰(zhàn)Redis的企業(yè)應(yīng)用案例今日課程介紹商戶查詢緩存企業(yè)的緩存使用技巧緩存雪崩、穿透等問題解決優(yōu)惠券秒殺Redis的計(jì)數(shù)器、Lua腳本Redis分布式鎖Redis的三種消息隊(duì)列附近的商戶Redis的GeoHash的應(yīng)用達(dá)人探店基于List的點(diǎn)贊列表基于SortedSet的點(diǎn)贊排行榜好友關(guān)注用戶簽到Redis的BitMap數(shù)據(jù)統(tǒng)計(jì)功能基于Set集合的關(guān)注、取關(guān)、共同關(guān)注、消息推送等功能黑馬點(diǎn)評RedisUV統(tǒng)計(jì)Redis的HyperLogLog的統(tǒng)計(jì)功能短信登錄Redis的共享session應(yīng)用短信登錄商戶查詢緩存優(yōu)惠券秒殺達(dá)人探店好友關(guān)注附近的商戶用戶簽到UV統(tǒng)計(jì)短信登錄01導(dǎo)入黑馬點(diǎn)評項(xiàng)目基于Session實(shí)現(xiàn)登錄集群的session共享問題基于Redis實(shí)現(xiàn)共享session登錄首先,導(dǎo)入課前資料提供的SQL文件:其中的表有:tb_user:用戶表tb_user_info:用戶詳情表tb_shop:商戶信息表tb_shop_type:商戶類型表tb_blog:用戶日記表(達(dá)人探店日記)tb_follow:用戶關(guān)注表tb_voucher:優(yōu)惠券表tb_voucher_order:優(yōu)惠券的訂單表導(dǎo)入黑馬點(diǎn)評項(xiàng)目注意Mysql的版本采用5.7及以上版本導(dǎo)入黑馬點(diǎn)評項(xiàng)目Redis集群MySQL集群tomcattomcattomcat在資料中提供了一個(gè)項(xiàng)目源碼:將其復(fù)制到你的idea工作空間,然后利用idea打開即可:啟動(dòng)項(xiàng)目后,在瀏覽器訪問:http://localhost:8081/shop-type/list

,如果可以看到數(shù)據(jù)則證明運(yùn)行沒有問題導(dǎo)入后端項(xiàng)目注意不要忘了修改application.yaml文件中的mysql、redis地址信息在資料中提供了一個(gè)nginx文件夾:將其復(fù)制到任意目錄,要確保該目錄不包含中文、特殊字符和空格,例如:導(dǎo)入前端項(xiàng)目在nginx所在目錄下打開一個(gè)CMD窗口,輸入命令:打開chrome瀏覽器,在空白頁面點(diǎn)擊鼠標(biāo)右鍵,選擇檢查,即可打開開發(fā)者工具:然后打開手機(jī)模式:然后訪問::8080

,即可看到頁面:運(yùn)行前端項(xiàng)目startnginx.exe導(dǎo)入黑馬點(diǎn)評項(xiàng)目基于Session實(shí)現(xiàn)登錄集群的session共享問題基于Redis實(shí)現(xiàn)共享session登錄短信驗(yàn)證碼登錄、注冊發(fā)送短信驗(yàn)證碼基于Session實(shí)現(xiàn)登錄開始提交手機(jī)號(hào)生成驗(yàn)證碼校驗(yàn)手機(jī)號(hào)不符合符合保存驗(yàn)證碼到session發(fā)送驗(yàn)證碼結(jié)束開始提交手機(jī)號(hào)和驗(yàn)證碼校驗(yàn)驗(yàn)證碼不一致一致保存用戶到session結(jié)束用戶是否存在根據(jù)手機(jī)號(hào)查詢用戶存在創(chuàng)建新用戶保存用戶到數(shù)據(jù)庫不存在校驗(yàn)登錄狀態(tài)開始請求并攜帶cookie保存用戶到ThreadLocal判斷用戶是否存在沒有有放行結(jié)束從session獲取用戶攔截發(fā)送短信驗(yàn)證碼說明請求方式POST請求路徑/user/code請求參數(shù)phone,電話號(hào)碼返回值無短信驗(yàn)證碼登錄說明請求方式POST請求路徑/user/login請求參數(shù)phone:電話號(hào)碼;code:驗(yàn)證碼返回值無登錄驗(yàn)證功能校驗(yàn)登錄狀態(tài)開始請求并攜帶cookie判斷用戶是否存在沒有有返回用戶結(jié)束從session獲取用戶攔截登錄驗(yàn)證功能校驗(yàn)登錄狀態(tài)開始請求并攜帶cookie判斷用戶是否存在沒有有返回用戶結(jié)束從session獲取用戶攔截黑馬點(diǎn)評服務(wù)OrderControllerUserControllerXxxController攔截器登錄驗(yàn)證功能黑馬點(diǎn)評服務(wù)OrderControllerUserControllerXxxController攔截器校驗(yàn)登錄狀態(tài)開始請求并攜帶cookie保存用戶到ThreadLocal判斷用戶是否存在沒有有放行結(jié)束從session獲取用戶攔截導(dǎo)入黑馬點(diǎn)評項(xiàng)目基于Session實(shí)現(xiàn)登錄集群的session共享問題基于Redis實(shí)現(xiàn)共享session登錄keyvalue集群的session共享問題Redis集群MySQL集群tomcattomcattomcatsessioncode:9527user:lisisessionsessionsession共享問題:多臺(tái)Tomcat并不共享session存儲(chǔ)空間,當(dāng)請求切換到不同tomcat服務(wù)時(shí)導(dǎo)致數(shù)據(jù)丟失的問題。session的替代方案應(yīng)該滿足:數(shù)據(jù)共享內(nèi)存存儲(chǔ)key、value結(jié)構(gòu)導(dǎo)入黑馬點(diǎn)評項(xiàng)目基于Session實(shí)現(xiàn)登錄集群的session共享問題基于Redis實(shí)現(xiàn)共享session登錄基于Redis實(shí)現(xiàn)共享session登錄短信驗(yàn)證碼登錄、注冊發(fā)送短信驗(yàn)證碼開始提交手機(jī)號(hào)生成驗(yàn)證碼校驗(yàn)手機(jī)號(hào)不符合符合保存驗(yàn)證碼到發(fā)送驗(yàn)證碼結(jié)束開始提交手機(jī)號(hào)和驗(yàn)證碼校驗(yàn)驗(yàn)證碼不一致一致保存用戶到結(jié)束用戶是否存在根據(jù)手機(jī)號(hào)查詢用戶存在創(chuàng)建新用戶保存用戶到數(shù)據(jù)庫不存在sessionRediskeyvalueRedisphone:138384114389527以手機(jī)號(hào)為key存儲(chǔ)驗(yàn)證碼以手機(jī)號(hào)為key讀取驗(yàn)證碼sessionRedis以隨機(jī)token為key存儲(chǔ)用戶數(shù)據(jù)token:fadfjklfweo{name:lisi}基于Redis實(shí)現(xiàn)共享session登錄校驗(yàn)登錄狀態(tài)開始請求并攜帶保存用戶到ThreadLocal判斷用戶是否存在沒有有放行結(jié)束從

獲取用戶攔截cookieToken以隨機(jī)token為key獲取用戶數(shù)據(jù)sessionRedis短信驗(yàn)證碼登錄、注冊開始提交手機(jī)號(hào)和驗(yàn)證碼校驗(yàn)驗(yàn)證碼不一致一致保存用戶到結(jié)束用戶是否存在根據(jù)手機(jī)號(hào)查詢用戶存在創(chuàng)建新用戶保存用戶到數(shù)據(jù)庫不存在結(jié)束keyvalueRedisphone:138384114389527以手機(jī)號(hào)為key讀取驗(yàn)證碼Redis以隨機(jī)token為key存儲(chǔ)用戶數(shù)據(jù)token:fadfjklfweo{name:lisi}返回token給客戶端基于Redis實(shí)現(xiàn)共享session登錄保存登錄的用戶信息,可以使用String結(jié)構(gòu),以JSON字符串來保存,比較直觀:Hash結(jié)構(gòu)可以將對象中的每個(gè)字段獨(dú)立存儲(chǔ),可以針對單個(gè)字段做CRUD,并且內(nèi)存占用更少:KEYVALUEheima:user:1{name:"Jack",age:21}heima:user:2{name:"Rose",age:18}KEYVALUEfieldvalueheima:user:1nameJackage21heima:user:2nameRoseage18Redis代替session需要考慮的問題:選擇合適的數(shù)據(jù)結(jié)構(gòu)選擇合適的key選擇合適的存儲(chǔ)粒度登錄攔截器的優(yōu)化黑馬點(diǎn)評服務(wù)OrderControllerUserControllerXxxController攔截器攔截:需要登錄的路徑獲取token查詢Redis的用戶不存在,則攔截存在,則繼續(xù)保存到ThreadLocal刷新token有效期放行登錄攔截器的優(yōu)化黑馬點(diǎn)評服務(wù)OrderControllerUserControllerXxxController攔截器攔截:需要登錄的路徑不存在,則攔截存在,則繼續(xù)攔截器攔截:一切路徑獲取token查詢Redis的用戶保存到ThreadLocal刷新token有效期放行查詢ThreadLocal的用戶:商戶查詢緩存02什么是緩存添加Redis緩存緩存更新策略緩存穿透緩存雪崩緩存擊穿緩存工具封裝緩存就是數(shù)據(jù)交換的緩沖區(qū)(稱作Cache[k??]),是存貯數(shù)據(jù)的臨時(shí)地方,一般讀寫性能較高。什么是緩存瀏覽器緩存應(yīng)用層緩存數(shù)據(jù)庫緩存CPU緩存磁盤緩存瀏覽器tomcat數(shù)據(jù)庫緩存的作用緩存的成本什么是緩存緩存的作用緩存的成本降低后端負(fù)載提高讀寫效率,降低響應(yīng)時(shí)間數(shù)據(jù)一致性成本代碼維護(hù)成本運(yùn)維成本緩存就是數(shù)據(jù)交換的緩沖區(qū)(稱作Cache[k??]),是存貯數(shù)據(jù)的臨時(shí)地方,一般讀寫性能較高。什么是緩存添加Redis緩存緩存更新策略緩存穿透緩存雪崩緩存擊穿緩存工具封裝緩存作用模型Redis添加Redis緩存客戶端數(shù)據(jù)庫請求返回緩存作用模型添加Redis緩存根據(jù)id查詢商鋪緩存的流程開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis客戶端數(shù)據(jù)庫Redis請求命中未命中寫緩存存在不存在給店鋪類型查詢業(yè)務(wù)添加緩存店鋪類型在首頁和其它多個(gè)頁面都會(huì)用到,如圖:需求:修改ShopTypeController中的queryTypeList方法,添加查詢緩存什么是緩存添加Redis緩存緩存更新策略緩存穿透緩存雪崩緩存擊穿緩存工具封裝緩存更新策略內(nèi)存淘汰超時(shí)剔除主動(dòng)更新說明不用自己維護(hù),利用Redis的內(nèi)存淘汰機(jī)制,當(dāng)內(nèi)存不足時(shí)自動(dòng)淘汰部分?jǐn)?shù)據(jù)。下次查詢時(shí)更新緩存。給緩存數(shù)據(jù)添加TTL時(shí)間,到期后自動(dòng)刪除緩存。下次查詢時(shí)更新緩存。編寫業(yè)務(wù)邏輯,在修改數(shù)據(jù)庫的同時(shí),更新緩存。一致性差一般好維護(hù)成本無低高業(yè)務(wù)場景:低一致性需求:使用內(nèi)存淘汰機(jī)制。例如店鋪類型的查詢緩存高一致性需求:主動(dòng)更新,并以超時(shí)剔除作為兜底方案。例如店鋪詳情查詢的緩存由緩存的調(diào)用者,在更新數(shù)據(jù)庫的同時(shí)更新緩存CacheAsidePattern01緩存與數(shù)據(jù)庫整合為一個(gè)服務(wù),由服務(wù)來維護(hù)一致性。調(diào)用者調(diào)用該服務(wù),無需關(guān)心緩存一致性問題。Read/WriteThroughPattern02調(diào)用者只操作緩存,由其它線程異步的將緩存數(shù)據(jù)持久化到數(shù)據(jù)庫,保證最終一致。WriteBehindCachingPattern03主動(dòng)更新策略CacheAsidePattern主動(dòng)更新策略操作緩存和數(shù)據(jù)庫時(shí)有三個(gè)問題需要考慮:刪除緩存還是更新緩存?更新緩存:每次更新數(shù)據(jù)庫都更新緩存,無效寫操作較多刪除緩存:更新數(shù)據(jù)庫時(shí)讓緩存失效,查詢時(shí)再更新緩存如何保證緩存與數(shù)據(jù)庫的操作的同時(shí)成功或失?。繂误w系統(tǒng),將緩存與數(shù)據(jù)庫操作放在一個(gè)事務(wù)分布式系統(tǒng),利用TCC等分布式事務(wù)方案先操作緩存還是先操作數(shù)據(jù)庫?由緩存的調(diào)用者,在更新數(shù)據(jù)庫的同時(shí)更新緩存CacheAsidePattern01先刪除緩存,再操作數(shù)據(jù)庫先操作數(shù)據(jù)庫,再刪除緩存CacheAsidePattern先刪除緩存,再操作數(shù)據(jù)庫先操作數(shù)據(jù)庫,再刪除緩存線程11.刪除緩存線程22.查詢緩存,未命中,查詢數(shù)據(jù)庫2.更新數(shù)據(jù)庫v=203.寫入緩存緩存數(shù)據(jù)庫10102020CacheAsidePattern先刪除緩存,再操作數(shù)據(jù)庫先操作數(shù)據(jù)庫,再刪除緩存線程11.刪除緩存線程22.查詢緩存,未命中,查詢數(shù)據(jù)庫4.更新數(shù)據(jù)庫v=203.寫入緩存線程13.查詢緩存,

未命中,

查詢數(shù)據(jù)庫線程21.更新數(shù)據(jù)庫v=204.

寫入緩存2.刪除緩存緩存數(shù)據(jù)庫10102020CacheAsidePattern先刪除緩存,再操作數(shù)據(jù)庫先操作數(shù)據(jù)庫,再刪除緩存線程11.刪除緩存線程22.查詢緩存,未命中,查詢數(shù)據(jù)庫4.更新數(shù)據(jù)庫v=203.寫入緩存線程11.查詢緩存,

未命中,

查詢數(shù)據(jù)庫線程22.更新數(shù)據(jù)庫v=204.

寫入緩存3.刪除緩存緩存數(shù)據(jù)庫10102010緩存更新策略的最佳實(shí)踐方案:低一致性需求:使用Redis自帶的內(nèi)存淘汰機(jī)制高一致性需求:主動(dòng)更新,并以超時(shí)剔除作為兜底方案讀操作:緩存命中則直接返回緩存未命中則查詢數(shù)據(jù)庫,并寫入緩存,設(shè)定超時(shí)時(shí)間寫操作:先寫數(shù)據(jù)庫,然后再刪除緩存要確保數(shù)據(jù)庫與緩存操作的原子性給查詢商鋪的緩存添加超時(shí)剔除和主動(dòng)更新的策略修改ShopController中的業(yè)務(wù)邏輯,滿足下面的需求:根據(jù)id查詢店鋪時(shí),如果緩存未命中,則查詢數(shù)據(jù)庫,將數(shù)據(jù)庫結(jié)果寫入緩存,并設(shè)置超時(shí)時(shí)間根據(jù)id修改店鋪時(shí),先修改數(shù)據(jù)庫,再刪除緩存什么是緩存添加Redis緩存緩存更新策略緩存穿透緩存雪崩緩存擊穿緩存工具封裝緩存穿透是指客戶端請求的數(shù)據(jù)在緩存中和數(shù)據(jù)庫中都不存在,這樣緩存永遠(yuǎn)不會(huì)生效,這些請求都會(huì)打到數(shù)據(jù)庫。常見的解決方案有兩種:緩存空對象優(yōu)點(diǎn):實(shí)現(xiàn)簡單,維護(hù)方便缺點(diǎn):額外的內(nèi)存消耗可能造成短期的不一致布隆過濾優(yōu)點(diǎn):內(nèi)存占用較少,沒有多余key缺點(diǎn):實(shí)現(xiàn)復(fù)雜存在誤判可能緩存穿透客戶端數(shù)據(jù)庫Redis請求2.未命中請求1.未命中3.緩存null客戶端數(shù)據(jù)庫Redis客戶端數(shù)據(jù)庫Redis布隆過濾器請求拒絕1.不存在則拒絕放行2.存在,

則放行返回3.緩存命中,

則返回4.緩存未命中,則查詢數(shù)據(jù)庫5.緩存數(shù)據(jù)返回設(shè)置TTL開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在解決緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在解決緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫將空值寫入Redis判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在解決緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫將空值寫入Redis判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在解決緩存穿透開始提交商鋪id判斷緩存是否命中未命中命中返回商鋪信息結(jié)束從Redis查詢商鋪緩存根據(jù)id查詢數(shù)據(jù)庫返回404判斷商鋪是否存在將商鋪數(shù)據(jù)寫入Redis存在不存在判斷是否是空值是不是緩存穿透產(chǎn)生的原因是什么?用戶請求的數(shù)據(jù)在緩存中和數(shù)據(jù)庫中都不存在,不斷發(fā)起這樣的請求,給數(shù)據(jù)庫帶來巨大壓力緩存穿透的解決方案有哪些?緩存null值布隆過濾增強(qiáng)id的復(fù)雜度,避免被猜測id規(guī)律做好數(shù)據(jù)的基礎(chǔ)格式校驗(yàn)加強(qiáng)用戶權(quán)限校驗(yàn)做好熱點(diǎn)參數(shù)的限流什么是緩存添加Redis緩存緩存更新策略緩存穿透緩存雪崩緩存擊穿緩存工具封裝緩存雪崩是指在同一時(shí)段大量的緩存key同時(shí)失效或者Redis服務(wù)宕機(jī),導(dǎo)致大量請求到達(dá)數(shù)據(jù)庫,帶來巨大壓力。解決方案:給不同的Key的TTL添加隨機(jī)值利用Redis集群提高服務(wù)的可用性給緩存業(yè)務(wù)添加降級(jí)限流策略給業(yè)務(wù)添加多級(jí)緩存緩存雪崩客戶端數(shù)據(jù)庫Redis請求請求1.未命中客戶端數(shù)據(jù)庫Redis請求請求Redis宕機(jī)什么是緩存添加Redis緩存緩存更新策略緩存穿透緩存雪崩緩存擊穿緩存工具封裝緩存擊穿問題也叫熱點(diǎn)Key問題,就是一個(gè)被高并發(fā)訪問并且緩存重建業(yè)務(wù)較復(fù)雜的key突然失效了,無數(shù)的請求訪問會(huì)在瞬間給數(shù)據(jù)庫帶來巨大的沖擊。常見的解決方案有兩種:緩存擊穿線程11.查詢緩存,

未命中4.

寫入緩存2.查詢數(shù)據(jù)庫重建緩存數(shù)據(jù)線程21.查詢緩存,

未命中2.查詢數(shù)據(jù)庫重建緩存線程31.查詢緩存,

未命中2.查詢數(shù)據(jù)庫重建緩存線程41.查詢緩存,

未命中2.查詢數(shù)據(jù)庫重建緩存互斥鎖邏輯過期緩存擊穿線程11.查詢緩存,

未命中3.查詢數(shù)據(jù)庫重建緩存數(shù)據(jù)互斥鎖邏輯過期2.獲取互斥鎖成功4.寫入緩存5.釋放鎖線程21.查詢緩存,

未命中2.獲取

互斥鎖失敗5.緩存命中4.重試3.休眠一會(huì)兒,再重試線程11.查詢緩存,發(fā)現(xiàn)邏輯時(shí)間已過期2.獲取互斥

鎖成功4.返回過期數(shù)據(jù)線程31.查詢緩存,發(fā)現(xiàn)邏輯時(shí)間已過期2.獲取

互斥鎖失敗3.返回過期數(shù)據(jù)線程21.查詢數(shù)據(jù)庫重建緩存數(shù)據(jù)2.寫入緩存,

重置邏輯

過期時(shí)間3.釋放鎖3.開啟新線程線程41.命中緩存,并且沒有過期KEYVALUEheima:user:1{name:"Jack",age:21,expire:152141223}緩存擊穿解決方案優(yōu)點(diǎn)缺點(diǎn)互斥鎖沒有額外的內(nèi)存消耗保證一致性實(shí)現(xiàn)簡單線程需要等待,性能受影響可能有死鎖風(fēng)險(xiǎn)邏輯過期線程無需等待,性能較好不保證一致性有額外內(nèi)存消耗實(shí)現(xiàn)復(fù)雜基于互斥鎖方式解決緩存擊穿問題需求:修改根據(jù)id查詢商鋪的業(yè)務(wù),基于互斥鎖方式來解決緩存擊穿問題開始提交商鋪id判斷緩存是否命中未命中結(jié)束從Redis查詢商鋪緩存命中根據(jù)id查詢數(shù)據(jù)庫將商鋪數(shù)據(jù)寫入Redis返回?cái)?shù)據(jù)嘗試獲取互斥鎖判斷是否獲取鎖釋放互斥鎖是否休眠一段時(shí)間基于邏輯過期方式解決緩存擊穿問題需求:修改根據(jù)id查詢商鋪的業(yè)務(wù),基于邏輯過期方式來解決緩存擊穿問題開始提交商鋪id判斷緩存是否命中命中結(jié)束從Redis查詢商鋪緩存未命中根據(jù)id查詢數(shù)據(jù)庫將商鋪數(shù)據(jù)寫入Redis,并設(shè)置邏輯過期時(shí)間判斷緩存是否過期返回商鋪信息返回空未過期嘗試獲取互斥鎖判斷是否獲取鎖開啟獨(dú)立線程釋放互斥鎖過期否是什么是緩存添加Redis緩存緩存更新策略緩存穿透緩存雪崩緩存擊穿緩存工具封裝基于StringRedisTemplate封裝一個(gè)緩存工具類,滿足下列需求:方法1:將任意Java對象序列化為json并存儲(chǔ)在string類型的key中,并且可以設(shè)置TTL過期時(shí)間方法2:將任意Java對象序列化為json并存儲(chǔ)在string類型的key中,并且可以設(shè)置邏輯過期時(shí)間,用于處理緩存擊穿問題方法3:根據(jù)指定的key查詢緩存,并反序列化為指定類型,利用緩存空值的方式解決緩存穿透問題方法4:根據(jù)指定的key查詢緩存,并反序列化為指定類型,需要利用邏輯過期解決緩存擊穿問題緩存工具封裝優(yōu)惠券秒殺03實(shí)現(xiàn)優(yōu)惠券秒殺下單超賣問題一人一單分布式鎖Redis優(yōu)化秒殺Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺全局唯一ID每個(gè)店鋪都可以發(fā)布優(yōu)惠券:當(dāng)用戶搶購時(shí),就會(huì)生成訂單并保存到tb_voucher_order這張表中,而訂單表如果使用數(shù)據(jù)庫自增ID就存在一些問題:id的規(guī)律性太明顯受單表數(shù)據(jù)量的限制全局唯一ID全局ID生成器,是一種在分布式系統(tǒng)下用來生成全局唯一ID的工具,一般要滿足下列特性:全局唯一ID全局唯一ID唯一性高可用高性能遞增性安全性為了增加ID的安全性,我們可以不直接使用Redis自增的數(shù)值,而是拼接一些其它信息:全局ID生成器0011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010011001010--時(shí)間戳(31bit)序列號(hào)(32bit)符號(hào)位ID的組成部分:符號(hào)位:1bit,永遠(yuǎn)為0時(shí)間戳:31bit,以秒為單位,可以使用69年序列號(hào):32bit,秒內(nèi)的計(jì)數(shù)器,支持每秒產(chǎn)生2^32個(gè)不同ID全局唯一ID生成策略:UUIDRedis自增snowflake算法數(shù)據(jù)庫自增Redis自增ID策略:每天一個(gè)key,方便統(tǒng)計(jì)訂單量ID構(gòu)造是時(shí)間戳+計(jì)數(shù)器實(shí)現(xiàn)優(yōu)惠券秒殺下單超賣問題一人一單分布式鎖Redis優(yōu)化秒殺Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺全局ID生成器每個(gè)店鋪都可以發(fā)布優(yōu)惠券,分為平價(jià)券和特價(jià)券。平價(jià)券可以任意購買,而特價(jià)券需要秒殺搶購:表關(guān)系如下:tb_voucher:優(yōu)惠券的基本信息,優(yōu)惠金額、使用規(guī)則等tb_seckill_voucher:優(yōu)惠券的庫存、開始搶購時(shí)間,結(jié)束搶購時(shí)間。特價(jià)優(yōu)惠券才需要填寫這些信息實(shí)現(xiàn)優(yōu)惠券秒殺下單在VoucherController中提供了一個(gè)接口,可以添加秒殺優(yōu)惠券:實(shí)現(xiàn)優(yōu)惠券秒殺下單用戶可以在店鋪頁面中搶購這些優(yōu)惠券:實(shí)現(xiàn)優(yōu)惠券秒殺下單說明請求方式POST請求路徑/voucher-order/seckill/{id}請求參數(shù)id,優(yōu)惠券id返回值訂單id實(shí)現(xiàn)優(yōu)惠券秒殺的下單功能下單時(shí)需要判斷兩點(diǎn):秒殺是否開始或結(jié)束,如果尚未開始或已經(jīng)結(jié)束則無法下單庫存是否充足,不足則無法下單開始提交優(yōu)惠券id判斷秒殺是否開始是否返回異常結(jié)果結(jié)束查詢優(yōu)惠券信息創(chuàng)建訂單返回訂單id扣減庫存判斷庫存是否充足是否實(shí)現(xiàn)優(yōu)惠券秒殺下單超賣問題一人一單分布式鎖Redis優(yōu)化秒殺Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺全局ID生成器超賣問題線程11.查詢庫存線程21.查詢庫存2.判斷是否大于0是:扣減否:報(bào)錯(cuò)2.判斷是否大于0是:扣減否:報(bào)錯(cuò)庫存1010超賣問題線程11.查詢庫存線程21.查詢庫存2.判斷是否大于0是:扣減否:報(bào)錯(cuò)2.判斷是否大于0是:扣減否:報(bào)錯(cuò)庫存1011-1線程21.查詢庫存2.判斷是否大于0是:扣減否:報(bào)錯(cuò)超賣問題是典型的多線程安全問題,針對這一問題的常見解決方案就是加鎖:超賣問題悲觀鎖鎖認(rèn)為線程安全問題一定會(huì)發(fā)生,因此在操作數(shù)據(jù)之前先獲取鎖,確保線程串行執(zhí)行。例如Synchronized、Lock都屬于悲觀鎖認(rèn)為線程安全問題不一定會(huì)發(fā)生,因此不加鎖,只是在更新數(shù)據(jù)時(shí)去判斷有沒有其它線程對數(shù)據(jù)做了修改。如果沒有修改則認(rèn)為是安全的,自己才更新數(shù)據(jù)。如果已經(jīng)被其它線程修改說明發(fā)生了安全問題,此時(shí)可以重試或異常。樂觀鎖version樂觀鎖的關(guān)鍵是判斷之前查詢得到的數(shù)據(jù)是否有被修改過,常見的方式有兩種:樂觀鎖版本號(hào)法idstock10線程11.查詢庫存線程21.查詢庫存2.判斷是否大于0是:扣減否:報(bào)錯(cuò)2.判斷是否大于0是:扣減否:報(bào)錯(cuò)和版本號(hào)stock=version=setstock=stock-1,version=version+1whereid=10andversion=和版本號(hào)stock=version=111110211setstock=stock-1,version=version+1whereid=10andversion=1樂觀鎖的關(guān)鍵是判斷之前查詢得到的數(shù)據(jù)是否有被修改過,常見的方式有兩種:樂觀鎖CAS法idstock10線程11.查詢庫存線程21.查詢庫存2.判斷是否大于0是:扣減否:報(bào)錯(cuò)2.判斷是否大于0是:扣減否:報(bào)錯(cuò)stock=setstock=stock-1whereid=10andstock=stock=11101setstock=stock-1whereid=10andstock=1超賣這樣的線程安全問題,解決方案有哪些?悲觀鎖:添加同步鎖,讓線程串行執(zhí)行優(yōu)點(diǎn):簡單粗暴缺點(diǎn):性能一般樂觀鎖:不加鎖,在更新時(shí)判斷是否有其它線程在修改優(yōu)點(diǎn):性能好缺點(diǎn):存在成功率低的問題實(shí)現(xiàn)優(yōu)惠券秒殺下單超賣問題一人一單分布式鎖Redis優(yōu)化秒殺Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺全局ID生成器需求:修改秒殺業(yè)務(wù),要求同一個(gè)優(yōu)惠券,一個(gè)用戶只能下一單一人一單開始提交優(yōu)惠券id判斷秒殺是否開始是否返回異常結(jié)果結(jié)束查詢優(yōu)惠券信息創(chuàng)建訂單返回訂單id扣減庫存判斷庫存是否充足是否需求:修改秒殺業(yè)務(wù),要求同一個(gè)優(yōu)惠券,一個(gè)用戶只能下一單一人一單開始提交優(yōu)惠券id判斷秒殺是否開始是否返回異常結(jié)果結(jié)束查詢優(yōu)惠券信息創(chuàng)建訂單返回訂單id扣減庫存判斷庫存是否充足是否根據(jù)優(yōu)惠券id和用戶id查詢訂單判斷訂單是否存在存在不存在通過加鎖可以解決在單機(jī)情況下的一人一單安全問題,但是在集群模式下就不行了。我們將服務(wù)啟動(dòng)兩份,端口分別為8081和8082:然后修改nginx的conf目錄下的nginx.conf文件,配置反向代理和負(fù)載均衡:現(xiàn)在,用戶請求會(huì)在這兩個(gè)節(jié)點(diǎn)上負(fù)載均衡,再次測試下是否存在線程安全問題。一人一單的并發(fā)安全問題一人一單的并發(fā)安全問題線程11.查詢訂單線程21.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單一人一單的并發(fā)安全問題線程11.查詢訂單線程21.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單一人一單的并發(fā)安全問題線程11.查詢訂單線程22.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖成功3.釋放鎖1.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖失敗等待鎖釋放線程31.查詢訂單線程42.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖成功3.釋放鎖1.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖失敗等待鎖釋放JVM2一人一單的并發(fā)安全問題線程11.查詢訂單線程22.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖成功3.釋放鎖1.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖失敗等待鎖釋放JVM1鎖監(jiān)視器鎖監(jiān)視器線程1線程3實(shí)現(xiàn)優(yōu)惠券秒殺下單超賣問題一人一單分布式鎖Redis優(yōu)化秒殺Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺全局ID生成器分布式鎖線程31.查詢訂單線程42.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖成功3.釋放鎖1.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖失敗等待鎖釋放JVM2線程11.查詢訂單線程22.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖成功3.釋放鎖1.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖失敗等待鎖釋放JVM1鎖監(jiān)視器鎖監(jiān)視器鎖監(jiān)視器分布式鎖線程3線程4JVM2線程11.查詢訂單線程22.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖成功3.釋放鎖0.獲取互斥鎖失敗等待鎖釋放JVM1鎖監(jiān)視器0.獲取互斥鎖失敗等待鎖釋放線程11.查詢訂單2.判斷是否存在是:報(bào)錯(cuò)否:插入新訂單0.獲取互斥鎖成功0.獲取互斥鎖失敗等待鎖釋放分布式鎖:滿足分布式系統(tǒng)或集群模式下多進(jìn)程可見并且互斥的鎖。什么是分布式鎖互斥...多進(jìn)程可見安全性高可用高性能分布式鎖MySQLRedisZookeeper分布式鎖的核心是實(shí)現(xiàn)多進(jìn)程之間互斥,而滿足這一點(diǎn)的方式有很多,常見的有三種:分布式鎖的實(shí)現(xiàn)安全性高性能互斥高可用利用mysql本身的互斥鎖機(jī)制利用setnx這樣的互斥命令利用節(jié)點(diǎn)的唯一性和有序性實(shí)現(xiàn)互斥好好好一般好一般斷開連接,自動(dòng)釋放鎖利用鎖超時(shí)時(shí)間,到期釋放臨時(shí)節(jié)點(diǎn),斷開連接自動(dòng)釋放基于Redis的分布式鎖實(shí)現(xiàn)分布式鎖時(shí)需要實(shí)現(xiàn)的兩個(gè)基本方法:獲取鎖:互斥:確保只能有一個(gè)線程獲取鎖非阻塞:嘗試一次,成功返回true,失敗返回false釋放鎖:手動(dòng)釋放超時(shí)釋放:獲取鎖時(shí)添加一個(gè)超時(shí)時(shí)間#添加鎖,NX是互斥、EX是設(shè)置超時(shí)時(shí)間SETlockthread1NXEX10#釋放鎖,刪除即可DELkey開始嘗試獲取鎖判斷結(jié)果nilok獲取鎖成功執(zhí)行業(yè)務(wù)釋放鎖獲取鎖失敗業(yè)務(wù)超時(shí)或服務(wù)宕機(jī)自動(dòng)釋放鎖#添加鎖,利用setnx的互斥特性SETNXlockthread1#添加鎖過期時(shí)間,避免服務(wù)宕機(jī)引起的死鎖EXPIRElock10基于Redis實(shí)現(xiàn)分布式鎖初級(jí)版本需求:定義一個(gè)類,實(shí)現(xiàn)下面接口,利用Redis實(shí)現(xiàn)分布式鎖功能。publicinterfaceILock{

/**

*嘗試獲取鎖

*@paramtimeoutSec鎖持有的超時(shí)時(shí)間,過期后自動(dòng)釋放

*@returntrue代表獲取鎖成功;false代表獲取鎖失敗

*/

booleantryLock(longtimeoutSec);

/**

*釋放鎖

*/

voidunlock();

}

基于Redis的分布式鎖Redis鎖線程1線程2線程3獲取鎖業(yè)務(wù)阻塞超時(shí)釋放鎖獲取鎖OKOK執(zhí)行業(yè)務(wù)釋放鎖業(yè)務(wù)完成獲取鎖OK執(zhí)行業(yè)務(wù)基于Redis的分布式鎖Redis鎖線程1線程2線程3獲取鎖業(yè)務(wù)阻塞超時(shí)釋放鎖獲取鎖OKOK執(zhí)行業(yè)務(wù)釋放鎖業(yè)務(wù)完成獲取鎖OK執(zhí)行業(yè)務(wù)獲取鎖標(biāo)示并判斷是否一致NIL釋放鎖獲取鎖標(biāo)示并判斷是否一致OK基于Redis的分布式鎖Redis鎖線程1線程2線程3獲取鎖業(yè)務(wù)阻塞超時(shí)釋放鎖獲取鎖OKOK執(zhí)行業(yè)務(wù)業(yè)務(wù)完成獲取鎖OK執(zhí)行業(yè)務(wù)獲取鎖標(biāo)示并判斷是否一致NIL釋放鎖獲取鎖標(biāo)示并判斷是否一致OK開始嘗試獲取鎖判斷結(jié)果nilok獲取鎖成功執(zhí)行業(yè)務(wù)釋放鎖獲取鎖失敗業(yè)務(wù)超時(shí)或服務(wù)宕機(jī)自動(dòng)釋放鎖基于Redis的分布式鎖Redis鎖線程1線程2線程3獲取鎖業(yè)務(wù)阻塞超時(shí)釋放鎖獲取鎖OKOK執(zhí)行業(yè)務(wù)業(yè)務(wù)完成獲取鎖OK執(zhí)行業(yè)務(wù)獲取鎖標(biāo)示并判斷是否一致NIL釋放鎖獲取鎖標(biāo)示并判斷是否一致OK開始嘗試獲取鎖判斷結(jié)果nilok獲取鎖成功執(zhí)行業(yè)務(wù)釋放鎖獲取鎖失敗業(yè)務(wù)超時(shí)或服務(wù)宕機(jī)自動(dòng)釋放鎖判斷鎖標(biāo)示是否是自己存入線程標(biāo)示是改進(jìn)Redis的分布式鎖需求:修改之前的分布式鎖實(shí)現(xiàn),滿足:在獲取鎖時(shí)存入線程標(biāo)示(可以用UUID表示)在釋放鎖時(shí)先獲取鎖中的線程標(biāo)示,判斷是否與當(dāng)前線程標(biāo)示一致如果一致則釋放鎖如果不一致則不釋放鎖基于Redis的分布式鎖Redis鎖線程1線程2線程3獲取鎖執(zhí)行業(yè)務(wù)超時(shí)釋放鎖獲取鎖OKOK執(zhí)行業(yè)務(wù)釋放鎖阻塞獲取鎖OK執(zhí)行業(yè)務(wù)獲取鎖標(biāo)示并判斷是否一致okRedis的Lua腳本Redis提供了Lua腳本功能,在一個(gè)腳本中編寫多條Redis命令,確保多條命令執(zhí)行時(shí)的原子性。Lua是一種編程語言,它的基本語法大家可以參考網(wǎng)站:/lua/lua-tutorial.html這里重點(diǎn)介紹Redis提供的調(diào)用函數(shù),語法如下:例如,我們要執(zhí)行setnamejack,則腳本是這樣:例如,我們要先執(zhí)行setnameRose,再執(zhí)行g(shù)etname,則腳本如下:#執(zhí)行redis命令redis.call('命令名稱','key','其它參數(shù)',...)#執(zhí)行setnamejackredis.call('set','name','jack')#先執(zhí)行setnamejackredis.call('set','name','jack')#再執(zhí)行g(shù)etnamelocalname=redis.call('get','name')#返回returnnameRedis的Lua腳本寫好腳本以后,需要用Redis命令來調(diào)用腳本,調(diào)用腳本的常見命令如下:例如,我們要執(zhí)行redis.call('set','name','jack')這個(gè)腳本,語法如下:如果腳本中的key、value不想寫死,可以作為參數(shù)傳遞。key類型參數(shù)會(huì)放入KEYS數(shù)組,其它參數(shù)會(huì)放入ARGV數(shù)組,在腳本中可以從KEYS和ARGV數(shù)組獲取這些參數(shù):#調(diào)用腳本EVAL"returnredis.call('set','name','jack')"0腳本內(nèi)容腳本需要的key類型的參數(shù)個(gè)數(shù)#調(diào)用腳本EVAL"returnredis.call('set',,)"1nameRose腳本內(nèi)容腳本需要的key類型的參數(shù)個(gè)數(shù)KEYS[1]ARGV[1]基于Redis的分布式鎖釋放鎖的業(yè)務(wù)流程是這樣的:獲取鎖中的線程標(biāo)示判斷是否與指定的標(biāo)示(當(dāng)前線程標(biāo)示)一致如果一致則釋放鎖(刪除)如果不一致則什么都不做如果用Lua腳本來表示則是這樣的:--這里的KEYS[1]就是鎖的key,這里的ARGV[1]就是當(dāng)前線程標(biāo)示--獲取鎖中的標(biāo)示,判斷是否與當(dāng)前線程標(biāo)示一致if(redis.call('GET',KEYS[1])==ARGV[1])then--一致,則刪除鎖

returnredis.call('DEL',KEYS[1])end--不一致,則直接返回return0再次改進(jìn)Redis的分布式鎖需求:基于Lua腳本實(shí)現(xiàn)分布式鎖的釋放鎖邏輯提示:RedisTemplate調(diào)用Lua腳本的API如下:基于Redis的分布式鎖實(shí)現(xiàn)思路:利用setnxex獲取鎖,并設(shè)置過期時(shí)間,保存線程標(biāo)示釋放鎖時(shí)先判斷線程標(biāo)示是否與自己一致,一致則刪除鎖特性:利用setnx滿足互斥性利用setex保證故障時(shí)鎖依然能釋放,避免死鎖,提高安全性利用Redis集群保證高可用和高并發(fā)特性基于Redis的分布式鎖優(yōu)化基于setnx實(shí)現(xiàn)的分布式鎖存在下面的問題:不可重入同一個(gè)線程無法多次獲取同一把鎖01不可重試獲取鎖只嘗試一次就返回false,沒有重試機(jī)制02超時(shí)釋放鎖超時(shí)釋放雖然可以避免死鎖,但如果是業(yè)務(wù)執(zhí)行耗時(shí)較長,也會(huì)導(dǎo)致鎖釋放,存在安全隱患03主從一致性如果Redis提供了主從集群,主從同步存在延遲,當(dāng)主宕機(jī)時(shí),如果從并同步主中的鎖數(shù)據(jù),則會(huì)出現(xiàn)鎖實(shí)現(xiàn)04RedissonRedisson是一個(gè)在Redis的基礎(chǔ)上實(shí)現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-MemoryDataGrid)。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務(wù),其中就包含了各種分布式鎖的實(shí)現(xiàn)。官網(wǎng)地址:

GitHub地址:

/redisson/redissonRedisson入門引入依賴:配置Redisson客戶端:<dependency>

<groupId>org.redisson</groupId>

<artifactId>redisson</artifactId>

<version>3.13.6</version>

</dependency>@Configuration

publicclassRedisConfig{

@Bean

publicRedissonClientredissonClient(){

//配置類

Configconfig=newConfig();

//添加redis地址,這里添加了單點(diǎn)的地址,也可以使用config.useClusterServers()添加集群地址

config.useSingleServer().setAddress("redis://01:6379").setPassowrd("123321");

//創(chuàng)建客戶端

returnRedisson.create(config);

}

}Redisson入門使用Redisson的分布式鎖@Resource

privateRedissonClientredissonClient;

@Test

voidtestRedisson()throwsInterruptedException{

//獲取鎖(可重入),指定鎖的名稱

RLocklock=redissonClient.getLock("anyLock");

//嘗試獲取鎖,參數(shù)分別是:獲取鎖的最大等待時(shí)間(期間會(huì)重試),鎖自動(dòng)釋放時(shí)間,時(shí)間單位

booleanisLock=lock.tryLock(1,10,TimeUnit.SECONDS);

//判斷釋放獲取成功

if(isLock){

try{

System.out.println("執(zhí)行業(yè)務(wù)");

}finally{

//釋放鎖

lock.unlock();

}

}

}Redisson可重入鎖原理//創(chuàng)建鎖對象

RLocklock=redissonClient.getLock("lock");

@Test

voidmethod1(){

booleanisLock=lock.tryLock();

if(!isLock){

log.error("獲取鎖失敗,1");

return;

}

try{

("獲取鎖成功,1");

method2();

}finally{

("釋放鎖,1");

lock.unlock();

}

}

voidmethod2(){

booleanisLock=lock.tryLock();

if(!isLock){

log.error("獲取鎖失敗,2");

return;

}

try{

("獲取鎖成功,2");

}finally{

("釋放鎖,2");

lock.unlock();

}

}KEYVALUElockthread1開始嘗試獲取鎖判斷結(jié)果nilok獲取鎖成功執(zhí)行業(yè)務(wù)釋放鎖獲取鎖失敗業(yè)務(wù)超時(shí)或服務(wù)宕機(jī)自動(dòng)釋放鎖判斷鎖標(biāo)示是否是自己存入線程標(biāo)示是Redisson可重入鎖原理//創(chuàng)建鎖對象

RLocklock=redissonClient.getLock("lock");

@Test

voidmethod1(){

booleanisLock=lock.tryLock();

if(!isLock){

log.error("獲取鎖失敗,1");

return;

}

try{

("獲取鎖成功,1");

method2();

}finally{

("釋放鎖,1");

lock.unlock();

}

}

voidmethod2(){

booleanisLock=lock.tryLock();

if(!isLock){

log.error("獲取鎖失敗,2");

return;

}

try{

("獲取鎖成功,2");

}finally{

("釋放鎖,2");

lock.unlock();

}

}KEYVALUEfieldvaluelockthread1021開始設(shè)置鎖有效期釋放鎖獲取鎖失敗鎖已釋放判斷鎖是否存在是否判斷鎖標(biāo)示是否是自己是否鎖計(jì)數(shù)+1執(zhí)行業(yè)務(wù)判斷鎖是否是自己否是鎖計(jì)數(shù)-1判斷鎖計(jì)數(shù)是否為0獲取鎖并添加線程標(biāo)示是否重置鎖有效期Redisson可重入鎖原理開始設(shè)置鎖有效期釋放鎖獲取鎖失敗鎖已釋放判斷鎖是否存在是否判斷鎖標(biāo)示是否是自己是否鎖計(jì)數(shù)+1執(zhí)行業(yè)務(wù)判斷鎖是否是自己否是鎖計(jì)數(shù)-1判斷鎖計(jì)數(shù)是否為0獲取鎖并添加線程標(biāo)示是否重置鎖有效期localkey=KEYS[1];--鎖的key

localthreadId=ARGV[1];--線程唯一標(biāo)識(shí)

localreleaseTime=ARGV[2];--鎖的自動(dòng)釋放時(shí)間

--判斷是否存在

if(redis.call('exists',key)==0)then

--不存在,獲取鎖

redis.call('hset',key,threadId,'1');

--設(shè)置有效期

redis.call('expire',key,releaseTime);

return1;--返回結(jié)果

end;

--鎖已經(jīng)存在,判斷threadId是否是自己

if(redis.call('hexists',key,threadId)==1)then

--不存在,獲取鎖,重入次數(shù)+1

redis.call('hincrby',key,threadId,'1');

--設(shè)置有效期

redis.call('expire',key,releaseTime);

return1;--返回結(jié)果

end;

return0;--代碼走到這里,說明獲取鎖的不是自己,獲取鎖失敗獲取鎖的Lua腳本:KEYVALUEfieldvaluelockthread10Redisson可重入鎖原理開始設(shè)置鎖有效期釋放鎖獲取鎖失敗鎖已釋放判斷鎖是否存在是否判斷鎖標(biāo)示是否是自己是否鎖計(jì)數(shù)+1執(zhí)行業(yè)務(wù)判斷鎖是否是自己否是鎖計(jì)數(shù)-1判斷鎖計(jì)數(shù)是否為0獲取鎖并添加線程標(biāo)示是否重置鎖有效期localkey=KEYS[1];--鎖的key

localthreadId=ARGV[1];--線程唯一標(biāo)識(shí)

localreleaseTime=ARGV[2];--鎖的自動(dòng)釋放時(shí)間

--判斷當(dāng)前鎖是否還是被自己持有

if(redis.call('HEXISTS',key,threadId)==0)then

returnnil;--如果已經(jīng)不是自己,則直接返回

end;

--是自己的鎖,則重入次數(shù)-1

localcount=redis.call('HINCRBY',key,threadId,-1);

--判斷是否重入次數(shù)是否已經(jīng)為0

if(count>0)then

--大于0說明不能釋放鎖,重置有效期然后返回

redis.call('EXPIRE',key,releaseTime);

returnnil;

else--等于0說明可以釋放鎖,直接刪除

redis.call('DEL',key);

returnnil;

end;釋放鎖的Lua腳本:KEYVALUEfieldvaluelock:orderthread10Redisson分布式鎖原理開始嘗試獲取鎖判斷ttl是否為null是否返回true結(jié)束開啟watchDogleaseTime是否為-1是否判斷剩余等待時(shí)間是否大于0否返回false訂閱并等待釋放鎖的信號(hào)是判斷等待時(shí)間是否超時(shí)是否開始嘗試釋放鎖判斷是否成功否記錄異常結(jié)束發(fā)送釋放鎖消息是取消watchDogRedisson分布式鎖原理:可重入:利用hash結(jié)構(gòu)記錄線程id和重入次數(shù)可重試:利用信號(hào)量和PubSub功能實(shí)現(xiàn)等待、喚醒,獲取鎖失敗的重試機(jī)制超時(shí)續(xù)約:利用watchDog,每隔一段時(shí)間(releaseTime/3),重置超時(shí)時(shí)間Redisson分布式鎖主從一致性問題RedisMasterRedisSlaveRedisSlaveJava應(yīng)用1.獲取鎖SETlockthread1NXEX10lock=thread1主從同步主從同步Redisson分布式鎖主從一致性問題RedisMasterRedisSlaveRedisMasterJava應(yīng)用1.獲取鎖SETlockthread1NXEX10lock=thread1主從同步鎖失效RedisSlaveRedisSlaveRedisSlaveRedisson分布式鎖主從一致性問題RedisNodeRedisNodeRedisNodeJava應(yīng)用2.獲取鎖SETlockthread1NXEX10lock=thread1lock=thread1lock=thread11.獲取鎖SETlockthread1NXEX103.獲取鎖SETlockthread1NXEX10RedisSlave主從同步RedisSlave主從同步RedisSlave主從同步Redisson分布式鎖主從一致性問題RedisNodeRedisNodeRedisNodeJava應(yīng)用2.獲取鎖SETlockthread1NXEX10lock=thread1lock=thread1lock=thread11.獲取鎖SETlockthread1NXEX103.獲取鎖SETlockthread1NXEX10RedisMaster主從同步RedisSlave主從同步RedisSlave主從同步1)不可重入Redis分布式鎖:原理:利用setnx的互斥性;利用ex避免死鎖;釋放鎖時(shí)判斷線程標(biāo)示缺陷:不可重入、無法重試、鎖超時(shí)失效2)可重入的Redis分布式鎖:原理:利用hash結(jié)構(gòu),記錄線程標(biāo)示和重入次數(shù);利用watchDog延續(xù)鎖時(shí)間;利用信號(hào)量控制鎖重試等待缺陷:redis宕機(jī)引起鎖失效問題3)Redisson的multiLock:原理:多個(gè)獨(dú)立的Redis節(jié)點(diǎn),必須在所有節(jié)點(diǎn)都獲取重入鎖,才算獲取鎖成功缺陷:運(yùn)維成本高、實(shí)現(xiàn)復(fù)雜實(shí)現(xiàn)優(yōu)惠券秒殺下單超賣問題一人一單分布式鎖Redis優(yōu)化秒殺Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺全局ID生成器Redis優(yōu)化秒殺查詢優(yōu)惠券判斷秒殺庫存查詢訂單減庫存創(chuàng)建訂單校驗(yàn)一人一單MySQL集群TomcatRedis優(yōu)化秒殺查詢優(yōu)惠券判斷秒殺庫存查詢訂單減庫存創(chuàng)建訂單校驗(yàn)一人一單MySQL集群Tomcat校驗(yàn)一人一單判斷秒殺庫存Redis優(yōu)化秒殺查詢優(yōu)惠券判斷秒殺庫存查詢訂單減庫存創(chuàng)建訂單校驗(yàn)一人一單MySQL集群Tomcat判斷秒殺庫存校驗(yàn)一人一單Redis保存優(yōu)惠券id、用戶id、訂單id到阻塞隊(duì)列異步讀取隊(duì)列中的信息,完成下單返回訂單idLua腳本Redis優(yōu)化秒殺開始判斷庫存是否充足結(jié)束否返回1是判斷用戶是否下單扣減庫存否將userId存入當(dāng)前優(yōu)惠券的set集合返回0開始執(zhí)行l(wèi)ua腳本判斷結(jié)果是否為0否返回異常信息結(jié)束將優(yōu)惠券id、用戶id和訂單id存入阻塞隊(duì)列是返回訂單id是返回2KEYVALUEstock:vid:7100KEYVALUEorder:vid:71,2,3,5,7,8異步下單改進(jìn)秒殺業(yè)務(wù),提高并發(fā)性能需求:新增秒殺優(yōu)惠券的同時(shí),將優(yōu)惠券信息保存到Redis中基于Lua腳本,判斷秒殺庫存、一人一單,決定用戶是否搶購成功如果搶購成功,將優(yōu)惠券id和用戶id封裝后存入阻塞隊(duì)列開啟線程任務(wù),不斷從阻塞隊(duì)列中獲取信息,實(shí)現(xiàn)異步下單功能秒殺業(yè)務(wù)的優(yōu)化思路是什么?先利用Redis完成庫存余量、一人一單判斷,完成搶單業(yè)務(wù)再將下單業(yè)務(wù)放入阻塞隊(duì)列,利用獨(dú)立線程異步下單基于阻塞隊(duì)列的異步秒殺存在哪些問題?內(nèi)存限制問題數(shù)據(jù)安全問題實(shí)現(xiàn)優(yōu)惠券秒殺下單超賣問題一人一單分布式鎖Redis優(yōu)化秒殺Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺全局ID生成器消息隊(duì)列(MessageQueue),字面意思就是存放消息的隊(duì)列。最簡單的消息隊(duì)列模型包括3個(gè)角色:消息隊(duì)列:存儲(chǔ)和管理消息,也被稱為消息代理(MessageBroker)生產(chǎn)者:發(fā)送消息到消息隊(duì)列消費(fèi)者:從消息隊(duì)列獲取消息并處理消息Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺生產(chǎn)者判斷秒殺時(shí)間和庫存校驗(yàn)一人一單MessageQueue發(fā)送優(yōu)惠券id和用戶id到消息隊(duì)列消費(fèi)者接收消息完成下單消息隊(duì)列(MessageQueue),字面意思就是存放消息的隊(duì)列。最簡單的消息隊(duì)列模型包括3個(gè)角色:消息隊(duì)列:存儲(chǔ)和管理消息,也被稱為消息代理(MessageBroker)生產(chǎn)者:發(fā)送消息到消息隊(duì)列消費(fèi)者:從消息隊(duì)列獲取消息并處理消息Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺生產(chǎn)者M(jìn)essageQueue消費(fèi)者Redis提供了三種不同的方式來實(shí)現(xiàn)消息隊(duì)列:list結(jié)構(gòu):基于List結(jié)構(gòu)模擬消息隊(duì)列PubSub:基本的點(diǎn)對點(diǎn)消息模型Stream:比較完善的消息隊(duì)列模型消息隊(duì)列(MessageQueue),字面意思就是存放消息的隊(duì)列。而Redis的list數(shù)據(jù)結(jié)構(gòu)是一個(gè)雙向鏈表,很容易模擬出隊(duì)列效果。隊(duì)列是入口和出口不在一邊,因此我們可以利用:LPUSH結(jié)合RPOP、或者RPUSH結(jié)合LPOP來實(shí)現(xiàn)。不過要注意的是,當(dāng)隊(duì)列中沒有消息時(shí)RPOP或LPOP操作會(huì)返回null,并不像JVM的阻塞隊(duì)列那樣會(huì)阻塞并等待消息。因此這里應(yīng)該使用BRPOP或者BLPOP來實(shí)現(xiàn)阻塞效果?;贚ist結(jié)構(gòu)模擬消息隊(duì)列生產(chǎn)者M(jìn)essageQueue消費(fèi)者LPUSHRPOPmsg1msg2基于List的消息隊(duì)列有哪些優(yōu)缺點(diǎn)?優(yōu)點(diǎn):利用Redis存儲(chǔ),不受限于JVM內(nèi)存上限基于Redis的持久化機(jī)制,數(shù)據(jù)安全性有保證可以滿足消息有序性缺點(diǎn):無法避免消息丟失只支持單消費(fèi)者PubSub(發(fā)布訂閱)是Redis2.0版本引入的消息傳遞模型。顧名思義,消費(fèi)者可以訂閱一個(gè)或多個(gè)channel,生產(chǎn)者向?qū)?yīng)channel發(fā)送消息后,所有訂閱者都能收到相關(guān)消息。

SUBSCRIBEchannel[channel]:訂閱一個(gè)或多個(gè)頻道

PUBLISHchannelmsg:向一個(gè)頻道發(fā)送消息

PSUBSCRIBEpattern[pattern]:訂閱與pattern格式匹配的所有頻道基于PubSub的消息隊(duì)列生產(chǎn)者M(jìn)essageQueue消費(fèi)者msg1消費(fèi)者subscribeorder.queuepsubscribeorder.*publishorder.queuemsg1msg1msg1基于PubSub的消息隊(duì)列有哪些優(yōu)缺點(diǎn)?優(yōu)點(diǎn):采用發(fā)布訂閱模型,支持多生產(chǎn)、多消費(fèi)缺點(diǎn):不支持?jǐn)?shù)據(jù)持久化無法避免消息丟失消息堆積有上限,超出時(shí)數(shù)據(jù)丟失Stream是Redis5.0引入的一種新數(shù)據(jù)類型,可以實(shí)現(xiàn)一個(gè)功能非常完善的消息隊(duì)列。發(fā)送消息的命令:例如:基于Stream的消息隊(duì)列如果隊(duì)列不存在,是否自動(dòng)創(chuàng)建隊(duì)列默認(rèn)是自動(dòng)創(chuàng)建設(shè)置消息隊(duì)列的最大消息數(shù)量消息的唯一id,*代表由Redis自動(dòng)生成。格式是"時(shí)間戳-遞增數(shù)字",例如"1644804662707-0"

發(fā)送到隊(duì)列中的消息,稱為Entry。格式就是多個(gè)key-value鍵值對##創(chuàng)建名為users的隊(duì)列,并向其中發(fā)送一個(gè)消息,內(nèi)容是:{name=jack,age=21},并且使用Redis自動(dòng)生成ID:6379>XADDusers*namejackage21"1644805700523-0"讀取消息的方式之一:XREAD例如,使用XREAD讀取第一個(gè)消息:基于Stream的消息隊(duì)列-XREAD每次讀取消息的最大數(shù)量當(dāng)沒有消息時(shí),是否阻塞、阻塞時(shí)長要從哪個(gè)隊(duì)列讀取消息,key就是隊(duì)列名起始id,只返回大于該ID的消息0:代表從第一個(gè)消息開始$:代表從最新的消息開始XREAD阻塞方式,讀取最新的消息:在業(yè)務(wù)開發(fā)中,我們可以循環(huán)的調(diào)用XREAD阻塞方式來查詢最新消息,從而實(shí)現(xiàn)持續(xù)監(jiān)聽隊(duì)列的效果,偽代碼如下:基于Stream的消息隊(duì)列-XREAD注意當(dāng)我們指定起始ID為$時(shí),代表讀取最新的消息,如果我們處理一條消息的過程中,又有超過1條以上的消息到達(dá)隊(duì)列,則下次獲取時(shí)也只能獲取到最新的一條,會(huì)出現(xiàn)漏讀消息的問題。STREAM類型消息隊(duì)列的XREAD命令特點(diǎn):消息可回溯一個(gè)消息可以被多個(gè)消費(fèi)者讀取可以阻塞讀取有消息漏讀的風(fēng)險(xiǎn)消費(fèi)者組(ConsumerGroup):將多個(gè)消費(fèi)者劃分到一個(gè)組中,監(jiān)聽同一個(gè)隊(duì)列。具備下列特點(diǎn):基于Stream的消息隊(duì)列-消費(fèi)者組隊(duì)列中的消息會(huì)分流給組內(nèi)的不同消費(fèi)者,而不是重復(fù)消費(fèi),從而加快消息處理的速度消息分流01消費(fèi)者組會(huì)維護(hù)一個(gè)標(biāo)示,記錄最后一個(gè)被處理的消息,哪怕消費(fèi)者宕機(jī)重啟,還會(huì)從標(biāo)示之后讀取消息。確保每一個(gè)消息都會(huì)被消費(fèi)消息標(biāo)示02消費(fèi)者獲取消息后,消息處于pending狀態(tài),并存入一個(gè)pending-list。當(dāng)處理完成后需要通過XACK來確認(rèn)消息,標(biāo)記消息為已處理,才會(huì)從pending-list移除。消息確認(rèn)03基于Stream的消息隊(duì)列-消費(fèi)者組創(chuàng)建消費(fèi)者組:key:隊(duì)列名稱groupName:消費(fèi)者組名稱ID:起始ID

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論