## 隊列的思考
### 隊列的本質
單純說隊列也可代指一種[數據接口](https://baike.baidu.com/item/%E9%98%9F%E5%88%97/14580481?fr=aladdin)。
隊列MQ(Message Queue/消息隊列)不是指某一項技術,而是項目中常用的解耦解決方案,或者說是一種中間件平臺。
我們通常所說的隊列(服務)一般都是指消息隊列。
> 異步是一種編程模型,MQ是一種異步實現方式。隊列實際上也是一種異步模型的應用。
**消息隊列的組成:**
- 數據結構(存/取任務:任務信息/任務參數payload)
- 消息發布(任何客戶端都可以往數據結構中新增消息)
- 消息消費(需要worker來消費消息,即執行任務)
> 所以現在理解了吧,隊列并不是某一技術,而是多種技術的組合形成的解決方案。
異步是一種編程模型,隊列是其實現。
(有點類似于[訂閱/發布](http://mp.weixin.qq.com/s?__biz=MzA5NzkwNDk3MQ==&mid=2650585339&idx=1&sn=d3276dcd3637b6e9ed5491ce3c225214&source=41#wechat_redirect))
* * * * *
### 隊列即服務
隊列的本質就是服務/任務。任務執行需要payload(載荷/參數)。
既然是服務,那就是通用的,所以它即既可加入隊列中執行,也可以直接執行。
```
if ($if_quque) {
QueueClient::push('cancelOrderUpdateStorage', $data);
} else {
Logic('queue')->cancelOrderUpdateStorage($data);
}
```
服務/任務可以被直接調用,也可在隊列中調用。不論怎么調用,任務都是通用的。
~~~
shopnc把隊列的 【具體任務的業務實現】 全部都放在 邏輯文件中了。
$logic_queue = Logic('queue');
...
while (true) {
$result = $logic_queue->$method($arg);
}
但是這樣有個問題:邏輯文件是在守護進程中載入內存了,如果后期有新的隊列任務,需要修改邏輯文件,那么就要停止守護進程,重新運行服務才能生效了。
~~~
* * * * *
### 隊列要注意不能忽視的一個問題:worker如何執行
這里面有一個很重要的問題,又容易被忽視的問題就是,**worker如何執行。**
**任務其實就是業務邏輯。**
>[danger] 通常我們的業務邏輯代碼是基于系統代碼和框架環境(依賴于工具類/函數/應用配置/系統配置等等)的,所以隊列的業務邏輯也要能保證在一樣的環境下執行,否則就很麻煩,不統一會造成邏輯侵入嚴重。
那么問題來了:
如果還以傳統的方式,worker是個無限循環,如果每次還從框架的入口進去:
```php
> php index.php/queue/task/sendmail
```
**那么每次都要加載初始化框架,這將是很大的開銷**(這用的還是訪問式web的url模式)。
所以隊列的運行就不能照搬**訪問式web運行方式**了。
我們希望只需要一次加載系統框架環境就可以,此后就直接循環執行task就可以,而無需每次初始化環境,**這種方式就有點類似于Swoole的運行方式了——長生命周期(邏輯內存常駐)**。
其實,成熟的框架已經想好了這個問題,都支持控制臺模式,url訪問模式和控制臺模式能能夠引入框架環境。
可參考:think-queue、shopnc的queue
之前的記錄:
```text
**有兩種觀點:**
1. 隊列處理程序為了性能有時候沒必要完全引入整個MVC
2. 如果不引入MVC的話,那就相對獨立了,系統配置什么的,環境,自動加載,數據模型等,這就很不方便了。
當然如果框架系統考慮到了隊列,CLI那么提供一種CLI模式運行環境,那也是極好的。
另外需要注意的一個問題就是,假設a程序是處理程序,那么現在每個幾秒鐘去運行一次a還是運行a,在里面死循環比較好。**(這個和php的運行模式和生命周期有關,例子是Laravel和Swoole,下面有探討。)**
如果每隔幾秒去運行a,那就相當于另外的程序去主動調用a了(命令行/自動任務等),那么就要注意不能重復調用a,不能多個其他程序都去調用了a,這可以做一個文件鎖來保證a同一時間內只能被調用一次,也就是不允許并發執行。死循環的話,一個腳本太長時間執行,擔心影響性能,出現內存泄露,并且死循環不能是“死的”,需要提供可以控制的能力,可以停止執行,這可以用文件鎖/配置/信號處理之類的來做到。不能強行的對處理程序做“熱插拔”,否則如果再處理重要任務時異常中斷就可能會出現意外。
還要有監控的能力,知道程序是否執行了,最后的執行時間,執行日志等。
所以兩種方法各有優點和缺點,如果取其中間,調用&程序內循環,增加調用間隔,循環增加限制(可以是執行時間,也可以是循環次數),超過自動退出。
記住任何時候,最好的就是最合適的。
```
### 隊列消費的順序
有時,隊列消息的消費順序也至關重要,必須滿足先進先出,即先入的隊列要先被消費。
比如兩條消息,存款和取款,正常順序,先存款后是可以成功完成取款的,但是如果取款消息被先消費了,那么就會造成取款失敗。這樣來看的話,多工人搶占消息,多進程錯開取消息,是不行的!因為不能保證消費順序與入列順序的同步。那么在這種對 隊列消息消費順序 有要求的情況的任務,只能單進程一條一條的取消息,串行、阻塞執行消息任務了。
* * * * *
### 隊列:延伸思考——為什么要有隊列
如果計算機是神,或許根本不需要異步這東西。沒有任何東西是不需要,而我們非要平白無故的造出來的。
#### 隊列不是神,而是農民
等待還是要等待的,不可能不等待的,**計算機又不是神,該耗的時還是要耗**(只不過放在另外一個地方去耗,不用你當前一直干等著而已),不可能一秒鐘做完所有的事情,不可能無條件的滿足你,只是告訴你操作放到隊列中排隊去做了(交給另外一個人去做),當前馬上給你返回,不阻塞你,不用讓你當前一直等著而不能干別的。
為了用戶體驗和系統效率,只能選擇異步的方式。
#### 隊列的組成:
1. 工人(執行者)
2. 任務(消費者)
3. 消息(生產—消費媒介)
客戶端(生產者)
#### 消息的安全性/隊列的可靠性
>[danger] 隊列是一種中間件服務,屬于外部系統,它本身的作用是為了[解耦](https://www.zhihu.com/question/20278169)(讓合適的人干合適的事情,并且相互之間沒有依賴)。**既然屬于外部系統,那么業務邏輯就不能依賴于隊列服務,所以消息重發、消息丟失、并發取消息、防重復消費等問題都需要考慮到,無論任何情況下都要確保業務邏輯的正確性。**
消息放在Redis里面,如果服務器故障,消息全部丟失怎么辦,不能出現取款永遠不能到賬吧,不能因為隊列的穩定性和故障,導致業務出錯,甚至出現BUG吧,所以在設計任何一條隊列消息和任務時時,都要考慮容錯性,比如要任務處理時必須要嚴格校驗到賬情況,有其他的業務記錄表,消息表等,**要用鎖,消息系統是不可靠的,即使出現打款消息重發,也不能導致系統多次打款,即要保證業務的冪等性**,同時在消息無法到達時,比如遲遲24小時還沒到賬,有可能是隊列丟消息了,那么系統要檢查出來,重發消息。這可以用計劃任務做,每隔24小時檢查一次,掃出超過24小時沒有到賬的訂單,看看是否是出現丟消息了。
隊列還要考慮一種情況,消息取了,但是任務進程卻被殺死了,那么也會出現消息假消費的問題,還有任何時候,任何程序執行到任何地方都有可能失敗,斷電啊,地震啊,進程意外被殺死啊,硬盤報廢啊,硬件不可逆損壞啊,……。
所以程序任何時候,任何部位都要考慮這些意外,都要做好容錯。
* * * * *
### 安全的守護進程實踐
#### 運行狀態文件
守護程序狀態可以用一個文件來控制表示,進程的生命周期中共有三種狀態:
1. name-runing
2. name-end
3. name-ending
操作只有兩種:啟動進程 和 停止進程。
只有 `無狀態文件` 和 `name-end` 時可以啟動進程(已停止 時可以啟動);只有 `name-runing` 時可以停止進程。
#### 確保安全,無并發問題:鎖
**程序運行過程中,會一直鎖住當前的狀態文件。**
這樣就能防止重復執行程序,確保沒有并發問題,保證其安全性。
鎖住當前狀態文件,除了保證程序的安全性,還能確保用戶不能手動誤刪除狀態文件,保護程序不會受外部影響,保證其可用性。
#### 怎樣判斷程序的運行狀態?
**運行中:** `name-runing`
**沒有運行:**
停止中:`name-ending`
已停止:`name-end` 或 `沒有任何狀態文件`(停止后,由于沒有鎖,狀態文件可能被用戶刪除)
>[danger] 計劃任務,比如每隔一分鐘,掃描表,將沒有IP位置的字段全部更新(IP \> IP城市),如果檢測上次的任務進程還在執行,也就是還在崗,那么本次放棄執行,否則就會出現前一分鐘的進程和當前進程一起執行,造成重復并發問題而重復消費了,所以每個進程開始前都需要檢查狀態。其實就相當于計劃任務每次檢測當前有無執行,有則不管了,沒有則啟動一個,相當于是一個喚醒,睡著的就把你喚醒,醒著的就不必喚了,**所以計劃任務其實是一個喚醒任務。**
> 這個不用文件記錄狀態,用MySQL表也可以,但是要用Inndb行鎖,任務名字段唯一索引。
>
> 后臺進程表(id,任務名,進程ID:每次可能都不一樣,本次執行之間,本次結束時間,狀態,啟動命令)
#### 運行日志
出了程序的運行狀態文件,我們還可以增加一個程序運行日志文件,記錄程序生命周期的運行動作:
```log
2018-4-30 12:00:37 start
2018-4-30 12:01:59 runing
2018-4-30 12:02:27 ending
2018-4-30 12:02:34 end
```
(運行日志只記錄程序的生命中期中的運行狀態等動作,不記錄程序本身日志信息)
* * * * *
### 再談阻塞

[Go并發編程案例解析](http://www.imooc.com/learn/982)
雖然最后還是阻塞的,總的執行時間無論怎樣都是一樣的,只是采用這樣非阻塞的方式運行可以異步的部分,在一定意義上達到了控制程序執行順序的目的了,如提前返回部分數據給用戶,使其盡早呈現結果,異步使得程序更加靈活,提高了程序整體的運行效率,雖然總的執行時間都是一樣的。
說錯了,串行的話總時間都是一樣的,真正異步并行執行的話(同時做多件事),執行時間會少。
* * * * *
### 擴展
[隊列 · php筆記 · 看云](http://www.hmoore.net/xiak/php-node/347805)
[RabbitMQ, ZeroMQ, Kafka 是一個層級的東西嗎, 相互之間有哪些優缺點? - 知乎](https://www.zhihu.com/question/22480085/answer/23106407)
[深入分析消息中間件的選型(AMQ,RMQ,Kafka,KMQ,ZMQ等)](https://www.toutiao.com/a6540024140257034759/?tt_from=weixin&utm_campaign=client_share×tamp=1522769518&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1)
> 并且消息中間件服務器(一般簡單的稱之為Broker)中沒有消息堆積,……,消息中間件大道至簡:一發一存一消費,沒有最好的消息中間件,只有最合適的消息中間件。
[【系統架構】Web系統大規模并發:電商秒殺與搶購](http://mp.weixin.qq.com/s/zDbcV_vJeBOnAYxK0WEJQQ)
> 必須盡可能“快”,在最短的時間里返回用戶的請求結果。為了實現盡可能快這一點,接口的后端存儲使用內存級別的操作會更好一點。仍然直接面向 MySQL之類的存儲是不合適的,如果有這種復雜業務的需求,都建議采用異步寫入。
>
> 當然,也有一些秒殺和搶購采用“滯后反饋”,就是說秒殺當下不知道結果,一段時間后才可以從頁面中看到用戶是否秒殺成功。但是,這種屬于“偷懶”行為,同時給用戶的體驗也不好,容易被用戶認為是“暗箱操作”。(這個沒有辦法,不異步你就干等啊,異步就是告訴你正在排隊啊)
>
> 更可怕的問題是,是用戶的行為特點,系統越是不可用,用戶的點擊越頻繁,惡性循環最終導致“雪崩”
>
> 我們知道在多線程寫入同一個文件的時候,會存現“線程安全”的問題(多個線程同時運行同一段代碼,如果每次運行結果和單線程運行的結果是一 樣的,結果和預期相同,就是線程安全的)。(即沒有并發問題,也可客觀的理解為操作的冪等性)
>
> FIFO隊列:這樣的話,我們就不會導致某些請求永遠獲取不到鎖。看到這里,是不是有點**強行將多線程變成單線程**的感覺哈。(解決并發問題,鎖,隊列異步操作,其實都是將并行強制變為串行的解決方案。)
[【系統架構】聊聊開源消息中間件的架構和原理](https://mp.weixin.qq.com/s/NwjYJde9_TC4PXMPpYw1Gw)
[RocketMQ 源碼合集](https://mp.weixin.qq.com/s/xrnEMQ07kuaE9oCK1Z71hg)
[消息隊列應用場景 - 13070113 - 博客園](http://www.cnblogs.com/stopfalling/p/5375492.html)
> 按照以上約定,用戶的響應時間相當于是注冊信息寫入數據庫的時間,也就是50毫秒。**注冊郵件,發送短信寫入消息隊列后,直接返回,<span style="color:red;">因為寫入消息隊列的速度很快,基本可以忽略</span>**,因此用戶的響應時間可能是50毫秒。因此架構改變后,系統的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了兩倍。
[消息隊列使用的四種場景 - CSDN博客](https://blog.csdn.net/ntotl/article/details/72765713)(原文)
[【原創】分布式之延時任務方案解析 - 孤獨煙 - 博客園](https://www.cnblogs.com/rjzheng/p/8972725.html)
[為什么分布式一定要有redis?](https://mp.weixin.qq.com/s/gEU8HtsQNPXY8bzkK-Qllg)
> 一剎那者為一念,二十念為一瞬,二十瞬為一彈指,二十彈指為一羅預,二十羅預為一須臾,一日一夜有三十須臾。
> 那么,經過周密的計算,一瞬間為0.36 秒,一剎那有 0.018 秒.一彈指長達 7.2 秒。
>[danger] **任何東西都要去深入思考,做到面面俱到,考慮每一種可能性,應對每一種突發情況,這樣才能保證是嚴謹的。** 避重就輕,拈輕怕重,避實就虛,怕麻煩,不肯深入問題的本質,這樣永遠都只會停留在表面,無法解決核心問題。
[【原創】分布式之消息隊列復習精講 - 孤獨煙 - 博客園](http://www.cnblogs.com/rjzheng/p/8994962.html)
~~~
1:為什么使用消息隊列?
2:使用消息隊列有什么缺點?
3:消息隊列如何選型?
4:如何保證消息隊列是高可用的?
5:**如何保證消息不被重復消費?**
6:如何保證消費的可靠性傳輸?
7:如何保證消息的順序性?
~~~
>[danger] 如何保證不會出現重復消費消息?
>
> 我們不要總是將目光聚集在**如何防止重復取消息**的問題上,雖然這是造成重復消費問題的根源(其實這只是會造成重復消費的原因之一,還有 消費確認失效 等情況也可能會造成這個問題),但是解決重復消費的問題并不是只有這一個辦法,不要緊盯目標,而忘記自己本來是要干什么了,被自己狹窄的眼界給局限了。**不能死板,不能墨守成規,轉變思路,側面突圍,用最令人意想不到的方式解決問題才更漂亮。** 多給自己提問,當你面對這個問題時,你實際是在面對什么,當你在解決某個問題時,你實際是在解決什么。
>
> 將目光從如何防止重復取消息上移開,還有很多解決這個問題的辦法:1. 增加消費記錄表,記錄消費的情況;2. 利用業務數據自己比對消費記錄;3. 消息ID為主鍵,做insert;**其實方案都是消費時自己再確認一遍,這也符合了正確性從不依賴于外部或其他系統的原則(比如不依賴,不信任取消息操作是否會重復取),不過很重要的一點是不能忘記鎖,查詢時一定要互斥鎖,不然還是于事無補,因為會出現并發問題。**
[【系統架構】分布式之消息隊列復習精講(上)](https://mp.weixin.qq.com/s/uRaG2ZB8hBoxum73OHDHNQ)
[從單一架構到分布式交易架構,網易嚴選的成功實踐](https://mp.weixin.qq.com/s/nv3Ht7OqTYQw31QFDX3gNg)
>[danger] **沒有完美的架構設計,世上也沒有絕對的事情,沒有誰能保證絕對可靠、安全和高可用,但我們有補償和容錯(類似還有重試,確認等機制),也是能做到萬無一失的。**
[【消息隊列 MQ 專欄】消息隊列之 ActiveMQ](https://mp.weixin.qq.com/s/ngfCCsuYJHBc6gRTIROgMQ)
[分布式消息隊列 RocketMQ 源碼分析 —— Message 拉取與消費(上)](https://mp.weixin.qq.com/s/EwZDg5IRHJ5TGfGvpfl9Ew)
[數據結構 | Java 隊列 —— Queue 詳細分析](https://mp.weixin.qq.com/s/FH5lQac65CT9Pt_TpT1E7Q)
mq和數據庫區別主要是是否解決了通知問題
* * * * *
關鍵字:線程安全
* * * * *
### Lua:代碼級的原子性
[Lua 是一個小巧的腳本語言 - HackerVirus - 博客園](https://www.cnblogs.com/Leo_wl/p/8405661.html)
> lua腳本是用C語言寫的,體積很小,運行速度很快,并且每次的執行都是作為一個原子事務來執行的
>
> 原子性的操作:Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
[Lua 與 Redis - 挨踢人啊 - 博客園](https://www.cnblogs.com/itrena/p/5926878.html)
[redis實現秒殺功能例子(采用lua的原子性保證數據的一致性) - CSDN博客](https://blog.csdn.net/futao127/article/details/80617214)
[利用redis + lua解決搶紅包高并發的問題 - CSDN博客](https://blog.csdn.net/hengyunabc/article/details/19433779/)
* * * * *
### 定時計劃:后知后覺 圖

* * * * *
last update:2018-1-16 14:10:56
- 開始
- 公益
- 更好的使用看云
- 推薦書單
- 優秀資源整理
- 技術文章寫作規范
- SublimeText - 編碼利器
- PSR-0/PSR-4命名標準
- php的多進程實驗分析
- 高級PHP
- 進程
- 信號
- 事件
- IO模型
- 同步、異步
- socket
- Swoole
- PHP擴展
- Composer
- easyswoole
- php多線程
- 守護程序
- 文件鎖
- s-socket
- aphp
- 隊列&并發
- 隊列
- 講個故事
- 如何最大效率的問題
- 訪問式的web服務(一)
- 訪問式的web服務(二)
- 請求
- 瀏覽器訪問阻塞問題
- Swoole
- 你必須理解的計算機核心概念 - 碼農翻身
- CPU阿甘 - 碼農翻身
- 異步通知,那我要怎么通知你啊?
- 實時操作系統
- 深入實時 Linux
- Redis 實現隊列
- redis與隊列
- 定時-時鐘-阻塞
- 計算機的生命
- 多進程/多線程
- 進程通信
- 拜占庭將軍問題深入探討
- JAVA CAS原理深度分析
- 隊列的思考
- 走進并發的世界
- 鎖
- 事務筆記
- 并發問題帶來的后果
- 為什么說樂觀鎖是安全的
- 內存鎖與內存事務 - 劉小兵2014
- 加鎖還是不加鎖,這是一個問題 - 碼農翻身
- 編程世界的那把鎖 - 碼農翻身
- 如何保證萬無一失
- 傳統事務與柔性事務
- 大白話搞懂什么是同步/異步/阻塞/非阻塞
- redis實現鎖
- 淺談mysql事務
- PHP異常
- php錯誤
- 文件加載
- 路由與偽靜態
- URL模式之分析
- 字符串處理
- 正則表達式
- 數組合并與+
- 文件上傳
- 常用驗證與過濾
- 記錄
- 趣圖
- foreach需要注意的問題
- Discuz!筆記
- 程序設計思維
- 抽象與具體
- 配置
- 關于如何學習的思考
- 編程思維
- 談編程
- 如何安全的修改對象
- 臨時
- 臨時筆記
- 透過問題看本質
- 程序后門
- 邊界檢查
- session
- 安全
- 王垠
- 第三方數據接口
- 驗證碼問題
- 還是少不了虛擬機
- 程序員如何談戀愛
- 程序員為什么要一直改BUG,為什么不能一次性把代碼寫好?
- 碎碎念
- 算法
- 實用代碼
- 相對私密與絕對私密
- 學習目標
- 隨記
- 編程小知識
- foo
- 落盤
- URL編碼的思考
- 字符編碼
- Elasticsearch
- TCP-IP協議
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依賴注入
- 科目一
- 開發筆記
- 經緯度格式轉換
- php時區問題
- 解決本地開發時調用遠程AIP跨域問題
- 后期靜態綁定
- 談tp的跳轉提示頁面
- 無限分類問題
- 生成微縮圖
- MVC名詞
- MVC架構
- 也許模塊不是唯一的答案
- 哈希算法
- 開發后臺
- 軟件設計架構
- mysql表字段設計
- 上傳表如何設計
- 二開心得
- awesomes-tables
- 安全的代碼部署
- 微信開發筆記
- 賬戶授權相關
- 小程序獲取是否關注其公眾號
- 支付相關
- 提交訂單
- 微信支付筆記
- 支付接口筆記
- 支付中心開發
- 下單與支付
- 支付流程設計
- 訂單與支付設計
- 敏感操作驗證
- 排序設計
- 代碼的運行環境
- 搜索關鍵字的顯示處理
- 接口異步更新ip信息
- 圖片處理
- 項目搭建
- 閱讀文檔的新方式
- mysql_insert_id并發問題思考
- 行鎖注意事項
- 細節注意
- 如何處理用戶的輸入
- 不可見的字符
- 抽獎
- 時間處理
- 應用開發實戰
- python 學習記錄
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文檔相似度驗證
- thinkphp5.0數據庫與模型的研究
- workerman進程管理
- workerman網絡分析
- java學習記錄
- docker
- 筆記
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京東
- pc_detailpage_wareBusiness
- doc
- 電商網站設計
- iwebshop
- 商品規格分析
- 商品屬性分析
- tpshop
- 商品規格分析
- 商品屬性分析
- 電商表設計
- 設計記錄
- 優惠券
- 生成唯一訂單號
- 購物車技術
- 分類與類型
- 微信登錄與綁定
- 京東到家庫存系統架構設計
- crmeb
- 命名規范
- Nginx https配置
- 關于人工智能
- 從人的思考方式到二叉樹
- 架構
- 今日有感
- 文章保存
- 安全背后: 瀏覽器是如何校驗證書的
- 避不開的分布式事務
- devops自動化運維、部署、測試的最后一公里 —— ApiFox 云時代的接口管理工具
- 找到自己今生要做的事
- 自動化生活
- 開源與漿果
- Apifox: API 接口自動化測試指南