自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

詳解使用Redis作為消息隊(duì)列服務(wù)場景應(yīng)用案例

存儲 存儲軟件 Redis
“消息”是在兩臺計(jì)算機(jī)間傳送的數(shù)據(jù)單位。消息可以非常簡單,例如只包含文本字符串;也可以更復(fù)雜,可能包含嵌入對象。消息被發(fā)送到隊(duì)列中,“消息隊(duì)列”是在消息的傳輸過程中保存消息的容器。

 一、消息隊(duì)列場景簡介

“消息”是在兩臺計(jì)算機(jī)間傳送的數(shù)據(jù)單位。消息可以非常簡單,例如只包含文本字符串;也可以更復(fù)雜,可能包含嵌入對象。消息被發(fā)送到隊(duì)列中,“消息隊(duì)列”是在消息的傳輸過程中保存消息的容器。

在目前廣泛的Web應(yīng)用中,都會出現(xiàn)一種場景:在某一個(gè)時(shí)刻,網(wǎng)站會迎來一個(gè)用戶請求的高峰期(比如:淘寶的雙十一購物狂歡節(jié),12306的春運(yùn)搶票節(jié)等),一般的設(shè)計(jì)中,用戶的請求都會被直接寫入數(shù)據(jù)庫或文件中,在高并發(fā)的情形下會對數(shù)據(jù)庫服務(wù)器或文件服務(wù)器造成巨大的壓力,同時(shí)呢,也使響應(yīng)延遲加劇。這也說明了,為什么我們當(dāng)時(shí)那么地抱怨和吐槽這些網(wǎng)站的響應(yīng)速度了。當(dāng)時(shí)2011年的京東圖書促銷,曾一直出現(xiàn)在購物車中點(diǎn)擊“購買”按鈕后一直是“Service is too busy”,其實(shí)就是因?yàn)楫?dāng)時(shí)的并發(fā)訪問量過大,超過了系統(tǒng)的最大負(fù)載能力。當(dāng)然,后邊,劉強(qiáng)東臨時(shí)購買了不少服務(wù)器進(jìn)行擴(kuò)展以求增強(qiáng)處理并發(fā)請求的能力,還請了信息部的人員“喝茶”,現(xiàn)在京東已經(jīng)是超大型的網(wǎng)上商城了,我也有同學(xué)在京東成都研究院工作了。

[[224240]]

從京東當(dāng)年的“Service is too busy”不難看出,高并發(fā)的用戶請求是網(wǎng)站成長過程中必不可少的過程,也是一個(gè)必須要解決的難題。在眾多的實(shí)踐當(dāng)中,除了增加服務(wù)器數(shù)量配置服務(wù)器集群實(shí)現(xiàn)伸縮性架構(gòu)設(shè)計(jì)之外,異步操作也被廣泛采用。而異步操作中最核心的就是使用消息隊(duì)列,通過消息隊(duì)列,將短時(shí)間高并發(fā)產(chǎn)生的事務(wù)消息存儲在消息隊(duì)列中,從而削平高峰期的并發(fā)事務(wù),改善網(wǎng)站系統(tǒng)的性能。在京東之類的電子商務(wù)網(wǎng)站促銷活動(dòng)中,合理地使用消息隊(duì)列,可以有效地抵御促銷活動(dòng)剛開始就開始大量涌入的訂單對系統(tǒng)造成的沖擊。

記得我在實(shí)習(xí)期間,成都市XXXX局的一個(gè)價(jià)格信息采集發(fā)布系統(tǒng)項(xiàng)目中有一個(gè)采集任務(wù)發(fā)布的模塊,其中每個(gè)任務(wù)都是一個(gè)事務(wù),這個(gè)事務(wù)中需要向數(shù)據(jù)庫中不斷地插入行,每個(gè)任務(wù)發(fā)布時(shí)都要往表中插入幾百行甚至幾千行的任務(wù)數(shù)據(jù)(比如價(jià)格采集日報(bào),往往需要發(fā)布2-3年的任務(wù)數(shù)據(jù),每一天都是一個(gè)任務(wù),所以大約有2,3千行任務(wù)期號數(shù)據(jù),還要發(fā)給很多個(gè)區(qū)縣的監(jiān)測中心,因此數(shù)據(jù)庫寫操作量很大,更別說同時(shí)發(fā)布的并發(fā)操作),由于業(yè)務(wù)邏輯的處理比較復(fù)雜和往數(shù)據(jù)庫的寫操作量交大,所以在沒有采用消息隊(duì)列時(shí)點(diǎn)擊“發(fā)布”按鈕后往往需要等待1分鐘左右的時(shí)間才提示“發(fā)布成功”,用戶體驗(yàn)極不友好。

