經驗整理-1-Redis-1-100-@

否使用過Redis集羣,集羣的高可用怎麼保證,三種集羣的原理是什麼?

主從模式:若master掛掉,則redis無法對外提供寫服務。一主多從,從庫只讀,同步接收主庫數據
Sentinel哨兵模式:着眼於高可用,在master宕機時會自動將slave提升爲master,繼續提供服務
Redis Cluster:着眼於擴展性,在單個redis內存不足時,使用Cluster進行分片存儲

 

?爲什麼選Redis?爲什麼使用Redis集羣?怎麼安裝集羣Redis?

爲什麼選Redis:因爲線程安全的
爲什麼使用集羣:單機無法發揮多核CPU性能
怎麼安裝集羣Redis:ruby來安裝集羣redis,選擇redis-cluster集羣搭建方案, 把集羣數據庫分成了 16384 個哈希槽;Redis集羣使用CRC16對key進行hash,當我們的存取的key到達的時候,redis會根據crc16的算法得出一個結果,然後把結果對 16384 求餘數,對應一個編號在 0-16383 之間的哈希槽(每一個節點對應着一部分哈希槽)
安裝過程:
1.
建起一臺 redis.conf設置爲啓動集羣模式的redis實例,比如9001端口,然後把 9001 實例 複製到另外五個文件夾中(cp -rf ),唯一要修改的就是 redis.conf 中的所有和端口的相關的信息.
2.啓動9001-9006六個節點:比如,/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9006/redis/etc/redis.conf
3安裝 ruby 和相關接口
4.使用 ruby命令來創建集羣模式,比如,設置主從複製比例爲1:1,前三個爲主節點端口,後三個爲對應從節點端
5。程序裏需要配置cluster:對應的節點6個端口
四、原理:

?動態擴容、增加節點和減少節點,重新分配槽大小?

Redis 集羣中總共有且僅有 16383 個 solt,默認情況會給我們平均分配,當然你可以指定,後續的增減節點也可以重新分配

Redis集羣支持事物嗎

Redis集羣不支持事物,只有節點內部才支持。如果想實現,只能靠三方插件,比如lua來寫腳本實現事物、、

?redis如何使用二級緩存?

mvn引入Ehcache工具類,通過Redis+ehCache實現兩級級緩存,相當於在前面加了ehCache做爲一級緩存,查時優先查它,如果不存在,再查redis(同時更新一級緩存ehCache),最後查不到再查mysql(同時更新二級緩存redis)

緩存雪崩是什麼?解決方案?(併發查數據庫,搞壞數據庫),緩存擊穿呢(惡意攻擊一條)

緩存擊穿緩存雪崩有點像.緩存雪崩是因爲大面積的緩存失效,打崩了DB,而緩存擊穿不同的是緩存擊穿是指一個Key非常熱點,在不停的扛着大併發,大併發集中對這一個點進行訪問,當這個Key在失效的瞬間,持續的大併發就穿破緩存,直接請求數據庫,就像在一個完好無損的桶上鑿開了一個洞。)
答:當某個key失效的時候,有大量線程來訪問查詢大量原本應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機,造成系統的崩潰

答:1:在緩存失效後,通過加鎖(分佈式鎖zk)或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數庫和寫redis緩存,其他線程等待。(影響吞吐量)
1:在緩存失效後,通過隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數庫和寫redis緩存,其他線程等待。(最好的方法)
3:不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻。(每個Key的失效時間都加個隨機值)
還有一種方案:
事前:儘量保證整個redis集羣的高可用性,發現機器宕機儘快補上。選擇合適的內存淘汰策略。
事中:本地ehcache緩存+ hystrix限流&降級,避免MySQL崩掉
事後:利用redis持久化機制保存的數據儘快恢復緩存

答:巧記:三個區別
1)緩存雪崩和緩存擊穿都是redis過期查mysql,區別是
緩存雪崩是正常業務大量不同key同時到期,才都去查mysql。---解決
緩存擊穿是被惡意攻擊頻繁查詢一條數據,這條數據剛好到期;---解決
2)緩存穿透是redis沒有,繞過redis穿透到了mysql----解決

通用的解決方案:
 

