基于c語言開發(fā)高性能key-value存儲非關(guān)系形數(shù)據(jù)庫數(shù)據(jù)庫较店。
一 基礎(chǔ)知識
1.1 五種類型操作
1.1.1 String
1. 腳本操作:
# 添加
set key value
# 獲取
get key
# 刪除數(shù)據(jù)
del key
# 添加或者修改多個數(shù)據(jù)
mset key1 value1 key2 value2
# 獲取多個數(shù)據(jù)
mget key1 key2
1.1.2 hash
每一個key對應(yīng)的value,類似HashMap集合存儲數(shù)據(jù)的結(jié)構(gòu)容燕。底層使用哈希表結(jié)構(gòu)實現(xiàn)數(shù)據(jù)存儲梁呈。
1. 腳本操作:
# 添加或者修改數(shù)據(jù)
hset key field value
# 獲取數(shù)據(jù)
hget key fileld
hgetall key
# 刪除數(shù)據(jù)
hdel key field1 [field2]
# 添加或者修改數(shù)據(jù)
hmset key field1 value1 field2 value2
#獲取多個數(shù)據(jù)
hmget key field1 field2
1.1.3 List
存儲一個key對應(yīng)多個數(shù)據(jù),并且數(shù)據(jù)的存入和取出順序是一致的缰趋。并且底層使用雙向列表捧杉。
1. 腳本操作:
# 添加/修改數(shù)據(jù)
lpush key value1 [value2] ......
rpush key value1 [value2] ......
# 獲取數(shù)據(jù)
lrange key start stop
lindex key index
# 獲取并移除數(shù)據(jù)
lpop key
rpop key
1.1.4 Set
一個key對應(yīng)多個value值,不能存儲重復(fù)元素秘血。存儲大量數(shù)據(jù)味抖,查詢效率更高。
1. 腳本操作:
# 添加數(shù)據(jù)
sadd key value1 [value2]
# 獲取全部數(shù)據(jù)
smembers key
# 刪除數(shù)據(jù)
srem key member1 [member2]
1.2 key
1.2.1 key基本操作
key是一個字符串灰粮,在redis中通過key能獲取值仔涩。
1. 腳本操作:
# 刪除指定key
del key
# 獲取key是否存在
exists key
# 獲取key類型
type key
# 設(shè)置key有效時間
expire key seconds
pexpire key milliseconds
# 獲取key有效時間
ttl key
pttl key
二 持久化
reids中數(shù)據(jù)存儲在內(nèi)存中。如果redis一直運行粘舟,則數(shù)據(jù)會一直保存在內(nèi)存中熔脂,我們隨時都可以讀取。但是在現(xiàn)實中柑肴,redis可能可能死機霞揉,或者部署redis服務(wù)器崩潰了。需要重啟服務(wù)器或者redis晰骑,redis原先內(nèi)存中數(shù)據(jù)就會丟适秩。所以為了保證redis中數(shù)據(jù)的安全性,redis就設(shè)計了持久化硕舆。redis存儲數(shù)據(jù)時候秽荞,會同時將數(shù)據(jù)存儲到硬盤上。如果重啟redis抚官,將硬盤數(shù)據(jù)恢復(fù)到redis中扬跋。Redis持久化到硬盤中兩種方式,RDB(日志指令)和AOF(數(shù)據(jù)快照)凌节。
2.1 持久化基本概述
1. 什么是持久化:
將內(nèi)存種數(shù)據(jù)存儲到內(nèi)盤中等永久存儲钦听,在一定的時機再從新恢復(fù)數(shù)據(jù)。
2. 持久化兩種方式:
計算機中數(shù)據(jù)是二進制存儲倍奢,將這二進制數(shù)據(jù)原封不動的記錄下來彪见,也叫快照存儲(RDB),保存的是某一時刻數(shù)據(jù)娱挨。
將改變數(shù)據(jù)的操作命令保存下來余指,即保存操作過程,稱為日志(AOF)。
2.2 RDB(快照)
2.2.1 save指令
1. 手動執(zhí)行save指令:
save
2. save指令相關(guān)配置:
#配置本地數(shù)據(jù)庫文件名酵镜,默認dump.rdb碉碉,常設(shè)置為dump-端口號.rdb
dbfilename filename
# 設(shè)置存儲文件名
dir path
# 存儲到本地數(shù)據(jù)庫,是否壓縮
rdbcompression yes|no
# 在讀寫過程是否對RDB格式校驗淮韭,節(jié)約10%的時間消耗
rdbchecksum yes|no
備注:
save指令會阻塞當(dāng)前Redis服務(wù)器垢粮,知道RDB過程完成位置,會造成長時間阻塞靠粪。不建議使用
2.2.2 bgsave
1. 手動執(zhí)行bgsave指令:
bgsave
2. bgsave指令相關(guān)配置:
# 后臺出現(xiàn)錯誤蜡吧,是否停止保存,默認yes
stop-writes-on-bgsave-error yes|no
dbfilename filename
dir path
rdbcompression yes|no
rdbchecksum yes|no
3. 配置bgsave自動執(zhí)行:
監(jiān)控時間key變化量
save second changes
例如:
# 900秒占键,有一個key發(fā)生變化
save 900 1
# 300秒昔善,有10個key發(fā)生變化
save 300 10
# 60秒,有10000個key發(fā)生變化
save 60 10000
完整配置
save second changes
filename filename
dir path
rdbcompression yes|no
rdbchecksum yes|no
stop-writes-on-bgsave-error yes|no
4. bgsave執(zhí)行原理:
針對save命令進行優(yōu)化畔乙,Redis中所有涉及RDB操作都采用bgsava方式筷狼。
當(dāng)客戶端給服務(wù)端發(fā)送一個save指令時候登下,服務(wù)端立馬給客戶端返回一個結(jié)果汪疮,同時創(chuàng)建一個子線程執(zhí)行save操作逢净。
2.2.3 RDB總結(jié)
優(yōu)點:
RDB時一個二進制文件,存儲效率很高牍鞠,比AOF速度快很多咖摹。
缺點:
無法做到實時持久化,容易造成數(shù)據(jù)丟失难述。
因為bgsave都會創(chuàng)建一個子線程萤晴,會犧牲一些性能。
Redis眾多版本中龄广,RDB的二進制文件無法統(tǒng)一硫眯,各個版本的服務(wù)之間數(shù)據(jù)格式無法兼容蕴侧。
2.3 AOF(日志)
以獨立日志方式择同,記錄每次讀寫命令。重啟服務(wù)時净宵,執(zhí)行AOF文件中命令敲才。主要用于解決數(shù)據(jù)實時性,是Redis持久化的主流方式择葡。
2.3.1 AOF日志配置
# 開啟AOF持久化功能
appendonly yes|no
# AOF持久化名字紧武,默認名字為appendonly.aof
appendfilename filename
# AOF保存路徑,和RDB路徑保持一致即可
dir path
# AOF寫數(shù)據(jù)策略敏储,默認everysec
appendfsync always|everysec|no
備注(AOF寫三種策略):
always(每次):每次寫操作都會同步到AOF文件中阻星,性能低。
everysec(每秒):每秒將緩沖區(qū)命令同步到AOF文件中,系統(tǒng)在突然宕機情況會有一秒鐘數(shù)據(jù)丟失妥箕,數(shù)據(jù)準(zhǔn)確性較高滥酥,性能高,建議使用畦幢。
no(系統(tǒng)控制):操作系統(tǒng)控制每次同步到AOF文件周期坎吻。
2.3.2 AOF重寫
對同一數(shù)據(jù)若干指令轉(zhuǎn)化為最終結(jié)果的對應(yīng)指令。
1. 重寫的作用:
將多條命令壓縮成一條宇葱。降低磁盤占用率瘦真,提高恢復(fù)效率。
2. 重寫的規(guī)則:
進程中有時效數(shù)據(jù)黍瞧,并且已經(jīng)超時诸尽,不寫進AOF文件中。
對于無效指令雷逆,直接忽略弦讽。
對一條數(shù)據(jù)的多條寫命令合并成一條命令。
3. 重寫配置:
手動執(zhí)行
bgrewriteaof
自動重寫配置
auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage
4. AOF寫和重寫流程:
2.4 RDB和AOF應(yīng)用場景
1. 對數(shù)據(jù)十分敏感膀哲,建議使用AOF持久化方案:
AOF持久化策略使用everysecond往产,默認一秒鐘同步一次。該策略redis仍然能保持很好的性能某宪,
當(dāng)機器突然出現(xiàn)故障仿村,也就丟失0-1秒鐘數(shù)據(jù)。
2. 數(shù)據(jù)呈現(xiàn)階段有效兴喂,使用RDB持久方案:
良好的保持?jǐn)?shù)據(jù)階段不丟失蔼囊,且恢復(fù)時間快。
三 數(shù)據(jù)刪除與淘汰策略
3.1 過期數(shù)據(jù)刪除(key已經(jīng)過期)
3.1.1 數(shù)據(jù)狀態(tài)
內(nèi)存中數(shù)據(jù)通過TTL指令獲取狀態(tài)
正數(shù): 數(shù)據(jù)在內(nèi)存中有存活時間衣迷。
-1:永久存在畏鼓。
2:已過期數(shù)據(jù),或者被刪除的數(shù)據(jù)壶谒。
3.1.2 Redis中時效性數(shù)據(jù)存儲結(jié)構(gòu)
過期數(shù)據(jù)是一塊獨立的存儲空間云矫,Hash結(jié)構(gòu)。field是value的內(nèi)存地址汗菜,value是過期時間让禀。最終進行過期處理時候,對該空間數(shù)據(jù)進行檢測陨界,當(dāng)時間到期后通過filel找到數(shù)據(jù)地址巡揍,進行相關(guān)操作。
3.1.3 數(shù)據(jù)刪除策略(過期數(shù)據(jù))
在內(nèi)存占用和cpu占用之間尋找一種平衡菌瘪,不能顧此失彼造成redis性能下降腮敌,引發(fā)服務(wù)器宕機和內(nèi)存泄漏。針對過期數(shù)據(jù)刪除策略如下:
定時刪除
惰性刪除
定期刪除
1. 定時刪除:
創(chuàng)建一個定時器,key設(shè)置過期時間糜工,當(dāng)?shù)竭_過期時候斗这,定時器任務(wù)立即執(zhí)行對鍵刪除。
總結(jié):
到時就刪除啤斗,節(jié)約內(nèi)存表箭。但是造成cpu壓力大,影響服務(wù)器響應(yīng)時間和指令吞吐量钮莲。即拿時間換空間免钻。
2. 惰性刪除:
數(shù)據(jù)到達過期時間,不做處理崔拥。下次訪問該數(shù)據(jù)時進行刪除极舔。
內(nèi)存壓力大,長期占用內(nèi)存空間链瓦。但是節(jié)省CPU性能拆魏,發(fā)現(xiàn)時刪除。即拿時間換空間慈俯。
3. 定期刪除:
相對前兩種方案的一種折中方案渤刃。
刪除過程:
Redis啟動服務(wù)器初始化時,讀取配置server.hz的值贴膘,默認為10.
每秒執(zhí)行server.hz次serverCron()-------->databasesCron()--------->activeExpireCycle()
activeExpireCycle()對每個expires[]進行逐一監(jiān)測卖子。
對某個expires[]檢測時,隨機挑選W個key檢測
如果key超時刑峡,刪除key洋闽。
如果一輪中刪除的key的數(shù)量>W*25%,循環(huán)該過程突梦。
如果一輪刪除的key的數(shù)量<W*25%诫舅,檢查下一個expires[*]
W取值=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP屬性值
總結(jié):
周期性輪詢redis庫中時效性數(shù)據(jù),采用隨機抽取數(shù)據(jù)宫患,利用過期的比例來控制刪除頻率刊懈。
檢測頻率可以自定義設(shè)置,內(nèi)存壓力不是很大撮奏,長期占用內(nèi)存的冷數(shù)據(jù)會被持續(xù)的刪除俏讹。即隨機抽查重點抽查当宴。
3.2 數(shù)據(jù)淘汰(redis中內(nèi)存不足)
在redis中執(zhí)行命令之前會調(diào)用freeMemoryIfNeeded()檢查內(nèi)存是否充足畜吊。如內(nèi)存不滿足,則要刪除一些數(shù)據(jù)户矢。清除數(shù)據(jù)策略稱為逐出算法玲献。
3.2.1 策略配置
# 使用最大內(nèi)存,生成環(huán)境上設(shè)置為50%以上
maxmemory ?mb
# 每次選取待刪除數(shù)據(jù)個數(shù)
maxmemory-samples count
# 對數(shù)據(jù)進行刪除,選擇策略
maxmemory-policy policy
刪除數(shù)據(jù)策略(三類八種)
第一類:檢測易失數(shù)據(jù)
volatile-lru:挑選最近最少使用的數(shù)據(jù)淘汰
volatile-lfu:挑選最近使用次數(shù)最少的數(shù)據(jù)淘汰
volatile-ttl:挑選將要過期的數(shù)據(jù)淘汰
volatile-random:任意選擇數(shù)據(jù)淘汰
第二類:檢測全庫數(shù)據(jù)
allkeys-lru:挑選最近最少使用的數(shù)據(jù)淘汰
allkeLyRs-lfu::挑選最近使用次數(shù)最少的數(shù)據(jù)淘汰
allkeys-random:任意選擇數(shù)據(jù)淘汰捌年,相當(dāng)于隨機
第三類:放棄數(shù)據(jù)驅(qū)逐
no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)(redis4.0中默認策略)瓢娜,會引發(fā)OOM(Out Of Memory)
三 原理分析
3.1 基礎(chǔ)概念
1. Redis是單線程還是多線程架構(gòu):
redis整體并非是一個線程,redis在處理網(wǎng)絡(luò)請求和k/v讀寫操作時候是一個線程礼预。而持久化眠砾,異步刪除,集群數(shù)據(jù)同步都是額外線程進行處理托酸。
2. 單線程為啥這么快褒颈?:
redis大部分操作是基于內(nèi)存中。
因為只有一個線程励堡,避免多線程上下文切換和競爭谷丸。
redis底層采用IO多路復(fù)用技術(shù),大量高并發(fā)下应结,提高系統(tǒng)吞吐量刨疼。
3.2 多路復(fù)用
3.2.1 前置知識
1. file descriptor(fd):
文件描述符。(Linux中一些皆文件鹅龄,比如:普通文件揩慕、目錄文件、連接文件扮休、設(shè)備文件等漩绵。)文件描述符是內(nèi)核為了高效管理系統(tǒng)打開文件而產(chǎn)生的一個索引(指針),是一個非負數(shù)肛炮,所有的io操作的系統(tǒng)調(diào)用都是通過fd來操作止吐。
2. 內(nèi)核空間和用戶空間:
內(nèi)核能管理系統(tǒng)所有資源,磁盤讀寫侨糟,網(wǎng)絡(luò)IO讀寫碍扔,內(nèi)存分配回收、進程管理等秕重;他能訪問受保護資源不同,能訪問底層硬件設(shè)備。應(yīng)用程序想要操作底層硬件必須通過內(nèi)核來進行訪問溶耘。
3.2.2 epoll(IO多路復(fù)用分析)
3.2.2.1 阻塞型通信分析
阻塞網(wǎng)絡(luò)IO流程圖:
a 啟動TestSocket服務(wù)->代碼執(zhí)行流程如下:
b ServerSocket ss = new ServerSocket(8888);--->
內(nèi)核調(diào)用socket()方法二拐,返回一個文件描述符3。
調(diào)用bind(3,8888)方法凳兵,將fd為3綁定8888端口百新。
調(diào)用listen(3,50),創(chuàng)建socket監(jiān)聽對象庐扫,fd為3監(jiān)聽8888端口饭望。
c Socket s = ss.accept();
調(diào)用系統(tǒng)函數(shù)accept(3,...) = 5仗哨,服務(wù)端應(yīng)用等待著內(nèi)核響應(yīng),內(nèi)核等待著客戶端連接铅辞,所以應(yīng)用處理阻塞中厌漂。
當(dāng)有客戶端連接創(chuàng)建一個socket連接對象。
d 獲取客戶端發(fā)過來數(shù)據(jù)斟珊。
調(diào)用系統(tǒng)函數(shù)recv(5,...) recvfrom(5,...) recvmsg(5,...)函數(shù)苇倡,內(nèi)核等到客戶端的數(shù)據(jù)發(fā)送。應(yīng)用等待內(nèi)核響應(yīng)囤踩。
內(nèi)核接受到數(shù)據(jù)雏节,返回給應(yīng)用。
e 將數(shù)據(jù)返回到客戶端高职。
應(yīng)用調(diào)用send(5,...)等系統(tǒng)函數(shù)钩乍,將數(shù)據(jù)發(fā)給內(nèi)核。內(nèi)核發(fā)送完畢返回給應(yīng)用怔锌。
總結(jié):
在服務(wù)端接受客戶端數(shù)據(jù)時候寥粹,因為應(yīng)用通過內(nèi)核調(diào)用系統(tǒng)函數(shù)accept(3,...) 、recv(5,...)埃元、send(5,...)涝涤,必須等到內(nèi)核響應(yīng)后,應(yīng)用才能進行下一步操作岛杀。所以等待連接阔拳、等待發(fā)送數(shù)據(jù)、發(fā)送數(shù)據(jù)都是阻塞的类嗤。
因為應(yīng)用是一個主線程執(zhí)行糊肠,所以當(dāng)有多個客戶端進行連接。必須等待上一個線程完全執(zhí)行完主線程遗锣,才能進行下一個線程處理货裹。
3.2.2.2 非阻塞網(wǎng)絡(luò)傳輸分析
異步通信和上面同步通信流程類似。只不過在調(diào)用系統(tǒng)函數(shù)accept(3,...) 精偿、recv(5,...)弧圆、send(5,...)等前,都調(diào)用系統(tǒng)函數(shù)fcntl(5,....)
導(dǎo)致應(yīng)用調(diào)用accept(3,...) 笔咽、recv(5,...)搔预、send(5,...)立馬執(zhí)行下一步,不用等待內(nèi)核處理完成響應(yīng)數(shù)據(jù)叶组。從而導(dǎo)致程序異步拯田。提高
網(wǎng)絡(luò)通信的性能。
但是因為應(yīng)用不等待內(nèi)核處理完畢扶叉,所以應(yīng)用要不斷的輪詢向內(nèi)核獲取數(shù)據(jù)勿锅。
3.2.2.3 一個線程處理多個客戶端連接
linux中提供了select/poll/epoll系統(tǒng)函數(shù)支持這一模型。它支持應(yīng)用程序?qū)⒁粋€或者多個fd交給內(nèi)核枣氧,內(nèi)核檢測fd上的狀態(tài)變化(連接溢十、讀、寫等)
流程分析
a 應(yīng)用程序調(diào)用epoll_create(1024)函數(shù)返回fd為5达吞。
創(chuàng)建epoll對象张弛,創(chuàng)建監(jiān)聽樹,創(chuàng)建隊列
socket函數(shù)酪劫,創(chuàng)建一個fd6
bind函數(shù)吞鸭,將fd6進行綁定
listen將fd6監(jiān)聽
fcntl函數(shù)讓epoll_ctl變成異步。
b 調(diào)用epoll_ctl函數(shù)
創(chuàng)建一個socket節(jié)點到監(jiān)聽樹上(注冊了節(jié)點的監(jiān)聽事件)覆糟。
c epoll_wait 函數(shù)
輪詢的從隊列中取出刻剥,然后處理多個事件。保證了一個線程處理多個客戶端連接滩字。
總結(jié):
epoll模型優(yōu)勢:
沒有fd模型限制造虏,不會隨著fd增加導(dǎo)致IO效率降低。
不需要每次將fd拷貝到內(nèi)核空間麦箍,只需要一次拷貝漓藕,后面復(fù)用。
mmap技術(shù)加速了用戶空間和內(nèi)核空間數(shù)據(jù)訪問挟裂。
3.2.3 Select(IO多路復(fù)用)
總結(jié):
select支持跨平臺的享钞。但是需要自己維護fd_set,每次都需要fdSet從用戶空間拷貝到內(nèi)核空間诀蓉。如果fd較多則是一個耗時操作栗竖。如果fdset中只有少數(shù)的fd在活躍,性能不高渠啤。
3.2.4 高性能的Redis有哪些慢操作划滋?什么樣操作影響性能?
在這里插入圖片描述
四 Redis高可用之-主從復(fù)制
當(dāng)redis服務(wù)不可用時候埃篓,導(dǎo)致應(yīng)用服務(wù)不可用处坪。物理故障可能導(dǎo)致數(shù)據(jù)丟失。redis提供了主從復(fù)制功能架专。
4.1 Redis主從復(fù)制初認識
4.1.1 主從復(fù)制優(yōu)點和弊端
1. 優(yōu)點:
如果主節(jié)點宕機后同窘,從節(jié)點作為主節(jié)點備份頂上來。擴展了主節(jié)點讀能力部脚。
2. 缺點:
a master宕機之后想邦,需要從slave選出一個master。然后其他的slave需要復(fù)制新master數(shù)據(jù)委刘。同時還需要通知客戶端新的master數(shù)據(jù)丧没。這個過程就叫做故障轉(zhuǎn)移鹰椒,但是限制這個過程仍然需要人工參與。
b redis的寫能力依然受到單機的限制呕童。
c redis存儲能力也受單機的限制漆际。
4.1.2 主從復(fù)制流程
1. 建立連接:
2. 數(shù)據(jù)同步:
數(shù)據(jù)同步注意問題:
a master數(shù)據(jù)量大,數(shù)據(jù)同步應(yīng)該避免高峰夺饲。
b 復(fù)制緩沖區(qū)大小要合理奸汇,會導(dǎo)致數(shù)據(jù)溢出⊥或者復(fù)制周期長擂找,復(fù)制部分?jǐn)?shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)丟失浩销,進行第二次全量復(fù)制贯涎,slave陷入死循環(huán)。
c master主機內(nèi)存不要太大慢洋,占用百分之50-70柬采。留百分之30-50用于執(zhí)行bgsave和創(chuàng)建復(fù)制緩沖區(qū)。
3. 命令轉(zhuǎn)移:
master庫數(shù)據(jù)狀態(tài)被修改后且警,導(dǎo)致主從服務(wù)器狀態(tài)不一致粉捻,需要讓主從同步到一致的狀態(tài)。同步叫命令傳播斑芜。
復(fù)制緩沖器:
是一個隊列肩刃,用于存儲服務(wù)器執(zhí)行命令。每次傳播命令杏头,master都會將命令記錄下來盈包,并存儲到復(fù)制緩沖區(qū)中。
總結(jié):
[站外圖片上傳中...(image-ac6d25-1623382960682)]
4.2 哨兵架構(gòu)
redis節(jié)點出現(xiàn)故障時醇王,sentinel能夠自動完成故障發(fā)現(xiàn)和轉(zhuǎn)移呢燥,并通知應(yīng)用端,實現(xiàn)真正高可用寓娩。
4.2.1 哨兵架構(gòu)分析
1. 哨兵架構(gòu)做了那些事情:
監(jiān)控: 每個哨兵節(jié)點會定時檢測redis數(shù)據(jù)節(jié)點以及其他sentinel節(jié)點是否可達叛氨。
主節(jié)點故障轉(zhuǎn)移: 當(dāng)master出現(xiàn)故障時候,從slave節(jié)點中選取一個master棘伴,從slave復(fù)制新的master的數(shù)據(jù)寞埠。并且維持后續(xù)主從關(guān)系。
通知: 哨兵在完成故障轉(zhuǎn)移后焊夸,會通知連接客戶端故障轉(zhuǎn)移的結(jié)果仁连。
配置提供者: 哨兵架構(gòu)中應(yīng)用端配置了sentinel的節(jié)點集合,通過sentinel獲取master信息阱穗。
2. sentinel配置多個節(jié)點好處饭冬?
對主節(jié)點進行故障判斷是由所有的sentinel共同判斷使鹅,防止誤判。
sentinel有多個昌抠,即使個別的sentinel不可用患朱,但整體還是可以用的。
4.2.2 sentinel三個定時任務(wù)
1. 第一個定時任務(wù):
每隔10秒扰魂,每個sentinel節(jié)點向master和slave發(fā)送info命令麦乞,作用如下:
a 向主節(jié)點發(fā)送info命令可用獲取從節(jié)點信息蕴茴,(sentinel無需配置從節(jié)點)劝评,當(dāng)有新的從節(jié)點加入,立刻感應(yīng)維護正確的拓撲結(jié)構(gòu)倦淀。
b 根據(jù)info命令的回復(fù)動態(tài)更新sentinel中維護主從節(jié)點的完整信息蒋畜。
2. 第二個定時任務(wù):
[站外圖片上傳中...(image-3be466-1623382960682)]
每隔2s,每個sentinel節(jié)點向sentinel:hello節(jié)點發(fā)布當(dāng)前sentinel信息和sentinel對主節(jié)點判斷撞叽,同時其他sentinel節(jié)點訂閱該頻道姻成,作用如下:
a 發(fā)現(xiàn)其他sentinel節(jié)點。
b sentinel節(jié)點交換主節(jié)點狀態(tài)愿棋,作為客觀下線和領(lǐng)導(dǎo)者選舉的依據(jù)科展。
3. 第三個定時任務(wù):
[站外圖片上傳中...(image-7ae324-1623382960682)]
每隔一秒鐘每個sentinel要向其他sentinel節(jié)點和redis節(jié)點發(fā)送ping。判斷這些節(jié)點是否可達糠雨,從而檢查節(jié)點健康狀況才睹。
4.2.3 主觀下線和客觀下線
[站外圖片上傳中...(image-39944e-1623382960682)]
主觀下線是某一個sentinel一家之言,存在誤判操作甘邀。
如果是從節(jié)點或者其他sentinel節(jié)點主觀下線琅攘,沒有后續(xù)操作。如果是主節(jié)點還需進行客觀下線判斷松邪。
4.2.4 故障轉(zhuǎn)移過程
- 沒有足夠的sentinel節(jié)點同意主節(jié)點下線坞琴,主節(jié)點主觀下線被移除。當(dāng)主節(jié)點從新向sentinel發(fā)送ping有效回應(yīng)時逗抑,主節(jié)點主觀下線移除剧辐。
- 主節(jié)點下線后,sentinel向主節(jié)點和所有從節(jié)點發(fā)送info命令邮府,由之前10s一次變?yōu)槊棵胍淮巍?/li>
- 接下來進行故障轉(zhuǎn)移前選舉出sentinel的leader
因為故障轉(zhuǎn)移工作僅僅是需要一個sentinel節(jié)點完成浙于。所有sentinel節(jié)點之間要進行選舉,選出一個leader完成故障轉(zhuǎn)移挟纱。
每一個sentinel都有可能成為leader羞酗。
每個sentinel只有一張票,只能投給sentinel紊服,先到先得檀轨。
如果某個sentinel得票數(shù)>=一半胸竞,選舉成功。如果選舉失敗参萄,進行下一輪選擇卫枝。
[站外圖片上傳中...(image-e38a21-1623382960682)]
- sentinel的leader節(jié)點完成故障轉(zhuǎn)移
從salve節(jié)點中選舉一個從節(jié)點,并升級為主節(jié)點讹挎。
從節(jié)點從新復(fù)制主節(jié)點數(shù)據(jù)校赤。
已下線主節(jié)點變成從節(jié)點,并復(fù)制新的主節(jié)點數(shù)據(jù)筒溃。(這個設(shè)置由于主節(jié)點已經(jīng)下線马篮,無法立刻通知。只能將該設(shè)置放到sentinel中怜奖,當(dāng)下線主節(jié)點上線浑测,sentinel會將設(shè)置發(fā)送)
將故障轉(zhuǎn)移結(jié)果告訴其他sentinel。(sentinel-leader節(jié)點將主節(jié)點相關(guān)信息歪玲,通過發(fā)布訂閱方式完成)
[站外圖片上傳中...(image-721261-1623382960682)]
- sentinel將故障轉(zhuǎn)移結(jié)果通知客戶端迁央。
[站外圖片上傳中...(image-62f77d-1623382960682)]
sentinel會在相關(guān)頻道發(fā)布故障轉(zhuǎn)移相關(guān)信息,應(yīng)用端只需要去自己感興趣頻道訂閱即可滥崩。
a 根據(jù)sentinel節(jié)點 調(diào)用sentinelGetMasteAddrByName獲取master相關(guān)信息岖圈。
b 為每一個sentinel創(chuàng)建一個監(jiān)聽線程,并訂閱“+switch-master”的頻道钙皮。(sentinel完成故障轉(zhuǎn)移后會在“+switch-master”頻道發(fā)布新的master信息)
c 客戶端從新初始化蜂科,連接master。
五 Redis高可用之-集群
雖然主從復(fù)制和哨兵架構(gòu)能解決高可用問題株灸。但是無法擴展寫能力和存儲能力崇摄。大數(shù)據(jù)量和高并發(fā)無法滿足高可用。redis提供了分布式解決方案慌烧。
5.1 分布式解決方案
1. 客戶端分區(qū):
[站外圖片上傳中...(image-468e5a-1623382960682)]
2. 代理分區(qū):
[站外圖片上傳中...(image-70cb1c-1623382960682)]
3. 查詢路由分區(qū):
[站外圖片上傳中...(image-dbafd7-1623382960682)]
5.2 redis分區(qū)理論
cluster采用虛擬槽分區(qū)方案逐抑。redis定義了16384個槽,編號0-16383屹蚊。每個master屬于16384哈希槽中一部分厕氨。
執(zhí)行GET/SET/DEL根據(jù)key進行操作,Redis通過CRC16算法計算key汹粤,得到redis節(jié)點命斧。然后操作指定的redis節(jié)點。
[站外圖片上傳中...(image-f83d7a-1623382960682)]
redis擴容:
新增的redis節(jié)點中沒有卡槽分配嘱兼,因此需要從新分配卡槽国葬,還需要考慮redis中數(shù)據(jù)遷移。
配置文件:
./redis-cli --cluster reshard 192.168.211.141:7001 --cluster-from c9687b2ebec8b99ee14fcbb885b5c3439c58827f,80a69bb8af3737bce2913b2952b4456430a89eb3,612e4af8ea e48426938ce65d12a7d7376b0b37e3 --cluster-to 443096af2ff8c1e89f1160faed4f6a02235822a7 -cluster-slots 100
#參數(shù)說明
--cluster-from:表示slot目前所在的節(jié)點的node ID,多個ID用逗號分隔
--cluster-to:表示需要新分配節(jié)點的node ID --cluster-slots:分配的slot數(shù)量
1. Cluster請求路由:
[站外圖片上傳中...(image-d2345f-1623382960682)]
六 災(zāi)難解決
6.1 緩存穿透
用戶查詢緩存沒有查到數(shù)據(jù)汇四,然后查詢MySQL數(shù)據(jù)庫也沒有查詢到數(shù)據(jù)接奈,然后用戶反復(fù)刷新導(dǎo)致反復(fù)查詢數(shù)據(jù)庫,這種現(xiàn)象叫做緩存穿透通孽。
1. 解決方案一:
第一次查詢查詢緩存和數(shù)據(jù)庫都沒查到數(shù)據(jù)序宦,此時將null做為value存儲到緩存中。下次同樣的請求就直接從緩存中取出null背苦。
[站外圖片上傳中...(image-cf4221-1623382960682)]
2. 第二種解決方案(布隆過濾器):
布隆過濾器是什么互捌?:
用于解決大規(guī)模數(shù)據(jù)情況下不需要精準(zhǔn)過濾場景。
布隆過濾器內(nèi)部是一個bit數(shù)組行剂,以及2個hash函數(shù)((f_1,f_2))秕噪。布隆過濾器有個誤判率概念,誤判率越高硼讽,數(shù)組越短巢价,誤判率越低牲阁,數(shù)組越長固阁。
如果有兩個數(shù)字,N_1經(jīng)過函數(shù)f_1,f_2計算出兩個數(shù)字城菊,讓存儲到bit數(shù)組中备燃。N_2經(jīng)過f_1 f_2計算也產(chǎn)生兩個數(shù)字。當(dāng)兩個數(shù)字和和N_1產(chǎn)生的數(shù)字有一個一樣凌唬,則代表N_2在集合中并齐,這就是布隆過濾器的計算原理。
[站外圖片上傳中...(image-1c22ae-1623382960682)]
解決思路:
在查詢緩存時候客税,先去緩存布隆器中查詢况褪。如果在緩存布隆器中查詢到結(jié)果后,則進行緩存查詢更耻。如果沒有查詢到直接返回测垛,不進行查詢。
6.2 緩存擊穿
緩存過期秧均,正在此時大量的請求訪問某個key,大量請求查詢數(shù)據(jù)庫,這種現(xiàn)象叫做緩存擊穿亡驰。
定時器:
后臺定義一個定時器源织,定時主動更新緩存。例如:某個在緩存中的數(shù)據(jù)誉己,在一分鐘過期眉尸,我每隔30秒去更新下緩存數(shù)據(jù)。
這種方案思路簡單,但是增加系統(tǒng)的復(fù)雜性噪猾。對于key相對固定的地消,適合。
多級緩存:
[站外圖片上傳中...(image-6a1ae3-1623382960683)]
我們應(yīng)用程序?qū)?shù)據(jù)存儲到緩存中畏妖,并設(shè)置永不過期脉执。用戶進行查詢的時候,先查詢ngnix緩存戒劫,緩存不存在半夷,查詢redis緩存中,并將數(shù)據(jù)存儲到Nginx一級緩存迅细,并設(shè)置更新時間巫橄。不僅防止緩存擊穿,還提升程序抗壓能力茵典。
分布式鎖(解決超賣):
和鎖湘换、同步代碼塊實現(xiàn)功能是一樣的,只是使用的業(yè)務(wù)場景不一樣统阿。普通鎖和同步代碼塊只能解決單體服務(wù)的彩倚,分布式鎖解決分布式集群環(huán)境。
使用Redission實現(xiàn)分布式鎖:
1. 引入依賴包扶平。
2. 創(chuàng)建redis集群配置文件帆离,添加配置。
3. 定義獲取鎖结澄、釋放鎖方法哥谷。
4. 創(chuàng)建Redisson工廠。
[站外圖片上傳中...(image-7174ec-1623382960683)]
隊列術(shù):
面對封流時候麻献,可以直接將流量放到隊列中们妥。讓后臺不用同時處理更多請求,讓隊列中請求逐個消費勉吻。
Nginx緩存隊列術(shù):
了Nginx的代 理緩存监婶,其中有一個屬性叫 proxy_cache_lock。多個客戶端請求一個緩存中不存在的文件餐曼。只允許第一個請求發(fā)送到服務(wù)端压储,其他請求在緩存中取到取到信息。
6.3 雪崩
大量的緩存失效源譬,導(dǎo)致大量請求查詢數(shù)據(jù)庫集惋,這種現(xiàn)象叫做雪崩。
解決方案:
多級緩存
限流
隊列限流
數(shù)據(jù)預(yù)熱
6.4 緩存一致性
數(shù)據(jù)庫中數(shù)據(jù)發(fā)生了更改踩娘,需要更新緩存中的數(shù)據(jù)刮刑。解決方案canal喉祭。
6.4.1 緩存一致性的原理
[站外圖片上傳中...(image-ecd675-1623382960683)]
使用Canal監(jiān)聽數(shù)據(jù)庫指定表的增量變化,在Java程序中消費Canal監(jiān)聽到的增量變化雷绢,并在Java程序中實現(xiàn)對Redis和Nginx緩存更新泛烙。
1. Mysql主從復(fù)制原理:
a 將mysql master數(shù)據(jù)變更記錄到二進制文件中。
b MySQL slave將master二進制文件拷貝到自己中繼日志中翘紊。
c MySQL slave從放relay log日志蔽氨,同步變更數(shù)據(jù)。
2. Canal工作原理:
a Canal偽裝成slave帆疟,向master發(fā)送drump協(xié)議鹉究。
b master接受命令之后,向slave(Canal)發(fā)送binary log踪宠。
c Canal開始解析binary log自赔。
6.4.2 認識Canal
Canal用于基于Mysql增量日志解析,并提供增量數(shù)據(jù)訂閱和消費柳琢。
1. Canal應(yīng)用場景:
搜索引擎和緩存的更新绍妨。
代替輪詢方式來對數(shù)據(jù)庫表變更進行監(jiān)控,有效緩解輪詢導(dǎo)致數(shù)據(jù)庫資源柬脸。
6.4.3 Canal的配置
1. 開啟MySQL的bin-log日志:
cd /etc/mysql/mysql.conf.d
在mysqld.cnf下面添加如下配置
# 開啟
binlog log-bin=/var/lib/mysql/mysql-bin
# 選擇 ROW 模式
binlog-format=ROW
# 配置 MySQL replaction 需要定義他去,不要和 canal 的 slaveId 重復(fù)
server-id=12345
2. Canal安裝:
docker run -p 11111:11111 --name canal -d docker.io/canal/canal-server
3. 配置CanalServer:
配置Canal的id:
/home/admin/canal-server/conf/canal.properties
[站外圖片上傳中...(image-906a36-1623382960683)]
配置數(shù)據(jù)庫監(jiān)聽地址和監(jiān)聽數(shù)據(jù)庫以及表變化:
/home/admin/canal-server/conf/example/instance.properties
[站外圖片上傳中...(image-16f8ce-1623382960683)]
配置regex規(guī)則
a 多個正則用逗號隔開(,),轉(zhuǎn)義符要雙斜杠(\\)
b 所有表:.* or .*\\..*
c canal schema下所有表: canal\\..*
d canal下的以canal打頭的表:canal\\.canal.*
e canal schema下的一張表:canal.test1
f 多個規(guī)則組合使用:canal\\..*,mysql.test1,mysql.test2 (逗號分隔)
重啟canal:
docker restart canal
MySQL創(chuàng)建賬號并授權(quán):
create user canal@'%' IDENTIFIED by 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%'; FLUSH PRIVILEGES;
6.4.4 同步更新緩存
1. 創(chuàng)建類MoneyLogSync肖粮,繼承EntryHandler類孤页,實現(xiàn)方法:
@CanalTable(value = "money_log")
@Component
public class MoneyLogSync implements EntryHandler<MoneyLog> {
@Autowired
private MoneyLogService moneyLogService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 數(shù)據(jù)增加變更
* @param moneyLog
*/
@Override
public void insert(MoneyLog moneyLog) {
//查詢用戶的搶紅包列表
List<MoneyLog> moneyLogs = moneyLogService.list(moneyLog.getUsername());
//將數(shù)據(jù)存入到Redis
redisTemplate.boundHashOps("UserMoneyLog").put(moneyLog.getUsername(), moneyLogs);
//更新nginx一級緩存同樣道理
}
}
配置Canal地址:
#Canal配置
canal:
server: 192.168.211.141:11111
destination: example
七 RESTful站點安全終極解決方案
lua腳本解決基于RESTFul開發(fā)安全風(fēng)控解決方案:【緩存穿透】尔苦、【緩存擊穿】涩馆、【緩存雪崩】、【黑白名單】允坚、 【定向日志收集】魂那、【防止攻擊】、【限流】稠项、【熔斷】
[站外圖片上傳中...(image-f07850-1623382960683)]
ngnix和lua腳本結(jié)合涯雅,用lua腳本對請求進行分析,然后降請求轉(zhuǎn)發(fā)下去展运。
[站外圖片上傳中...(image-f6762e-1623382960683)]
1. lua執(zhí)行http請求:
--輸出json類型數(shù)據(jù)
ngx.header.content_type="application/json;charset=utf8"
local http = require "resty.http"
local httpc = http.new()
--執(zhí)行請求
local res, err = httpc:request_uri("http://192.168.0.105:18082/api/userinfo/one", {
method = "GET",
body = "a=1&b=2",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
},
--當(dāng)前連接保存時間
keepalive_timeout = 60000,
--連接池數(shù)量
keepalive_pool = 10
})
if not res then ngx.say("failed to request: ", err) return end
ngx.say(res.body)
修改nginx.config
#api
location ~ /api {
content_by_lua_file /usr/local/openresty/nginx/lua/resthttp.lua;
}
八 Nginx緩存學(xué)習(xí)
Nginx處于Web網(wǎng)站服務(wù)最外層活逆,而且支持瀏覽器緩存配置和后端緩存,用它做部分?jǐn)?shù)據(jù)緩存效率更高拗胜。
8.1 實現(xiàn)瀏覽器緩存
8.2 Nginx清理緩存
如果不想采用緩存過期蔗候,采用第三方緩存清理模塊,nginx_ngx_cache_purge 埂软。在安裝OpenRestry就已經(jīng)實現(xiàn)了锈遥。
1. 配置清理緩存:
#清理緩存
location ~ /purge(/.*) {
#清理緩存
proxy_cache_purge openresty_cache $host$1$is_args$args;
}
2. 查看緩存:
每次請求 key 就可以刪除指定緩存,我們可以先查看緩存文件的可以
[站外圖片上傳中...(image-c942ad-1623382960683)]
訪問路徑:http://ip+port/purge/user/wangwu
8.3 Lua腳本基本語法
百度吧
8.4 多級緩存架構(gòu)
1. 用Java實現(xiàn)多級緩存:
[站外圖片上傳中...(image-86551f-1623382960683)]
1、用戶請求經(jīng)過Nginx
2所灸、Nginx檢查是否有緩存丽惶,如果Nginx有緩存,直接響應(yīng)用戶數(shù)據(jù)
3爬立、Nginx如果沒有緩存钾唬,則將請求路由給后端Java服務(wù)
4、Java服務(wù)查詢Redis緩存侠驯,如果有數(shù)據(jù)知纷,則將數(shù)據(jù)直接響應(yīng)給Nginx,并將數(shù)據(jù)存入緩存陵霉,Nginx將數(shù)據(jù)響應(yīng)給用戶
5琅轧、如果Redis沒有緩存,則使用Java程序查詢MySQL踊挠,并將數(shù)據(jù)存入到Reids乍桂,再將數(shù)據(jù)存入到Nginx中
2. 用lua腳本實現(xiàn)多級緩存:
[站外圖片上傳中...(image-5d44f4-1623382960683)]
nginx+lua多級緩存架構(gòu)搭建,用lua腳本實現(xiàn)連接redis和mysql效床。避免了tomcat并發(fā)能力瓶頸睹酌。
用戶請求查詢數(shù)據(jù):
a 先查詢nginx緩存,如果緩存存在直接響應(yīng)剩檀,不存在直接用lua腳本查詢redis憋沿。
b redis中有數(shù)據(jù)直接響應(yīng),并把緩存加載到nginx中沪猴。如果沒有查詢到緩存辐啄,查詢MySQL。
c 查詢到數(shù)據(jù)运嗜,響應(yīng)用戶壶辜,然后以次放入到redis和nginx緩存中。
Lua腳本連接MySQL:
--MySQL查詢操作担租,封裝成一個模塊
--Java操作MySqL
--導(dǎo)入依賴包
local mysql = require "resty.mysql"
--配置數(shù)據(jù)源鏈接
local props = {
host = "192.168.211.141",
port = 3306,
database = "redpackage",
user = "root",
password = "123456"
}
--創(chuàng)建一個對象
local mysqldb = {}
--查詢數(shù)據(jù)庫
function mysqldb.query(sql)
--創(chuàng)建鏈接
local db = mysql:new()
--設(shè)置超時時間
db:set_timeout(10000)
db:connect(props)
--配置編碼格式
db:query("SET NAMES utf8")
--查詢數(shù)據(jù)庫 "select * from activity_info where id=1"
local result = db:query(sql)
--關(guān)閉鏈接
db:close()
--返回結(jié)果集
return result
end
return mysqldb
Lua腳本連接Redis:
--操作Redis集群砸民,封裝成一個模塊
--引入依賴庫
local redis_cluster = require "resty.rediscluster"
--配置Redis集群鏈接信息
local config = {
name = "test",
serv_list = {
{ip="192.168.211.141", port = 7001},
{ip="192.168.211.141", port = 7002},
{ip="192.168.211.141", port = 7003},
{ip="192.168.211.141", port = 7004},
{ip="192.168.211.141", port = 7005},
{ip="192.168.211.141", port = 7006},
},
idle_timeout = 1000,
pool_size = 10000,
}
--定義一個對象
local lredis = {}
--創(chuàng)建set()添加數(shù)據(jù)方法
function lredis.set(key,value)
--1)打開鏈接
local red = redis_cluster:new(config)
red:init_pipeline()
--2)執(zhí)行命令【set】
red:set(key,value)
red:commit_pipeline()
--3)關(guān)閉鏈接
red:close()
end
--創(chuàng)建查詢數(shù)據(jù)get()
function lredis.get(key)
--1)打開鏈接
local red = redis_cluster:new(config)
red:init_pipeline()
--2)執(zhí)行命令【set】
red:get(key)
local result = red:commit_pipeline()
--3)關(guān)閉鏈接
red:close()
--4)返回結(jié)果集
return result
end
return lredis
Lua腳本執(zhí)行業(yè)務(wù):
--多級緩存流程操作
--1)Lua腳本查詢Nginx緩存
--2)Nginx如果沒有緩存
--2.1)Lua腳本查詢Redis
--2.1.1)Redis如果有數(shù)據(jù),則將數(shù)據(jù)存入到Nginx緩存奋救,并響應(yīng)用戶
--2.1.2)Redis沒有數(shù)據(jù)岭参,Lua腳本查詢MySQL
-- MySQL有數(shù)據(jù),則將數(shù)據(jù)存入到Redis尝艘、Nginx緩存[需要額外定義]演侯,響應(yīng)用戶
--3)Nginx如果有緩存,則直接將緩存響應(yīng)給用戶
--響應(yīng)數(shù)據(jù)為JSON類型
ngx.header.content_type="application/json;charset=utf8"
--引入依賴庫
--cjson:對象轉(zhuǎn)JSON或者JSON轉(zhuǎn)對象
local cjson = require("cjson")
local mysql = require("mysql")
local lrredis = require("redis")
--獲取請求參數(shù)ID http://192.168.211.141/act?id=1
local id = ngx.req.get_uri_args()["id"];
--加載本地緩存
local cache_ngx = ngx.shared.act_cache;
--組裝本地緩存的key,并獲取nginx本地緩存
local ngx_key = 'ngx_act_cache_'..id
local actCache = cache_ngx:get(ngx_key)
--如果nginx中沒有緩存利耍,則查詢Redis集群緩存
if actCache == "" or actCache == nil then
--從Redis集群中加載數(shù)據(jù)
local redis_key = 'redis_act_'..id
local result = lrredis.get(redis_key)
--Redis中數(shù)據(jù)為空蚌本,查詢數(shù)據(jù)庫
if result[1]==nil or result[1]==ngx.null then
--組裝SQL語句
local sql = "select * from activity_info where id ="..id
--執(zhí)行查詢
result = mysql.query(sql)
--數(shù)據(jù)不為空盔粹,則添加到Redis中
if result[1]==nil or result[1]==ngx.null then
ngx.say("no data")
else
--數(shù)據(jù)添加到Nginx緩存和Redis緩存
lrredis.set(redis_key,cjson.encode(result))
cache_ngx:set(ngx_key, cjson.encode(result), 2*60);
ngx.say(cjson.encode(result))
end
else
--將數(shù)據(jù)添加到Nginx緩存中
cache_ngx:set(ngx_key, result, 2*60);
--直接輸出
ngx.say(result)
end
else
--輸出緩存數(shù)據(jù)
ngx.say(actCache)
end
nginx配置:
#活動查詢
location /act {
content_by_lua_file /usr/local/openresty/nginx/lua/activity.lua;
}
8.5 紅包雨案例
8.5.1 紅包場景概述
1. 搶紅包的特點:
a 并發(fā)量大(搶紅包,白撿的都去搶程癌。所以人很多)
b 按照時間段來發(fā)放(生活中的紅包就是在幾個小時發(fā)幾波)
c 搶的紅包肯定是不能超過預(yù)設(shè)的總金額
e 搶紅包肯定是先到先得舷嗡。(搶紅包的公平性)
f 在發(fā)紅包時候,可以追加紅包的數(shù)量和延遲搶紅包時間嵌莉。
2. 搶紅包策略:
[站外圖片上傳中...(image-c0d688-1623382960683)]
老板規(guī)定發(fā)金額和發(fā)的個數(shù)確定好进萄,通過算法得出每個紅包金額,然后分批次將紅包放入到redis中锐峭。每個人搶紅包直接從redis中拿就可以了中鼠。因為redis是單線程所有每次只能一個用戶取到,所以避免了一個紅包多個人搶沿癞。傳說中解決超賣援雇。
8.5.2 紅包放入緩存隊列中
1. 定時將紅包導(dǎo)入緩存隊列:
初始化讀取:
創(chuàng)建容器監(jiān)聽類 ,讓該類實現(xiàn)接口 ApplicationListener 椎扬,當(dāng)容器初始化完成后會調(diào)用onApplicationEvent 方法惫搏。然后去去到數(shù)據(jù)到redis隊列中。
@Component
public class MoneyPushTask implements ApplicationListener<ContextRefreshedEvent>{
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//清空歷史數(shù)據(jù)
//加載新的數(shù)據(jù)
}
}
定時加載:
可以使用定時任務(wù)蚕涤,定時的更新紅包數(shù)量
8.5.3 解決大量的人搶紅包導(dǎo)致服務(wù)器崩潰
當(dāng)有大量人去搶紅包筐赔,服務(wù)器很有可能會崩潰。采用隊列削峰揖铜。
[站外圖片上傳中...(image-fac8bc-1623382960683)]
大量用戶來搶紅包時候茴丰,使用Lua腳本將將請求放到緩存隊列中,服務(wù)端處理隊列中的請求天吓。在lua腳本中可以導(dǎo)入 lua-resty-jwt模塊贿肩,用來安全驗證。
8.6 Nginx限流
我們采用多級緩存的模式失仁,但是當(dāng)用戶反復(fù)刷新頁面沒有必要讓所有請求到達服務(wù)器尸曼。還有一些惡意的攻擊請求,也要避免請求到達服務(wù)器萄焦。限流是保護系統(tǒng)的一種方式。
1. 控制速率(控制請求數(shù)量和請求速度):
[站外圖片上傳中...(image-3f59f7-1623382960683)]
水過來先放到桶里冤竹,然后勻速的將水流出拂封。當(dāng)流入桶里水過大,水就直接溢出了鹦蠕。用戶請求類似這樣原理冒签,當(dāng)請求來了先放到緩沖中然后勻速的到達服務(wù)器,
當(dāng)請求過多钟病,直接拒絕請求萧恕。
nginx配置文件:
# 配置限制流緩存空間
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=2r/s;
# 配置限流
limit_req zone=contentRateLimit;
# 上面參數(shù)的解釋
binary_remote_addr 是一種key刚梭,表示基于 remote_addr(客戶端IP) 來做限流,binary_ 的目的是壓縮內(nèi)存占用 量票唆。 zone:定義共享內(nèi)存區(qū)來存儲
訪問信息朴读, contentRateLimit:10m 表示一個大小為10M,名字為contentRateLimit的 內(nèi)存區(qū)域走趋。1M能存儲16000 IP地址的訪問信息衅金,10M可以存儲
16W IP地址訪問信息。 rate 用于設(shè)置大訪問速率簿煌,rate=10r/s 表示每秒多處理10個請求氮唯。Nginx 實際上以毫秒為粒度來跟蹤請求信息,因 此 10r/s
實際上是限制:每100毫秒處理一個請求姨伟。這意味著惩琉,自上一個請求處理完后,若后續(xù)100毫秒內(nèi)又有請求到達夺荒,將 拒絕處理該請求.我們這里設(shè)置
成2 方便測試琳水。
注意:當(dāng)設(shè)置了限流但是并發(fā)上來了,這樣大部分請求都會被拒絕般堆。
lilimit_req zone=contentRateLimit burst=4 nodelay;
2. 控制并發(fā)連接數(shù)(限制某個ip連接服務(wù)器的個數(shù)在孝,連接服務(wù)器的總數(shù)):
利用limit_conn_zone和limit_conn兩個指令,限制某一個ip連接數(shù)。
nginx參數(shù)配置:
#根據(jù)IP地址來限制淮摔,存儲內(nèi)存大小10M
limit_conn_zone $binary_remote_addr zone=addr:1m;
limit_conn addr 2;
# 參數(shù)解釋
limit_conn_zone $binary_remote_addr zone=addr:10m; 表示限制根據(jù)用戶的IP地址來顯示私沮,設(shè)置存儲地址為的 內(nèi)存大小10M
limit_conn addr 2; 表示 同一個地址只允許連接2次。
限制某個ip連接數(shù)量和橙。限制連接的總個數(shù)
#IP限流
limit_conn_zone $binary_remote_addr zone=perip:10m;
#根據(jù)server的名字限流
limit_conn_zone $server_name zone=perserver:10m;
#單個客戶端ip與服務(wù)器的連接數(shù).
limit_conn perip 10;
#限制與服務(wù)器的總連接數(shù)
limit_conn perserver 100;