這時(shí),我們就可以使用消息隊(duì)列的思想來重構(gòu)這個(gè)發(fā)布模塊,在用戶點(diǎn)擊“發(fā)布”按鈕后,系統(tǒng)只需要把往數(shù)據(jù)庫插入的這個(gè)事務(wù)信息插入到指定的任務(wù)發(fā)布消息隊(duì)列里邊去(入隊(duì)操作,這里一般有一臺獨(dú)立的消息隊(duì)列服務(wù)器來單獨(dú)存儲和處理),然后系統(tǒng)就可以立即對用戶的這個(gè)發(fā)布請求進(jìn)行響應(yīng)(比如給出一個(gè)發(fā)布成功的操作提示,這里暫不考慮消息隊(duì)列服務(wù)操作失敗的情形,如果失敗了,可以考慮采用給用戶發(fā)送郵件、短信或站內(nèi)消息,讓其重新進(jìn)行發(fā)布操作)。

最后,消息隊(duì)列服務(wù)器中有一個(gè)進(jìn)程單獨(dú)對消息隊(duì)列進(jìn)行處理,首先判斷消息隊(duì)列中是否有待處理的消息,如果有,則將其取出(出隊(duì)操作,堅(jiān)持“先進(jìn)先出”的順序,保證事務(wù)的準(zhǔn)確性)進(jìn)行相應(yīng)地處理(比如這里是進(jìn)行保存數(shù)據(jù)的操作,將數(shù)據(jù)插入到數(shù)據(jù)庫服務(wù)器中的指定數(shù)據(jù)庫里邊,實(shí)質(zhì)還是文件的IO操作)。就這樣,通過消息隊(duì)列將高并發(fā)用戶請求進(jìn)行異步操作,然后一一對消息隊(duì)列進(jìn)行出隊(duì)的同步操作,也避免了并發(fā)控制的難題。

說到這里,大家可能會想到這尼瑪不就是生產(chǎn)者消費(fèi)者模式么?對的,么么嗒,消息隊(duì)列就是生產(chǎn)者消費(fèi)者模式的典型場景。簡單地說,客戶端不同用戶發(fā)送的操作請求就是生產(chǎn)者,他們將要處理的事務(wù)存儲到消息隊(duì)列中,然后消息隊(duì)列服務(wù)器的某個(gè)進(jìn)程不停地將要處理的單個(gè)事務(wù)從消息隊(duì)列中一個(gè)一個(gè)地取出來進(jìn)行相應(yīng)地處理,這就是消費(fèi)者消費(fèi)的過程。

下面我們將以異常日志為案例,介紹在.Net中如何采用消息隊(duì)列的思想解決并發(fā)問題。當(dāng)然,消息隊(duì)列只是解決并發(fā)問題的其中一種方式,在實(shí)際中往往需要結(jié)合多種不同的技術(shù)方式來共同解決,比如負(fù)載均衡、反向代理、集群等方案。這里,雖然以異常日志為案例,但是“麻雀雖小五臟俱全”,日志寫入文件的高并發(fā)操作也同樣適用于數(shù)據(jù)庫的高并發(fā),所以,研究這個(gè)案例是具有實(shí)際意義的。

二、使用預(yù)置類型實(shí)現(xiàn)異常日志隊(duì)列

在日常的Web應(yīng)用中,異常日志的記錄是一個(gè)十分重要的要點(diǎn)。因?yàn)?,人無完人,系統(tǒng)也一樣,難免會在什么時(shí)候出一個(gè)測試階段未能完全測試到的異常。這時(shí)候,不能將異常信息直接顯示給客戶,那樣既不友好也不安全。所以,一般都采用將異常信息記錄到日志文件中(比如某個(gè)txt文件,數(shù)據(jù)庫中某個(gè)表等),然后技術(shù)支持人員通過查看異常日志,分析異常原因,改進(jìn)BUG重新發(fā)布,保障系統(tǒng)正常運(yùn)行。