setRedis(Key,value,time + Math.random() * 10000);

4。熱點數據永遠不過期(哈哈,不太可取)
5:Ehcache做二級緩存,A1爲原始緩存,A2爲拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置爲短期,A2設置爲長期(此點爲補充)

舉個簡單的例子我瞭解的,目前電商首頁以及熱點數據都會去做緩存 ,一般緩存都是定時任務去刷新,或者是查不到之後去更新的,但是有一個問題。如果所有首頁的Key失效時間都是12小時,中午12點刷新的,我零點有個秒殺活動大量用戶涌入,假設當時每秒 6000 個請求,本來緩存在可以扛住每秒 5000 個請求,但是緩存當時所有的Key都失效了。此時 1 秒 6000 個請求全部落數據庫,數據庫必然扛不住,它會報一下警,真實情況可能DBA都沒反應過來就直接掛了。此時,如果沒用什麼特別的方案來處理這個故障,DBA 很着急,重啓數據庫,但是數據庫立馬又被新的流量給打死了。這就是我理解的緩存雪崩。同一時間大面積失效,那一瞬間Redis跟沒有一樣,那這個數量級別的請求直接打到數據庫幾乎是災難性的,你想想如果打掛的是一個用戶服務的庫,那其他依賴他的庫所有的接口幾乎都會報錯,如果沒做熔斷等策略基本上就是瞬間掛一片的節奏,你怎麼重啓用戶都會把你打掛,等你能重啓的時候,用戶早就睡覺去了,並且對你的產品失去了信心,什麼垃圾產品。

緩存穿透是什麼?解決方案?(併發查數據庫,搞壞數據庫)?

答:Redis緩存穿透:比如用戶查詢數據,本身沒有創建過數據,在緩存中找不到,比如,id爲“-1”每次都要去數據庫再查詢一遍(低效地查兩次,之後還頻繁來查,這樣就多次穿過緩存,給數據庫增加壓力),然後返回空。這樣請求就繞過緩存直接查數據庫,這也是經常提的緩存命中率問題
答:

1、如果查詢數據庫也爲空,直接設置一個默認值存放到緩存,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問數據庫,這種辦法最簡單粗暴。(比如我們都要去查用戶信息是否存在,不存在,就標記爲notfound,存入緩存,下次再找不到就返回這個緩存裏的標記作爲找不到的標識,不會查數據庫)
注意:再給對應的ip存放真值的時候,需要先清除對應的之前的空緩存。
2、最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力
 

熱點key是什麼?解決方案?(併發存redis,搞壞redis)

答:熱點key即失效熱點訪問併發:比如,當某個key失效的時候,有大量線程來訪問查詢,剛好都從數據庫回來查到數據同時去redis構建緩存導致負載增加,系統崩潰

答:解決辦法:

①使用鎖,單機用synchronized,lock等分佈式用分佈式鎖zk,只允許一個線程查詢數庫和寫redis緩存

②緩存過期時間不設置,而是設置在key對應的value裏。如果檢測到存的時間超過過期時間則異步更新緩存。

③在value設置一個比過期時間t0小的過期時間值t1,當t1過期的時候,延長t1並做更新緩存操作。

4、過期時間後面帶上一些值

?你們如何解決分佈式定時任務冪等性重複性的?

redis分佈式鎖,發現存在了,就跳過

?redis分佈式鎖原理與實現?

過程分析:

1、A通過setnx(lockkey,currenttime+timeout)命令,對lockkey進行setnx,將value值設置爲當前時間+鎖超時時間;
2、如果返回值爲1,說明前面沒有人,也就是沒有其他用戶擁有這個鎖,A就能獲取鎖成功;
3、A完成後,要釋放擁有的鎖,del(lockkey),也就是刪除redis中該鎖的內容,接下來的用戶才能進行重新設置鎖新值。
 

?redis定時取消訂單還原庫存,延時操作的原理(訂閱Pub/Sub頻道 )?

在訂單生產時,設置一個key,同時設置10分鐘後過期, 我們在後臺實現一個監聽器,監聽key的實效,監聽到key失效時將後續邏輯加上。 當然我們也可以利用rabbitmq、activemq等消息中間件的延遲隊列服務實現該需求。

