
專注用戶體驗(yàn)設(shè)計(jì)與開發(fā)
-
商務(wù)合作
- 郵箱:123456789@qq.com
- 手機(jī):15323711532
- 座機(jī):0755-84185494
- 地址:廣東省深圳市龍崗區(qū)布吉中興路21號(hào)基業(yè)大廈
Copyright ? 2015 深圳市鑫惠廣網(wǎng)絡(luò)科技有限公司 粵ICP備2023111395號(hào)
一,Redis作緩存服務(wù)器
redis作為緩存服務(wù)器是眾多企業(yè)中的選擇之一,雖然該技術(shù)很成熟但也是存在一定的問題。就是緩存帶來的緩存穿透,緩存擊穿,緩存失效問題,繼而引用分布式鎖。
1.1,緩存穿透
在如今的項(xiàng)目中大多采用垂直的MVC架構(gòu),由service層去調(diào)用DAO層,然后DAO層再去查詢數(shù)據(jù)庫(kù)。而redis作為緩存服務(wù)器就是在service層去調(diào)用DAO層去查詢時(shí)先去緩存服務(wù)器查詢,如果存在則直接返回該數(shù)據(jù),否則再去查詢數(shù)據(jù)庫(kù)。由此可知,這么做大量減少了對(duì)磁盤I/O的操作,減輕了數(shù)據(jù)庫(kù)的壓力。
現(xiàn)在我們假設(shè)一種情況,在數(shù)據(jù)庫(kù)中存在有id為1到1000的數(shù)據(jù)。現(xiàn)在如果有人手動(dòng)去模擬一個(gè)id為1001的請(qǐng)求,那么該數(shù)據(jù)在緩存服務(wù)器中是不存在的,因而便會(huì)去查詢數(shù)據(jù)庫(kù)。那么問題來了,如果是一個(gè)大量無效的請(qǐng)求去查詢數(shù)據(jù)庫(kù)。則勢(shì)必會(huì)對(duì)數(shù)據(jù)庫(kù)造成難以承受的壓力,這種情況就是所謂的緩存穿透。
那如何解決呢?
1,將查詢到的null值直接保存到緩存服務(wù)器中,但是這種做法并不推薦,因?yàn)槿绻谴罅坎煌恼?qǐng)求id同樣會(huì)去查詢數(shù)據(jù)庫(kù)。
2,接口的限流,降級(jí)與熔斷
在項(xiàng)目中對(duì)于重要的接口一定要做限流,對(duì)于以上惡意攻擊的請(qǐng)求除了要限流,還要做好降級(jí)準(zhǔn)備,并且進(jìn)行熔斷,這種做法可以有效控制大量無效請(qǐng)求。
3,布隆過濾器
Bloomfilter就類似于一個(gè)hash set,用于快速判某個(gè)元素是否存在于集合中,其典型的應(yīng)用場(chǎng)景就是快速判斷一個(gè)key是否存在于某容器,不存在就直接返回。布隆過濾器的關(guān)鍵就在于hash算法和容器大小,該做法是多數(shù)企業(yè)所選擇的。
1.2,緩存擊穿
在高并發(fā)下,對(duì)某些 熱點(diǎn)的值進(jìn)行查詢,但是這個(gè)時(shí)候緩存正好過期了,緩存沒有命中,導(dǎo)致大量請(qǐng)求直接落到數(shù)據(jù)庫(kù)上,此時(shí)這種大量的請(qǐng)求可能會(huì)是數(shù)據(jù)庫(kù)崩盤。
解決方案:
1,將熱點(diǎn)key設(shè)置成永不過期。
2,使用互斥鎖。
以上兩種情況均是屬于緩存失效,但里面還有小小的細(xì)節(jié)。那就是存在多個(gè)緩存同時(shí)失效的問題,尤其在高并發(fā)時(shí)間段。為避免這種多個(gè)緩存失效的問題,我們?cè)谠O(shè)置超時(shí)時(shí)間的時(shí)候,可以使用固定時(shí)間+隨機(jī)時(shí)間。以最大限度避免當(dāng)緩存失效時(shí)大量請(qǐng)求去查詢數(shù)據(jù)庫(kù)。
1.3,分布式鎖
通常情況下分布式鎖有三種實(shí)現(xiàn)方式,1. 數(shù)據(jù)庫(kù)樂觀鎖;2. 基于ZooKeeper的分布式鎖;3. 基于Redis的分布式鎖;這里只記錄基于redis的分布式鎖。
作為分布式鎖的要求:
先參看如下代碼:
public List<Goods> goodsManager() { System.out.println("調(diào)用過了業(yè)務(wù)層的goodsManager方法"); return goodsDao.queryAllPage(); // 1,先去查詢緩存服務(wù)器 List<Goods> goodsList = (List<Goods>) redisTemplate.opsForValue().get("goods"); if(goodsList == null){ // 2,申請(qǐng)分布式鎖 RedisConnection conn = redisTemplate.getConnectionFactory().getConnection(); if(conn.setNX("lock".getBytes(), "1".getBytes())){ // 3,給分布式鎖設(shè)置一個(gè)超時(shí)時(shí)間 conn.expire("lock".getBytes(), 60); System.out.println("去數(shù)據(jù)庫(kù)中查詢所有的商品"); // 4,緩存中沒有商品列表的數(shù)據(jù) goodsList = goodsDao.queryAllPage(); // 5,將結(jié)果放入緩存中 redisTemplate.opsForValue().set("goods", goodsList); redisTemplate.expire("goods", 5, TimeUnit.MINUTES); // 6,釋放分布式鎖 conn.del("lock".getBytes()); } else { try { Thread.sleep(50); goodsManager(); } catch (InterruptedException e) { e.printStackTrace(); } } return goodsList; } else { //緩存服務(wù)器中有商品列表的數(shù)據(jù) return goodsList; } }
代碼設(shè)計(jì)思路:
1,請(qǐng)求到來調(diào)用方法。
2,先去redis緩存中查詢是否存在,如果沒有則查詢數(shù)據(jù)庫(kù)。
3,使用原生的連接(setNX)獲得分布式鎖,然后設(shè)置超時(shí)時(shí)間。
設(shè)置超時(shí)時(shí)間的原因在于,如果線程獲得鎖之后不下心崩潰,為防止發(fā)生死鎖因而設(shè)置超時(shí)時(shí)間。
4,查詢數(shù)據(jù)庫(kù)獲得數(shù)據(jù),并保存在數(shù)據(jù)庫(kù)中。
5,釋放鎖。
1.4,雪崩效應(yīng)
簡(jiǎn)單來說,緩存在同一時(shí)間內(nèi)大量鍵(key)過期(失效),而新的緩存又沒有即時(shí)的保存到服務(wù)器中,此時(shí)大量的請(qǐng)求瞬間都落在了數(shù)據(jù)庫(kù)中導(dǎo)致連接異常。
解決方案:
1、可以使用分布式鎖 ,單架構(gòu)項(xiàng)目使用syn
2、永不過期
3、在設(shè)置緩存超時(shí)時(shí)間,固定時(shí)間+隨機(jī)超時(shí)時(shí)間,防止多數(shù)緩存同時(shí)失效。
4、高可用,集群
1.5,redis緩存與springboot整合
在啟動(dòng)函數(shù)中先要開啟緩存注解@Enablecaching
@Cacheable
被該注解標(biāo)注的方法,會(huì)在執(zhí)行前查詢緩存服務(wù)器,如果緩存服務(wù)器有結(jié)果,則直接返回結(jié)果,當(dāng)前方法就不會(huì)執(zhí)行。如果沒有結(jié)果,則在執(zhí)行該方法的方法體,并且將該方法返回值放入緩存服務(wù)器中。
@CachePut
該注解和@Cacheable注解的功能差不多,唯一的區(qū)別在于不管緩存服務(wù)器有沒有對(duì)應(yīng)的值,都會(huì)去調(diào)用相應(yīng)的方法用于添加和更新的方法。
@CacheEvict
刪除指定的緩存,一般用于刪除方法的使用 。
二,Redis持久化
### 2.1,redis提供兩種持久化方式:
2.2,RDB原理分析
RDB持久化有兩種操作方式,手動(dòng)操作進(jìn)行持久化。
bgsave和save最大的區(qū)別在于bgsave不會(huì)阻塞客戶端的寫操作,但是如果bgsave執(zhí)行失敗,Redis默認(rèn)將停止接受接入操作,否則就沒人會(huì)注意到災(zāi)難的發(fā)生,如果不希望這樣做,可以將
stop-writes-on-bgsave-error yes設(shè)置為no
另一種為自動(dòng)觸發(fā)持久化,首先我們可以在配置文件中配置快照的規(guī)則。
save 900 1 當(dāng)900秒以內(nèi)執(zhí)行1個(gè)寫命令,使用快照備份
save 300 10 當(dāng)300秒以內(nèi)執(zhí)行10個(gè)寫命令,使用快照備份
save 60 10000 當(dāng)60秒以內(nèi)執(zhí)行10000個(gè)寫命令,使用快照備份
注意:redis執(zhí)行備份命令時(shí),將禁止寫入命令
2.3,AOF原理分析
AOF的整個(gè)流程大體來看可以分為兩步,一步是命令的實(shí)時(shí)寫入,第二步是對(duì)aof文件的重寫,重寫是為了減少aof文件的大小。
AOF文件追加大致流程為:命令寫入-->追加到aof_buf(緩沖區(qū)) -->同步到aof磁盤。為什么要先寫入buf緩沖區(qū)在同步到磁盤呢?因?yàn)槿绻麑?shí)時(shí)寫入便會(huì)帶來大量的磁盤I/O操作,會(huì)很大程度上降低系統(tǒng)的性能。
關(guān)于AOF持久化大概有以下幾種配置。
2.4,Redis內(nèi)存回收策略
在redis.conf中的配置項(xiàng)maxmemory-policy用于配置redis的內(nèi)存回收策略,當(dāng)內(nèi)存達(dá)到最大值時(shí)所采取的內(nèi)存處理方式。
redis提供六種內(nèi)存淘汰策略
volatile-lru: allkeys-lru: volatile-random: allkeys-random: volatile-ttl: noeviction(默認(rèn)):
在內(nèi)存回收機(jī)制中,LRU算法和TTl算法在redis中都不是精準(zhǔn)計(jì)算,而是一個(gè)近似算法。redis默認(rèn)有一個(gè)探測(cè)數(shù)量的配置maxmemory-samples 默認(rèn)為3。
三,Redis高可用
3.1,主從復(fù)制
在用戶量非常龐大的時(shí)候,單臺(tái)redis肯定是完全不夠用的。因此更多的時(shí)候我們更希望可以讀/寫分離,讀/寫分離的前提就是讀操作比寫操作頻繁的多,將數(shù)據(jù)放在多臺(tái)服務(wù)器上那么久可以消除單臺(tái)服務(wù)器的壓力。
因此對(duì)于服務(wù)器的搭建如圖:
假設(shè)一臺(tái)服務(wù)器負(fù)責(zé)寫操作,其余三臺(tái)為讀操作,以此實(shí)現(xiàn)一個(gè)獨(dú)寫分離的緩存功能。但是很明顯存在一種弊端,就是其余三臺(tái)讀取數(shù)據(jù)的服務(wù)器它們之間的數(shù)據(jù)是不能夠進(jìn)行同步的。這樣便造成數(shù)據(jù)不一致的情況,此時(shí)就需要對(duì)它們之間進(jìn)行一個(gè)數(shù)據(jù)上的互通。
簡(jiǎn)單介紹一下主從復(fù)制的概念,如上圖 Master為主,負(fù)責(zé)寫入數(shù)據(jù)的操作,其余 三臺(tái)為從(Slave),負(fù)責(zé)讀取數(shù)據(jù)操作。當(dāng)有數(shù)據(jù)寫入時(shí),根據(jù)配置好的屬性自動(dòng)將更新的數(shù)據(jù)復(fù)制到其余三臺(tái)服務(wù)器中,這樣便實(shí)現(xiàn)了服務(wù)器之間的數(shù)據(jù)一致性。
主從復(fù)制的大致流程:
1、保證主服務(wù)器(Master)的啟動(dòng)。
2、當(dāng)從服務(wù)器啟動(dòng)時(shí),發(fā)送SYNC命令給主服務(wù)器。主服務(wù)器接受到同步命令時(shí),就是執(zhí)行bgsave命令備份數(shù)據(jù),但是主服務(wù)器并不會(huì)拒絕客戶端的寫操作,而是將來自客戶端的寫命令寫入緩沖區(qū)。從服務(wù)器在未收到主服務(wù)器的備份快照文件之前,會(huì)根據(jù)配置決定使用現(xiàn)有數(shù)據(jù)響應(yīng)客戶端還是返回錯(cuò)誤。
3、當(dāng)bgsave命令被主服務(wù)器執(zhí)行完后,開始向從服務(wù)器發(fā)送備份文件,這個(gè)時(shí)候從服務(wù)器就會(huì)丟棄現(xiàn)有的所有數(shù)據(jù),開始載入發(fā)送過來的快照文件。
4、當(dāng)主服務(wù)器發(fā)送完備份文件后,會(huì)將bgsave執(zhí)行之后的緩存區(qū)內(nèi)的寫命令也發(fā)送給從服務(wù)器,從服務(wù)器完成備份文件解析后,就開始等待主服務(wù)器的后續(xù)命令。
5、同步完成以后,每次主服務(wù)器完成一次寫入命令,都會(huì)同時(shí)往從服務(wù)器發(fā)送同步寫入命令,主從同步就完成了。
從機(jī)配置:
slaveof server port 設(shè)置Master的ip和端口
masterauth root 設(shè)置Master的密碼
到此為止,就完成了嗎?
并不是,以上步驟只是完成了主從復(fù)制,并沒有完成讀寫分離。并且,如果主(Master)服務(wù)器宕機(jī),那整個(gè)緩存服務(wù)器就全部掛掉了。==而且作為從(Slave)服務(wù)器時(shí)不可以進(jìn)行寫的操作,==那又如何解決呢(哨兵模式)?
3.2,哨兵模式
1,什么時(shí)哨兵模式?
當(dāng)Master宕機(jī)以后需要手動(dòng)的把一臺(tái)Slave切換為Master,這種方式需要人工干預(yù),費(fèi)時(shí)費(fèi)力。因此哨兵模式可以幫助我們解決這個(gè)問題。
2,簡(jiǎn)述哨兵模式
3,哨兵模式配置
3.1,#配置哨兵配置文件:
redis/src/sentinel.conf
3.2,#禁止保護(hù)模式
protected-mode no
3.3,#配置監(jiān)聽的主服務(wù), 最后的2表示當(dāng)2個(gè)或2個(gè)以上的哨兵認(rèn)為主服務(wù)不可用才會(huì)進(jìn)行故障切換
sentinel monitor 服務(wù)器名稱(自定義) 主服務(wù)ip 端口 2
3.4,#定義服務(wù)密碼
sentinel auth-pass 服務(wù)器名稱(和上面相同) 密碼
3.5,#啟動(dòng)哨兵模式;
./redis-sentinel sentinel.conf
4,其他相關(guān)配置
sentinel down-after-milliseconds : 指定哨兵在檢測(cè)redis服務(wù)時(shí),當(dāng)redis服務(wù)在一個(gè)毫秒數(shù)內(nèi)都無法回答時(shí),單個(gè)哨兵認(rèn)為的主觀下線時(shí)間,默認(rèn)為30秒。
sentinel failover-timeout: 指定故障切換運(yùn)行的毫秒數(shù),當(dāng)超過這個(gè)毫秒數(shù)時(shí),就認(rèn)為切換故障失敗,默認(rèn)3分鐘。
sentinel notification-script: 指定哨兵檢測(cè)到redis實(shí)例異常時(shí),調(diào)用的報(bào)警腳本。
3.3,分片集群
分片集群原理在于多個(gè)緩存服務(wù)器之間兩兩相互通信,每個(gè)復(fù)制集具有一個(gè)主實(shí)例和多個(gè)從實(shí)例。并且每個(gè)復(fù)制集朱保存一部分?jǐn)?shù)據(jù)庫(kù)中的鍵值對(duì),解決了主從復(fù)制集中總數(shù)據(jù)存儲(chǔ)量最小實(shí)例的限制,大大擴(kuò)大了緩存服務(wù)器的大小。
其結(jié)構(gòu)圖如下:
1,分片集群特點(diǎn)
1、Client與redis節(jié)點(diǎn)直接連接,不需要中間proxy層。
2、 redis-cluster把所有的物理節(jié)點(diǎn)映射到[0-16383]slot(插槽)上,cluster 負(fù)責(zé)維護(hù)。
3、所有的redis節(jié)點(diǎn)彼此互聯(lián)(PING-PONG機(jī)制),內(nèi)部使用gossip二進(jìn)制協(xié)議優(yōu)化傳輸數(shù)據(jù)。
4、 節(jié)點(diǎn)的失效檢測(cè)是通過集群中超過半數(shù)的節(jié)點(diǎn)檢測(cè)失效時(shí)才生效。
==問題:Redis 集群中內(nèi)置了 16384 個(gè)哈希槽,那他是如何決定將key放入哪個(gè)插槽的?==
當(dāng)Redis 集群中放置一個(gè) key-value 時(shí),redis 先對(duì) key 使用 crc16 算法算出一個(gè)結(jié)果,然后把結(jié)果對(duì) 16384 求余數(shù),這樣每個(gè) key 都會(huì)對(duì)應(yīng)一個(gè)編號(hào)在 0-16383 之間的哈希槽,redis 會(huì)根據(jù)節(jié)點(diǎn)數(shù)量大致均等的將哈希槽映射到不同的節(jié)點(diǎn)。
2,集群搭建步驟
前置條件:
刪除redis/src下的appendonly.aof,dump.rdb,nodes-6379.conf3個(gè)文件。
1、修改redis.conf,配置集群信息開啟集群,
cluster-enabled yes 指定集群的配置文件,
cluster-config-file nodes- 端口.conf
2、用redis-trib.rb搭建集群因?yàn)閞edis-trib.rb是用Ruby實(shí)現(xiàn)的Redis集群管理工具,所以我們需要先安裝ruby的環(huán)境.
2.1、安裝ruby
yum -y install zlib ruby rubygems
2.2、安裝rubygems的redis依賴
gem install -l redis-3.3.0.gem
3、安裝好依賴的環(huán)境之后,我們就可以來使用腳本命令了
注意:此腳本文件在我們的解壓縮目錄src下。
執(zhí)行命令:
./redis-trib.rb create --replicas 0 192.168.10.167:6379 192.168.10.167:6380 192.168.10.167:6381 開放16379 redis端口+1W--replicas 0:指定了從數(shù)據(jù)的數(shù)量為0。
4、查看集群狀態(tài)
通過客戶端輸入以下命令:
cluster nodes:這個(gè)命令可以查看插槽的分配情況
整個(gè)Redis提供了16384個(gè)插槽,./redis-trib.rb 腳本實(shí)現(xiàn)了是將16384個(gè)插槽平均分配給了N個(gè)節(jié)點(diǎn)。
end:如果你覺得本文對(duì)你有幫助的話,記得關(guān)注點(diǎn)贊轉(zhuǎn)發(fā),你的支持就是我更新動(dòng)力。
專注用戶體驗(yàn)設(shè)計(jì)與開發(fā)
Copyright ? 2015 深圳市鑫惠廣網(wǎng)絡(luò)科技有限公司 粵ICP備2023111395號(hào)