在用戶的各種操作中,如果出現(xiàn)異常的時(shí)間一致,那么記錄異常日志的操作就會成為并發(fā)操作,而記錄異常日志又屬于文件的IO操作(其實(shí)數(shù)據(jù)庫的讀寫歸根結(jié)底也是對文件即對磁盤進(jìn)行的IO操作),因此很有可能帶來并發(fā)控制的一系列問題。在以往的編碼實(shí)踐中,我們可以通過給不同的IO請求進(jìn)行加鎖(C#中的lock),等第一個(gè)請求完成寫入后釋放鎖,第二個(gè)請求再獲得鎖,進(jìn)行IO操作,然后釋放掉,一直到第N個(gè)請求釋放后結(jié)束。這種方式,雖然解決了并發(fā)操作帶來的問題,但是通過加鎖延遲了用戶響應(yīng)請求的時(shí)間(比如第一個(gè)正在IO寫入操作時(shí),后面的均處于等待狀態(tài)),并且加鎖也會給服務(wù)器帶來一定的性能負(fù)擔(dān),造成服務(wù)器性能的下降。

基于以上原因,我們采用消息隊(duì)列的思想將異常日志的記錄操作改為隊(duì)列版,這里我們先不采用Redis,直接使用.Net為我們提供的預(yù)置類型-Queue。接下來,就讓我們動(dòng)手開刀,寫起來。

(1)新建一個(gè)ASP.NET MVC 4項(xiàng)目,選擇“基本”類型,視圖引擎選擇“Razor”。

(2)既然是異常日志記錄,首先得有異常。這時(shí),我們腦海中想到了那個(gè)經(jīng)典的異常:DividedByZeroException。于是,在Controllers文件夾中新建一個(gè)Controller,取名為Home(這里因?yàn)镚lobal文件中的默認(rèn)路由就指向了Home控制器中的Index這個(gè)Action),在HomeController中修改Index這個(gè)Action的代碼如下:

  1. public ActionResult Index() 
  2.        { int a = 10; int b = 0; 
  3.         int c = a / b; 
  4.         //會拋一個(gè)DividedByZero的異常  
  5.         return View(); 
  6.        } 

(3)在ASP.NET MVC項(xiàng)目中,我們需要在Global.asax中的Application_Start這個(gè)事件中修改全局過濾器(主要是App_Start中的FilterConfig類的RegisterGlobalFilters這個(gè)方法),讓系統(tǒng)支持對異常的全局處理操作(我們這里主要是對異常進(jìn)行記錄到指定文件中)。PS:Application_Start是整個(gè)Web應(yīng)用的起始事件,主要進(jìn)行一些配置(如過濾器配置、日志器配置、路由配置等等)的初始化操作,當(dāng)然這些配置也只會進(jìn)行一次。

  1. public class FilterConfig 
  2.     { public static void RegisterGlobalFilters 
  3.     (GlobalFilterCollection filters) 
  4.     { // MyExceptionFilterAttribute繼承自HandleError, 
  5.     主要作用是將異常信息寫入日志文件中 filters.Add 
  6.     (new MyExceptionFilterAttribute()); // 
  7.      默認(rèn)的異常記錄類 filters.Add(new  
  8.      HandleErrorAttribute 
  9.      ()); 
  10.         } 
  11.     } 

通過改寫過濾器配置,我們向全局過濾器中注冊了一個(gè)異常處理的過濾器配置,那么這個(gè)MyExceptionFilterAttribute類又是如何編寫的呢?

通過使該類繼承HandlerErrorAttribute并使其覆寫OnException這個(gè)事件,代表在異常發(fā)生時(shí)可以進(jìn)行的操作。而我們在這兒主要通過一個(gè)異常隊(duì)列將獲取的異常寫入隊(duì)列,然后跳轉(zhuǎn)到自定義錯(cuò)誤頁:~/Common/CommonError.html,這個(gè)錯(cuò)誤頁很簡單,就是簡單的顯示“系統(tǒng)發(fā)生錯(cuò)誤,5秒后自動(dòng)跳轉(zhuǎn)到首頁”

