1. 程式人生 > >用MySQL模擬訊息佇列

用MySQL模擬訊息佇列

  1. 佇列(queue)是一種先進先出FIFO的線性表結構,只允許在尾部插入和在頭部刪除。最近一兩年火爆的團購秒殺等應用,其中的大併發是會把資料庫壓垮的,一個解決方法就是把HTTP請求放入記憶體中的高速佇列,然後對佇列裡的資料按一定的規則進行分流處理,這就是HTTP請求佇列。比如,微博和SNS通常擁有上億的受眾數量,一個明星或公眾人物可能有幾千萬的粉絲,如果一個公眾人物發了條微博,那麼就得推送到所有關注者那裡(有推策略、拉策略、隨機策略、優先策略等),這時就必須用到佇列。面對大資料量和高併發的WEB應用,佇列工具可大有所為。

  2. 下面以SNS中的動態分發的設計過程為例進行講解佇列在訊息高度機制中的實現和使用。
    當某使用者釋出一條動態的時候,先把這條動態插入feed表,然後判斷當前使用者組,普通朋友則給所有好友釋出廣播,往feedBroadCast表寫入冗餘資料,但這種頻繁不間斷的讀寫資料庫會給伺服器造成很大壓力,注意到好友動態不必具有十分高的實時要求,所以這裡採用非同步的推,把動態推給好友而不是好友主支拉取。(我理解為在冗餘表裡非同步寫入資料,不是很理解把動態推給好友 是什麼意思。。)
    隨著使用者增加,在前端頁面直接定稿大量資料會延長使用者的等待時間,在多併發的情況下頁面效率低壓力集中,為解決上述問題,考慮使用資料的非同步處理,而訊息佇列的背後實質就是一種“非同步處理”的思想。
    “訊息佇列”是在訊息的傳輸過程中儲存訊息的容器。訊息佇列管理器將訊息從它的源中繼到它的目標時充當中間人的角色。佇列主要提供跌幅並保證訊息的傳遞;如果傳送訊息時接收者不可用,訊息佇列會保留訊息直到成功傳遞。利用訊息佇列可以很好地非同步處理資料傳送和儲存,當頻繁地向資料中插入資料時就可採用訊息佇列非同步插入,另外可將較慢的處理邏輯、有併發數量限制的處理邏輯,通過訊息佇列放在後臺處理如視訊轉換髮送手機簡訊等。
    訊息傳送很簡單,在動態產生之後,直接在DB中插入一條記錄即可,這裡,完全依賴資料庫來模擬實現訊息佇列。接收者從已獲取的訊息列表中取出下一條訊息。
    在佇列中儲存的是訊息,而不是實際要分發和處理的資料。秒殺佇列中儲存的僅僅是一個HTTP請求,SNS佇列中儲存的僅僅是一條製造出來的動態,而不是所有要分發的動態。動態的頒發不是訊息佇列所負責的,其由另一個程式處理。

  1. 用資料庫模擬訊息佇列。新建一個訊息隊列表,結構如下:DROP TABLE IF EXIISTS 'eventqueue'; CREATE TABLE IF NOT EXISTS 'eventqueue' ('qid' int(11) NOT NULL AUTO_INCREMENT COMMENT '訊息佇列','topic' tinyint(4) NOT NULL COMMENT '應用類別','status' tinyint(4) NOT NULL COMMENT '狀態,標識是否進行分發,0為未發1已分發','data' varchar(1024) COLLATE gbk_bin DEFAULT NULL COMMENT '訊息內容','uid' int(11) NOT NULL COMMENT '動態產生者的uid','create_date' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '傳送時間',PRIMARY KEY('qid')) ENGINE=MEMORY DEFAULT CHARSET=gbk COLLATE=gbk_bin COMMENT='訊息隊列表,用以記錄訊息的屬性及內容'
    上述結構中存放訊息的產生者的是uid而不是具體的動態內容,data欄位是訊息的內容並非動態的內容,動態的內容儲存在feed表中。訊息佇列佇列有了,還需要一個對訊息佇列進行輪詢高度的程式。對此在資料庫中建立3個儲存過程和一個定時器來實現模擬:
    儲存過程名----------------------作用------------------說明
    Proc_msg_receiver_friend---潤滑處理好友的動態分----從訊息佇列中獲取訊息,業務處理:給好友發動態
    Proc_msg_receiver_vip------接收並處理公共主頁的動態分發---從訊息佇列中獲取訊息,業務處理:給關注者發訊息
    msg_scheduler--------------排程程式-------------根據伺服器訪問的高峰期不同,排程訊息接收者從訊息佇列中獲取訊息
    Proc_msg_cleaner-----------訊息刪除程式------------定期刪除訊息佇列中已經接收的資料
    下面來看其中一個訊息分發過程,這是通過一個儲存過程實現的:
    DROP PROCEDURE IF EXISTS 'proc_msg_receiver_friend';
    DELIMITER //
    CREATE DEFINDER ='TEST'@'%' PROCEDURE 'proc_msg_receiver_friend'()
    COMMENT '訊息佇列中好友分發儲存過程'
    BEGIN
    DECLARE CONTINUE HANDLER FOR SQL EXCEPTION ROLLBACK;
    START TRANSACTION;
    --好友動態分發
    INSERT INTO feed_broadcast
    SELECT feed_id,ef.fri_uid,temp.create_date,app_id,src_type
    FROM (SELECT * FROM eventqueue AS eq WHERE eq.status=0 AND eq.topic=1
    ORDER BY create_date DESC LIMIT 100) AS temp, friend as ef
    WHERE temp.uid=ef.uid;
    --更改動態訊息狀態
    UPDATE eventqueue AS eq SET status=1
    WHERE eq.status=0 AND eq.topic=1
    ORDER BY create_date DESC LIMIT 100;
    COMMIT
    END //
    DELIMITER ;
    有了訊息和訊息的分發,接下來需要通過排程決定分發的策略、時間和頻率,利用MYSQL的event實現排程。
    下面是訊息清除器的event:
    DROP EVENT IF EXISTS 'event_msg_cleaner';
    DELIMITER //
    CREATE EVENT 'event_msg_cleaner' ON SCHEDULE EVERY 1 HOUR STATTS '2016-03-02 00:00:00'
    也許看到這裡覺得和常規實現沒多大區別,只不過把頁面上的摘取操作變為伺服器上的推操作還麻煩了許多。但是,通過進位制的高度把原先一瞬間可能發生的成千上萬條動態分發的insert操作延遲了,並且是定製分發量,把高峰期的負載分一部分到低俗時間段進行處理,這樣能減少很多負擔。使用MYSQL的表來儲存和處理訊息佇列確實不夠快且會造成資料庫的壓力,但我們可以把訊息佇列放到以快著稱的記憶體快取中如Memcached.如果再配合上作業管理系統,這個分發機制 的策略還能更復雜,甚至可進行優先順序管理。
    這樣,佇列的“非同步操作”特點就體現出來了,對於VIP使用者或者活躍使用者優先分發,對“垃圾”使用者延遲分發。通過指定訊息的優先順序就能分清輕重緩急,合理分攤伺服器負擔。
    佇列的非同步操作的優點:部署簡單,公使用MYSQL的內建儲存過程和定時器即可完成大部分排程,伺服器部署也很簡單,易遷移。
    佇列的非同步操作的缺點:對於大數理量、上千萬的訊息佇列,即使使用資料庫模擬訊息佇列也力不從心,另外這種方法實時性不高,對於動態分發、郵件、簡訊等實時性相對力低的應用可以採用這種方案進行處理,但是不能用於對實時性要求很高的場所。當訊息很大時,可把訊息放入內在或者進行叢集分發。
    訊息佇列的應用場景取決於訊息的重要性,如果是重要的訊息,就必須一條一條的分發不允許丟棄。關於HTTP佇列,金山公司內部有一開源作品HTTPSQL,其基於Tokyo Cabinet的B+treeKEY/VALUE資料庫做資料的持久化儲存。這個作品正是把這裡的佇列從MYSQL移到記憶體資料庫中,並且由一個專門的程序負責佇列的操作,客戶端則有通過Socket請求進行讀寫。HTTPSQL和訊息佇列的實現思路是一樣的,不過結合了NSQL,能更快,並支撐更高的併發量。
  2. https://blog.csdn.net/denglitong/article/details/62089783