?點贊排名、好友列表的原理(set--sismember/sadd )?

一、點贊排名還是使用zset好一點,如果沒有排名也可以用set;
二、好友列表只需要考慮去重,用set;如下:

sismember 我的ID 好友ID   (如果存在會返回好友信息,就不要在插入了)

 

?redis對賬找差異的原理(set--sinterstore、sdiffstore)?

1、分別得到我方的交易集合set1和三方的交易集合set2。
sadd {account}:localSet  訂單信息1 訂單信息2 ,表示給我方添加兩個訂單進redis這個{account}:localSet集合。{account}這樣寫是爲了集羣到同一個槽裏,集羣只看{}裏面的值。
sadd {account}:outerSet  訂單信息11 訂單信息22 同理,和到第三方。

2、然後使用redis的sinterstore功能對兩個SET集合進行交集得到sinter1集合,
sinterstore 新名稱sinters1 {account}:localSet {account}:outerSet 

3、把三方賬務集合與我方記錄集合分別和交集結果比較,
sdiffstore 新名稱localDiff {account}:localSet sinter1,表示得出我方單邊多的。
sdiffstore 新名稱outerDiff {outerDiff}:outerSet sinter1,表示得出第三方單邊多的。

4、然後把差集拿出來,
smembers localDiff 得到我方單邊多的。
smembers outerDiff 得出第三方單邊多的。

逐個對比一下兩個集合具體錯在哪,記錄差異賬,等待人工審覈。如果是我方單邊多賬,可能是三方還未結算,這種會等第二天對賬發現三方單邊多賬,再來查一下前一天的對賬結果裏是否有多的記錄,如果有就抹掉,沒有就是差賬(可能是漏記)。

 

?redis實時排行榜的原理(zset--zunionstore)?

舉例,商品收藏月排行榜實現。
(首次如果需要添加,使用語句:zadd rank:202001 3 商品ID1 5 商品ID2 ,表示給兩個商品初始3和5的收藏數)
1、創建收藏的時候,集合名稱=商品_當前年月,增加減少語句(不存在就新增):
zincrby 集合名稱 該成員的變化量 集合成員姓名
比如 
zincrby  rank:202001 1 商品ID1   ,表示該商品ID1累加一個收藏,如 zincrby rank:202001 1 商品ID2   ,表示該商品ID2增加一個收藏
2、獲取自然月排行榜前10的方法如下,zrange  rank:202001 0 9 withscores,倒序;zrevrange rank:202001 0 9 withscores,升序
3、如果要統計日榜,就重新建一個有序集合,名稱以rank:20200101開頭。
如果是周榜,就得算是哪周了,算到哪周的方法:new SimpleDateFormat("yyyy-MM-dd").applyPattern("W").format(date);//算一月中的第幾周
4.如果是實時週期性,從當前時間至前面一週內,那就得用:zunionstore 放入新集合的名稱 成員個數 原集合1名稱 原集合2名稱 原集合3名稱 weights 1 1 1 ,表示合併3個原集合至新集合裏,權重都是1(weights 權重可以不設置,默認就是1),每個集合中相同的商品ID因爲是唯一的,所以對應的收藏數會加相的
計算給定的一個或多個有序集的並集,並存儲在新的 key 中
實時周榜獲取方法:
1)zunionstore rank:last_week 7 rank:20200101 rank:20200102 rank:20200103 rank:20200104 rank:20200105 rank:20200106 rank:20200107 WEIGHTS 1 1 1 1 1 1 1
2)實時月榜獲取方法,也用zunionstore ,把最近一個月的每天的給並集在一起。
如果要精確到秒的話,那隻能按秒存了,再並集秒

?redis秒殺減庫存,集羣高併發的原理(列表(隊列性質)list--rpush、lpop,Hash---hset )?Hash(商品及規格-用戶-數量)

高性能系統的優化原則:寫入內存而不是寫入硬盤,異步處理而不是同步處理,分佈式處理
一、最初版,用戶可以買多單。
1、ZK實現的分佈式計數器DistributedAtomicInteger存庫存,到了100個就攔截
2、list存搶購成功的用戶信息及商品序號。
rpush key value,插入秒殺請求。然後快速返回。
lpop key,(異步)讀取秒殺成功者,依次做下單後續處理。