(4)走到這里,生產(chǎn)者消費(fèi)者模式中生產(chǎn)者的任務(wù)已經(jīng)完成了,接下來消費(fèi)者就需要開始消費(fèi)了。也就是說,消息隊(duì)列已經(jīng)建好了,我們什么時(shí)候從隊(duì)列中去任務(wù),在哪里執(zhí)行?怎么樣執(zhí)行?通過上面的介紹,我們知道,在專門的消息隊(duì)列服務(wù)器中有一個(gè)進(jìn)程在始終不停地監(jiān)視消息隊(duì)列,如果有需要待辦的任務(wù)信息,則會立即從隊(duì)列中取出來執(zhí)行相應(yīng)的操作,直到隊(duì)列為空為止。于是,思路有了,我們馬上來實(shí)現(xiàn)以下。這個(gè)消息監(jiān)視的操作也是一個(gè)全局操作,在系統(tǒng)啟動(dòng)時(shí)就會一直運(yùn)行,于是它也應(yīng)該寫在Application_Start這個(gè)全局起始事件里邊,于是按照標(biāo)準(zhǔn)的配置寫法,我們在Application_Start中添加了如下代碼:MessageQueueConfig.RegisterExceptionLogQueue();

  1. protected void Application_Start() 
  2.         { 
  3.             AreaRegistration.RegisterAllAreas(); 
  4.  
  5.             WebApiConfig.Register 
  6.             (GlobalConfiguration.Configuration); 
  7.             FilterConfig.RegisterGlobalFilters 
  8.             (GlobalFilters.Filters); 
  9.             RouteConfig.RegisterRoutes 
  10.             (RouteTable.Routes); 
  11.             BundleConfig.RegisterBundles 
  12.             (BundleTable.Bundles); 
  13.  
  14.             //自定義事件注冊 
  15.             MessageQueueConfig. 
  16.             RegisterExceptionLogQueue 
  17.             (); 
  18.         } 

那么,這個(gè)MessageQueueConfig.RegisterExceptionLogQueue()又是怎么寫的呢?

現(xiàn)在,讓我們來看看這段代碼:

①首先定義Log文件存放的文件夾目錄,這里我們一般放到App_Data里邊,因?yàn)榉诺竭@里邊外網(wǎng)是無法訪問到的,可以防止下載操作;

②其次通過線程池ThreadPool開啟一個(gè)線程,不停地監(jiān)聽消息隊(duì)列里邊的待辦事項(xiàng)個(gè)數(shù),如果個(gè)數(shù)>0,則進(jìn)行出隊(duì)(FIFO,先入隊(duì)的先出隊(duì))操作。這里主要是取出具體的異常實(shí)例對象,并將異常的具體堆棧信息追加寫入到指定命名格式的文件中。

PS:許多應(yīng)用程序創(chuàng)建的線程都要在休眠狀態(tài)中消耗大量時(shí)間,以等待事件發(fā)生。其他線程可能進(jìn)入休眠狀態(tài),只被定期喚醒以輪詢更改或更新狀態(tài)信息。線程池通過為應(yīng)用程序提供一個(gè)由系統(tǒng)管理的輔助線程池使您可以更為有效地使用線程。

③如果該線程檢測到消息隊(duì)列中無待辦事項(xiàng),則使用Thread.Sleep使線程“休息”一會,避免了CPU空轉(zhuǎn)(從理論上來說,CPU資源是很珍貴的,應(yīng)該盡量提高CPU的利用率)。

(5)最后,我們來看看效果如何?

①首先,高大上的VS捕捉到了異常-DividedByZeroException:

②按照我們的全局異常處理過濾器,會將此異常記入隊(duì)列中,并返回HTTP 302重定向跳轉(zhuǎn)到自定義錯(cuò)誤頁面:

③最后,打開App_Data文件夾,查看日志文件:

到這里時(shí),我們已經(jīng)借助消息隊(duì)列的思想完成了一個(gè)自定義的異常日志隊(duì)列服務(wù)。但也許有朋友會說,這個(gè)跟Redis有關(guān)系么?異常日志不都是用Log4Net么?不要著急,后邊我們就會使用Redis+Log4Net來重構(gòu)這個(gè)異常日志隊(duì)列服務(wù)

三、使用Redis重構(gòu)異常日志隊(duì)列

(1)第一步,開啟Redis的服務(wù),這里我們使用命令開啟Redis服務(wù)(之前已經(jīng)將Redis注冊到了Windows系統(tǒng)服務(wù)中了嘛,么么嗒):net start redis-instance,當(dāng)然,也可以通過在Windows服務(wù)列表中開啟。

(2)第二步,在剛剛的版本1的Demo中新建一個(gè)文件夾,命名為Lib,將ServiceStack.Redis的dll和Log4Net的dll都拷貝進(jìn)去。然后,在引用中添加對Lib文件夾中所有dll的引用。

(3)第三步,重寫MyExceptionFilterAttribute這個(gè)全局異常信息過濾器。這里使用到了Redis的客戶端連接池,每次連接時(shí)都是從池中取,不需要每次都創(chuàng)建,節(jié)省了時(shí)間和資源,提高了資源利用率。對于,多臺Redis服務(wù)器組成的集群而言,這里需要指定多個(gè)形如 IP地址:端口號 的字符串?dāng)?shù)組。

