眾所周知在對(duì)網(wǎng)站設(shè)計(jì)的時(shí)候,會(huì)遇到給用戶“群發(fā)短信”,“訂單系統(tǒng)有大量的日志”,“秒殺設(shè)計(jì)”等,服務(wù)器沒(méi)法處理這種瞬間迸發(fā)的壓力,這種情況要保證系統(tǒng)正常有效的使用,就需要“消息隊(duì)列”的幫助。本篇主要通過(guò)消息隊(duì)列的思路進(jìn)行學(xué)習(xí)。
主要了解如下知識(shí):
1、隊(duì)列是個(gè)什么東西,他能干什么?
2、對(duì)列的應(yīng)用場(chǎng)景有哪些?
3、如何使用隊(duì)列對(duì)業(yè)務(wù)進(jìn)行解偶?
4、如何使用Redis隊(duì)列來(lái)消除高壓力?
5、專業(yè)的對(duì)列系統(tǒng)RabbitMQ如何使用?
歸納如下主要內(nèi)容
@消息隊(duì)列的概念,原理和場(chǎng)景
@解耦案例:隊(duì)列處理訂單系統(tǒng)和配送系統(tǒng)
@流量削峰案例:Redis的List類型實(shí)現(xiàn)秒殺
@RabbitMQ:更專業(yè)的消息系統(tǒng)實(shí)現(xiàn)方案
一、認(rèn)識(shí)消息隊(duì)列
1.1 消息對(duì)列概念
從本質(zhì)上說(shuō)消息對(duì)列就是一個(gè)隊(duì)列結(jié)構(gòu)的中間件,也就是說(shuō)消息放入這個(gè)中間件之后就可以直接返回,并不需要系統(tǒng)立即處理,而另外會(huì)有一個(gè)程序讀取這些數(shù)據(jù),并按順序進(jìn)行逐次處理。
也就是說(shuō)當(dāng)你遇到一個(gè)并發(fā)特別大并且耗時(shí)特別長(zhǎng)同時(shí)還不需要立即返回處理結(jié)果,使用消息隊(duì)列可以解決這類問(wèn)題。
1.2 核心結(jié)構(gòu)
由一個(gè)業(yè)務(wù)系統(tǒng)進(jìn)行入隊(duì),把消息逐次插入到消息隊(duì)列中,插入成功之后直接返回成功的結(jié)果,后續(xù)會(huì)有一個(gè)消息處理系統(tǒng),這個(gè)系統(tǒng)會(huì)把消息系統(tǒng)中的記錄逐次進(jìn)行取出并進(jìn)行處理,完成一個(gè)出隊(duì)的流程。
1.3 應(yīng)用場(chǎng)景
數(shù)據(jù)冗余:比如訂單系統(tǒng),后續(xù)需要嚴(yán)格的進(jìn)行數(shù)據(jù)轉(zhuǎn)換和記錄,消息隊(duì)列可以把這些數(shù)據(jù)持久化的存儲(chǔ)在隊(duì)列中,然后有訂單,后續(xù)處理程序進(jìn)行獲取,后續(xù)處理完之后在把這條記錄進(jìn)行刪除來(lái)保證每一條記錄都能夠處理完成。
系統(tǒng)解耦:使用消息系統(tǒng)之后,入隊(duì)系統(tǒng)和出隊(duì)系統(tǒng)是分開(kāi)的,也就說(shuō)只要一天崩潰了,不會(huì)影響另外一臺(tái)系統(tǒng)正常運(yùn)轉(zhuǎn)。
流量削峰:例如秒殺和搶購(gòu),我們可以配合緩存來(lái)使用消息隊(duì)列,能夠有效的頂住瞬間訪問(wèn)量,防止服務(wù)器承受不住導(dǎo)致崩潰。
異步通信:消息本身使用入隊(duì)之后可以直接返回。
擴(kuò)展性:例如訂單隊(duì)列,不僅可以處理訂單,還可以給其他業(yè)務(wù)使用。
排序保證:有些場(chǎng)景需要按照產(chǎn)品的順序進(jìn)行處理比如單進(jìn)單出從而保證數(shù)據(jù)按照一定的順序處理,使用消息隊(duì)列是可以的。
以上都是消息隊(duì)列常見(jiàn)的使用場(chǎng)景,當(dāng)然消息隊(duì)列只是一個(gè)中間件,可以配合其他產(chǎn)品進(jìn)行使用。
1.4 常見(jiàn)隊(duì)列實(shí)現(xiàn)優(yōu)缺點(diǎn)
隊(duì)列介質(zhì)
1、數(shù)據(jù)庫(kù),例如mysql(可靠性高,易實(shí)現(xiàn),速度慢)
2、緩存, 例如redis (速度快,單個(gè)消息報(bào)包過(guò)大時(shí)效率低)
3、消息系統(tǒng),例如rabbitMq (專業(yè)性強(qiáng),可靠,學(xué)習(xí)成本高)
消息處理觸發(fā)機(jī)制
1、死循環(huán)方式讀取:易實(shí)現(xiàn),故障時(shí)無(wú)法及時(shí)恢復(fù);(比較適合做秒殺,比較集中,運(yùn)維集中維護(hù))
2、定時(shí)任務(wù):壓力均分,有處理上限;目前比較流行的處理觸發(fā)機(jī)制。(唯一的缺點(diǎn)是間隔和數(shù)據(jù)需要注意,不要等上一個(gè)任務(wù)沒(méi)有完成下一個(gè)任務(wù)又開(kāi)始了)
3、守護(hù)進(jìn)程:類似于php-fpm 和php-cg,需要shell基礎(chǔ)
二、解藕案例:隊(duì)列處理“訂單系統(tǒng)”和“配送系統(tǒng)”
對(duì)于訂單流程,我們可以設(shè)計(jì)兩個(gè)系統(tǒng),一個(gè)是“訂單系統(tǒng)” 另外一個(gè)是 “配送系統(tǒng)”, 在網(wǎng)購(gòu)的時(shí)候我們應(yīng)該都見(jiàn)過(guò),當(dāng)我提交了一個(gè)訂單之后,我在后臺(tái)可以看到我的貨物正在配送中。這個(gè)時(shí)候就要參與進(jìn)來(lái)一個(gè)“配送系統(tǒng)”。
如果我們?cè)谧黾軜?gòu)的時(shí)候把 “訂單系統(tǒng)” 和 “配送系統(tǒng)” 設(shè)計(jì)在一起的話就會(huì)出現(xiàn)一些問(wèn)題,首先對(duì)于訂單系統(tǒng)來(lái)說(shuō),因?yàn)橄到y(tǒng)的壓力會(huì)比較大,但是 "配送系統(tǒng)" 沒(méi)必要為這些壓力做一些即時(shí)的反應(yīng)。
第二個(gè)我們也不希望在訂單系統(tǒng)出現(xiàn)故障之后導(dǎo)致配送系統(tǒng)也出現(xiàn)故障,這個(gè)時(shí)候就會(huì)同時(shí)影響到兩個(gè)系統(tǒng)的正常運(yùn)轉(zhuǎn)。所以我們希望把這兩個(gè)系統(tǒng)進(jìn)行解耦。這兩系統(tǒng)分開(kāi)之后我們可以通過(guò)一個(gè)中間的 “隊(duì)列表” 進(jìn)行這兩個(gè)系統(tǒng)的溝通。
2.1 架構(gòu)設(shè)計(jì)
1、首先訂單系統(tǒng)會(huì)接收用戶的訂單,然后進(jìn)行訂單的處理。
2、然后會(huì)把這些訂單信息寫到隊(duì)列表中,這個(gè)隊(duì)列表是溝通這兩個(gè)系統(tǒng)的關(guān)鍵。
3、由配送系統(tǒng)定時(shí)執(zhí)行的一個(gè)程序來(lái)讀取隊(duì)列表進(jìn)行處理。
4、配送系統(tǒng)處理之后,會(huì)把已處理的記錄進(jìn)行標(biāo)記。
2.2 程序流程
三、流量削峰案例:Redis 的 list 類型實(shí)現(xiàn)秒殺
redis 基于內(nèi)存,它的速度會(huì)非???,redis 對(duì)數(shù)據(jù)庫(kù)有一個(gè)非常好的補(bǔ)充作用因?yàn)樗强沙志没模瑀edis會(huì)周期性的把數(shù)據(jù)寫到硬盤里,所以它不用擔(dān)心斷電的問(wèn)題,從這方面說(shuō)它比另一款緩存 memcache 更有優(yōu)勢(shì)些,另外 redis 提供五種數(shù)據(jù)類型(字符串,雙向鏈表,哈希,集合,有序集合)
一般情況下,做秒殺案例,搶購(gòu),瞬間高比你高發(fā),需要排隊(duì) 的案例中 redis是一個(gè)很好的選擇。
3.1 redis數(shù)據(jù)類型中的 list 類型
redis 的list 是一個(gè)雙向鏈表,可以從頭部或者尾部追加數(shù)據(jù)。
* LPUSH/LPUSHX :將值插入到(/存在的)列表頭部
* RPUSH/RPUSHX: 將值插入到(/存在的)列表尾部
* LPOP : 移除并獲取列表的第一個(gè)元素
* RPOP: 移除并獲取列表的最后一個(gè)元素
* LTRIM: 保留指定區(qū)間內(nèi)的元素
* LLEN: 獲取列表長(zhǎng)度
* LSET: 通過(guò)索引設(shè)置列表元素的值
* LINDEX: 通過(guò)索引獲取列表中的元素
* LRANGE: 獲取列表指定范圍內(nèi)的元素
3.2 架構(gòu)設(shè)計(jì)
一個(gè)簡(jiǎn)單結(jié)構(gòu)秒殺的程序設(shè)計(jì)。
1、首先記錄是哪一個(gè)用戶參與了秒殺同時(shí)記錄他的時(shí)間。
2、將用戶的id存到redis列表中,讓它排隊(duì)。如果規(guī)定只有前10個(gè)用戶可以參與成功,如果列表中的個(gè)數(shù)已經(jīng)夠了就不會(huì)讓它繼續(xù)追加數(shù)據(jù)。這樣redis的列表長(zhǎng)度就只會(huì)是10個(gè)
3、最后在慢慢的將redis中的數(shù)據(jù)寫入到數(shù)據(jù)庫(kù)中,以減少數(shù)據(jù)的壓力
3.3 代碼級(jí)設(shè)計(jì)
1、當(dāng)用戶開(kāi)始秒殺時(shí),將秒殺程序的請(qǐng)求寫入Redis (uid, time_stamp)中。
2、假使規(guī)定只有10人可以秒殺成功,檢查 Redis 已經(jīng)存放數(shù)據(jù)的長(zhǎng)度,超出上限直接丟棄說(shuō)明秒殺完成。
3、最后在死循環(huán)處理存入Redis中的10條數(shù)據(jù),然后在慢慢的取數(shù)據(jù)并存入到mysql數(shù)據(jù)庫(kù)中。
在秒殺這一塊對(duì)于數(shù)據(jù)庫(kù)的壓力特別的大,如果我們沒(méi)有這樣的設(shè)計(jì),會(huì)造成mysql的寫入瓶頸。我們通過(guò)Redis的一個(gè)對(duì)列l(wèi)ist,然后把秒殺的請(qǐng)求放入到Redis里面, 最后通過(guò)入庫(kù)程序,把數(shù)據(jù)慢慢的寫入到數(shù)據(jù)庫(kù),這樣的話就可以實(shí)現(xiàn)流量的均衡,對(duì)mysql不會(huì)造成太大的壓力。
四、RabbitMQ
這里講解一些RabbitMQ的使用,首先我們之前講秒殺案例的時(shí)候提到了鎖的機(jī)制,防止其他程序處理同一條記錄,如果我們的系統(tǒng)架構(gòu)非常的復(fù)雜,有多個(gè)程序?qū)崟r(shí)的讀取一個(gè)隊(duì)列,或者我有多個(gè)發(fā)送程序,同時(shí)來(lái)操作一個(gè)或多個(gè)隊(duì)列,甚至我還想這些程序分布在不同的機(jī)器上,這種情況下用redis隊(duì)列就有些力不從心了。這種時(shí)候怎么辦呢,我們就需要來(lái)引入一些更專業(yè)的消息隊(duì)列系統(tǒng),這些系統(tǒng)可以更好的來(lái)解決問(wèn)題。
4.1 RabbitMQ的架構(gòu)和原理
特點(diǎn):完整的實(shí)現(xiàn)了AMQP,集群簡(jiǎn)化,持久化,跨平臺(tái)
RabbitMQS使用
1、RabbitMQ安裝 (rabbitmq-server, php-amqplib)
2、生產(chǎn)者向消息通道發(fā)送消息
3、消費(fèi)者處理消息
工作隊(duì)列
思想:由生產(chǎn)者發(fā)送給消息系統(tǒng),消息系統(tǒng)把任務(wù)封裝成消息隊(duì)列之后,然后供多個(gè)消費(fèi)者使用同一個(gè)隊(duì)列
這不僅解決了生產(chǎn)者和消費(fèi)者之間的解耦,還可以實(shí)現(xiàn)了消費(fèi)者和任務(wù)的共享,減緩了服務(wù)器的壓力。
五、總結(jié)
以上主要學(xué)習(xí)消息隊(duì)列的概念,原理,場(chǎng)景。解耦案例,以及了解RabbitMQ的簡(jiǎn)單使用方法。
六、問(wèn)題
redis 和消息服務(wù)器 選擇的最大區(qū)別是什么。
我的理解是Redis 是一個(gè)一個(gè)處理請(qǐng)求,redis屬于單線程,它和消息服務(wù)器 IO 的實(shí)現(xiàn)方式不同,一個(gè)是同步一個(gè)是異步,而redis使用的是同步阻塞,而消息服務(wù)器使用的是異步非阻塞。