二、升級版,防刷單,限用戶每人一單
1、list存庫存,將庫存100先放進一個redis的list結構中,來一個用戶pop一個,直到最後pop出第100個秒殺結束。提示“搶光了!”pop成功則進入下單流程,插入新訂單到數據庫,初始狀態0未支付(0:未支付 1:已支付,2:30min已過期)。對於過期的則歸還庫存到redis的庫存list中等待pop。命令如下:
rpush key value,插入庫存。
lpop key,減庫存。
2、Hash來存放搶購成功的用戶ID,防止重複搶購刷單(商品key->(用戶key,val))。等到,命令如下:
hset  商品key 用戶key MapValue  (如果存在會覆蓋,但返回0)
3、list存搶購成功的用戶信息及商品序號。(最好用mq來異步處理,統一消費,商品類型區分不同商品
rpush key value,插入秒殺請求。然後快速返回。
lpop key,(異步)讀取秒殺成功者,依次做下單後續處理。

(string類型如果value是Int等數據類型,可以用Incre key 1 //實現加1
,decrby key 1 //是減)

?redis購物車基本功能,集羣高併發的原理(列表(隊列性質)list--rpush、lpop,Hash---hset )?Hash(用戶-商品及規格-數量)

spu(商品)和sku(規格單品)

hset 存/修改:hset appid:openid:cart 3:1 10  (hset 用戶ID相關  spu_id:sku_id   數量)
hgetall 取所有:hgetall appid:openid:cart
hdel 刪除:hdel appid:openid:cart 3:1
hincrby 增加,減少:hincrby appid:openid:cart 3:1 -1

?發佈與訂閱(pub/sub)?

訂閱一個頻道:subscribe   頻道名
通過命令publish向頻道發送信息:publish   頻道名    “消息”


如果對方究極TM追問Redis如何實現延時隊列?(zset--zadd、zrangebyscore)


延時隊列:使用sortedset,拿時間戳作爲score,消息內容作爲key調用zadd來生產消息,消費者用zrangebyscore指令獲取N秒之前的數據輪詢進行處理
zadd  某點前的時間戳long值 key 
zrangebyscore key -inf 某點前的時間戳long值


zrange 和zrangebyscore 區別:
zrange 是指索引:zrangeby key 0 10
zrangebyscore 是指值:

zrangebyscore key -inf 時間戳long值//小於等於
zrangebyscore key -inf 時間戳long值//小於等於

 

?redis持久化原理?結合 redis 宕機了再重啓說明一下處理過程?()

一、持久化原理:(啓動時會先檢查AOF文件是否存在,如果不存在就嘗試加載RDB)。
redis 持久化的兩種方式,RDB:RDB 持久化機制,是對 redis 中的數據執行週期性的持久化,恢復速度快,丟失多。AOF:AOF 機制對每條寫入命令作爲日誌,,恢復速度快慢,日誌文件大,丟失少。
一般採用同時開啓開啓兩種持久化方式,優先用 增量日誌文件AOF 來保證數據不丟失,作爲數據恢復的第一選擇; 其次AOF損壞不可用時,用數據RDB快照快迅恢復大體數據,避免所有數據丟失;
二、redis 宕機了再重啓:如果 redis 宕機重啓,自動從磁盤上加載增量日誌文件AOF恢復數據,如果AOF損壞或恢復故障,會從RDB日誌快照裏拿到數據恢復。
(在redis實例重啓時,會使用RDB持久化文件重新構建內存,再使用AOF重放近期的操作指令來實現完整恢復重啓之前的狀態。?)

?redis 宕機怎麼辦,怎麼保持數據一致性?平時沒宕機呢,怎麼保持數據一致性?

答1:1)redis有持久化,啓動時會先檢查AOF文件是否存在,如果不存在就嘗試加載RDB,恢復數據。
2)如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,就重啓就好了。
答2:
1)如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,不用優化。定時任務掃秒會重置。或cannal訂況MYSQL更準
2)redis讀行串行化,串到一個內存隊列裏去,這樣就可以保證一定不會出現不一致
的情況----導致系統的吞吐量會大幅度的降低,加機器了