(4)第四步,首先在Web.config中加入Log4Net的詳細(xì)配置。

 View Code

PS:Log4Net是用來記錄日志的一個(gè)常用組件(Log4J的移植版本),可以將程序運(yùn)行過程中的信息輸出到一些地方(文件、數(shù)據(jù)庫、EventLog等)。由于Log4Net不是本篇博文介紹的重點(diǎn),所以對Log4Net不熟悉的朋友,請?jiān)诓┛蛨@首頁搜索:Log4Net,瀏覽其詳細(xì)的介紹。

其次,在App_Start文件夾中添加一個(gè)類,取名為LogConfig,定義一個(gè)靜態(tài)方法:RegisterLog4NetConfigure,具體代碼只有一行,實(shí)現(xiàn)了Log4Net配置的初始化操作。

  1. public class LogConfig 
  2.  { public static void RegisterLog4NetConfigure() 
  3.          
  4.  { //獲取Log4Net配置信息(配置信息定義在Web.config文件中) 
  5.    log4net.Config.XmlConfigurator.Configure(); 
  6.         } 
  7.     } 

最后,在Global.asax中的Application_Start方法中添加一行代碼,注冊Log4Net的配置:

(5)第五步,改寫MessageQueueConfig中的RegisterExceptionLogQueue方法。這里就不再需要從預(yù)置類型Queue中取任務(wù)了,而是Redis中取出任務(wù)出隊(duì)進(jìn)行相應(yīng)處理。這里,我們使用了Log4Net進(jìn)行異常日志的記錄工作。PS:注意在代碼頂部添加對log4net的引用:using log4net;

 (6)最后一步,調(diào)試驗(yàn)證是否能正常寫入App_Data文件的日志中,發(fā)現(xiàn)寫入的異常日志如下,格式好看,信息詳細(xì),圓滿完成了我們的目的。

四、小結(jié)

使用消息隊(duì)列將調(diào)用異步化,可以改善網(wǎng)站系統(tǒng)的性能:消息隊(duì)列具有很好的削峰作用,即通過異步處理,將短時(shí)間高并發(fā)產(chǎn)生的事務(wù)消息存儲在消息隊(duì)列中,從而削平高峰期的并發(fā)事務(wù)。在電商網(wǎng)站的促銷活動(dòng)中,合理使用消息隊(duì)列,可以有效地抵御促銷活動(dòng)剛開始大量涌入的訂單對系統(tǒng)造成的沖擊。本文使用消息隊(duì)列的思想,借助Redis+Log4Net完成了一個(gè)超簡單的異常日志隊(duì)列的應(yīng)用案例,可以有效地解決在多線程操作中對日志文件的并發(fā)操作帶來的一些問題。同樣地,借助消息隊(duì)列的思想,我們也可以完成對數(shù)據(jù)庫的高并發(fā)的消息隊(duì)列方案。

責(zé)任編輯:武曉燕 來源: PHP開源社區(qū)
相關(guān)推薦

2024-03-22 12:10:39

Redis消息隊(duì)列數(shù)據(jù)庫

2018-08-15 09:48:27

數(shù)據(jù)庫Redis應(yīng)用場景

2024-03-29 08:33:10

應(yīng)用場景存儲搜索

2022-01-15 07:20:18

Redis List 消息隊(duì)列

2022-01-21 19:22:45

RedisList命令

2010-04-13 17:00:43

Unix消息隊(duì)列

2024-05-29 14:34:07

2022-04-12 11:15:31

Redis消息隊(duì)列數(shù)據(jù)庫

2010-04-21 12:12:56

Unix 消息隊(duì)列

2022-05-31 08:21:07

MQ使用場景消費(fèi)消息

2025-04-24 10:40:46

CatalogFlink SQL元數(shù)據(jù)

2010-04-21 14:49:13

Unix消息隊(duì)列

2018-04-26 15:18:49

RTOS應(yīng)用MPU

2024-10-25 08:41:18

消息隊(duì)列RedisList

2023-12-30 13:47:48

Redis消息隊(duì)列機(jī)制

2024-04-19 08:32:07

Redis緩存數(shù)據(jù)庫

2021-03-11 06:01:41

Linux消息隊(duì)列

2021-04-30 08:39:10

架構(gòu)消息隊(duì)列高并發(fā)

2017-10-11 15:08:28

消息隊(duì)列常見

2009-06-25 15:33:13

Java消息服務(wù)JMS
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號