Redis的同步機制瞭解麼?

Redis可以使用主從同步,從從同步。第一次同步時,主節點做一次bgsave,並同時將後續修改操作記錄到內存buffer,待完成後將RDB文件全量同步到複製節點,複製節點接受完成後將RDB鏡像加載到內存。加載完成後,再通知主節點將期間修改的操作記錄命令(好像是AOF)同步到複製節點進行重放就完成了同步過程後續的增量數據通過AOF日誌同步即可,有點類似數據庫的binlog

數據怎麼主從同步的呢?

slave啓動,會發送一個psync命令給master ,
1)如果是第一次連接到master,他會觸發全量複製。master就會啓動一個線程,生成RDB快照,同步給從節點。slave寫入RDB至本地。
2)master會把新的寫請求命令,同步給slave。

Redis裏面所有的key過期時間了怎麼處理的?(清除策略)

一、Redis刪除策略(過期策略),是有定期刪除+惰性刪除兩種。
定期刪除,好理解,默認100s就隨機抽一些設置了過期時間的key,去檢查是否過期,過期了就刪了。
惰性刪除,見名知意,惰性嘛,我不主動刪,我懶,我等你來查詢了我看看你過期沒,過期就刪了還不給你返回
二、內存淘汰策略機制!
官網上給到的內存淘汰機制是以下幾個:
巧記:最久內存隨機頻率
           1:內存不夠,寫就報錯
            2:通過LRU算法驅逐最久沒有使用的鍵
            3:從設置了過期時間的鍵集合中驅逐最久沒有使用的鍵
            4:從所有key隨機刪除
            5:從過期鍵的集合中隨機驅逐
            6:從配置了過期時間的鍵中驅逐馬上就要過期的鍵
            7:從所有配置了過期時間的鍵中驅逐使用頻率最少的鍵
            8:從所有鍵中驅逐使用頻率最少的鍵
noeviction:返回錯誤當內存限制達到並且客戶端嘗試執行會讓更多內存被使用的命令(大部分的寫入指令,但DEL和幾個例外)
allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數據有空間存放。
volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新添加的數據有空間存放。
allkeys-random: 回收隨機的鍵使得新添加的數據有空間存放。
volatile-random: 回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過期集合的鍵。
volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新添加的數據有空間存放。
如果沒有鍵滿足回收的前提條件的話,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。

 


  2.1 redis 客戶端

    答:Lettuce (springboot默認使用的客戶端),Jedis
    jedis和lettuce有什麼區別:
    jedis採用的是直連redis server,單jedis實例時,是線程不安全的(發送命令和獲取返回值時使用全局變量RedisOutputStream和RedisInputStream)。要使用連接池pool(配置引用jedis.pool即可),這樣每個線程單獨使用一個jedis實例。由此帶來的問題時,如果線程數過多,帶來redis server的負載加大。有點類似於BIO的模式。

lettuce採用netty連接redis server,實例可以在多個線程間共享,不存在線程不安全的情況,這樣可以減少線程數量。當然,在特殊情況下,lettuce也可以使用多個實例。有點類似於NIO的模式

性能差不多,jedis連接池比lettuce弱一點

 redis高危命令

    答:keys flushdb (清空數據庫) flushall (清空所有記錄)


  2.3 redis清除策略
  https://www.cnblogs.com/rinack/p/11549362.html
  刪除策略:定期刪除+惰性刪除(使用時,檢查是否過期,過期刪除)
  淘汰策略:1:內存不夠時,寫時,返回錯誤
            2:通過LRU算法驅逐最久沒有使用的鍵
            3:從設置了過期時間的鍵集合中驅逐最久沒有使用的鍵
            4:從所有key隨機刪除
            5:從過期鍵的集合中隨機驅逐
            6:從配置了過期時間的鍵中驅逐馬上就要過期的鍵
            7:從所有配置了過期時間的鍵中驅逐使用頻率最少的鍵
            8:從所有鍵中驅逐使用頻率最少的鍵

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章