淺談工作中celery與Redis遇到的一些問題


celery的內存泄漏?  

  總結:   celery執行完任務不釋放內存與原worker一直沒有被銷毀有關,因此CELERYD_MAX_TASKS_PER_CHILD可以適當配置小點,而任務並發數與CELERYD_CONCURRENCY配置項有關,   

  每增加一個worker必然增加內存消耗,同時也影響到一個worker何時被銷毀,因為celery是均勻調度任務至每個worker,因此也不宜配置過大,適當配置。  

     CELERYD_MAX_TASKS_PER_CHILD 

      CELERYD_CONCURRENCY = 20  # 並發worker數 

      CELERYD_FORCE_EXECV = True    # 非常重要,有些情況下可以防止死鎖  

      CELERYD_MAX_TASKS_PER_CHILD = 100    # 每個worker最多執行萬100個任務就會被銷毀,可防止內存泄露  

      CELERYD_TASK_TIME_LIMIT = 60    # 單個任務的運行時間不超過此值,否則會被SIGKILL 信號殺死   

    任務發出后,經過一段時間還未收到acknowledge , 就將任務重新交給其他worker執行 

      CELERY_DISABLE_RATE_LIMITS = True 

 

 在Django中使用celery內存泄漏問題?   

    在django下使用celery作為異步任務系統,十分方便。

    同時celery也提供定時任務機制,celery beat。使用celery beat 可以為我們提供 cron,schedule 形式的定時任務。

    在django下使用celery beat的過程中,發現了 celery beat進程 占用內存非常大,而且一直不釋放。

    懷疑其有內存占用不釋放的可能。

    因為之前使用django的時候,就知道在django中開啟DEBUG模式,會為每次的SQL查詢 緩存結果。   celery beat 作為 定時任務的timer和heartbeat程序,是長期運行的,而我使用了MYSQL作為存儲定時任務的backend。

    因為每次heartbeat和timer產生的sql查詢在開啟了DEBUG模式下的django環境中,都會緩存查詢結果集。因此 celery beat占用的 內存會一直不釋放。在我的線上環境中 達到10G內存占用!

       解決: 關掉django的DEBUG模式,在setting中,設置DEBUG=False 即可。   關閉DEBUG模式后的celery beat程序 的內存占用大概 一直維持在150M左右。

       

 

數據庫連接是單利嗎? 有必要實現多例嗎?

     單例數據庫連接在連接池中只有一個實例,對系統資源的開銷比較少,甚至可以長時間保持連接不回收,以節省創建連接和回收連接的時間。  

   但這樣的連接在多用戶並發時不能提供足夠的效率,形象的來講就是大家要排隊。  初級程序員的作法是每個用戶需求過來都打開一次連接,用完回收掉。  

   進階的做法是建立一個連接池,連接池里面給定一些已打開的連接,用程序控制這些連接的分配與調度  

      數據庫鏈接用單例模式的原因:

        單例只保留一個對象,可以減少系統資源開銷。

   提高創建速度,每次都獲取已經存在的對象因此提高創建速度全局共享對象。

   單例在系統中只存在一個對象實例,因此任何地方使用此對象都是一個對象避免多實例創建使用時產生的邏輯錯誤。

   例模式是一種常用的軟件設計模式,它的核心結構只包含一個被稱為單例的特殊類。它的目的是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。

   單例模式有3種實現方式:懶漢式、餓漢式和雙重鎖的形式。

 

  單例模式優點 只允許創建一個對象,因此節省內存,加快對象訪問速度,因此對象需要被公用的場合適合使用,如多個模塊使用同一個數據源連接對象等等  因為類控制了實例化過程,所以類可以靈活更改實例化過程   

  單例模式會阻止其他對象實例化其自己的單例對象的副本,從而確保所有對象都訪問唯一實例。  單例的缺點  就是不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,

  單例就會引起數據的錯誤,不能保存彼此的狀態。 用單例模式,就是在適用其優點的狀態下使用。    

    比如:   要進一個房間(數據庫),就為這個房間開了一扇門(數據庫類),一般情況下是一個人開一扇門,   不管你進出(數據庫操作)這個房間多少次,門就這一扇(單例),

    當然一個人也可以開很多扇門(非單例),   但你知道一個房間能開的門的數量是有限的,因此你不使用單例的話,一是性能慢一些,二是走別人的門,讓別人無門可進。。。

 

 

數據類型的堆棧存儲?  

   堆棧是一個后進先出的數據結構,其工作方式就像一堆汽車排隊進去一個死胡同里面,最先進去的一定是最后出來。  

   隊列是一種先進先出的數據類型,它的跟蹤原理類似於在超市收銀處排隊,隊列里的的第一個人首先接受服務,新的元素通過入隊的方式添加到隊列的末尾,而出隊就是將隊列的頭元素刪除。  

   棧:是一種容器,可存入數據元素、訪問元素、刪除元素  

   特點:只能從頂部插入(入棧)數據和刪除(出棧)數據  

   原理:LIFO(Last In First Out)后進先出  棧可以使用順序表實現也可使用鏈表實現 使用python列表實現代碼:  class Stack(object):   """   棧   使用python列表實現   """

      def __init__(self):    self.items = list()

      def is_empty(self):    """判空"""    return self.items == []

      def size(self):    """獲取棧元素個數"""    return len(self.items)

      def push(self, item):    """入棧"""    self.items.append(item)

      def pop(self):    """出棧"""    self.items.pop()

      def peek(self):    """獲取棧頂元素"""    if self.is_empty():     raise IndexError("stack is empty")    return self.items[-1]      

  

 

flask的jwt?  

  一篇文章需求分析:(Flask + flask-jwt 實現基於Json Web Token的用戶認證授權)

   jwt是flask的一個第三方庫:flask-jwt-------->可以實現基於Json Web Token的用戶認證授權

   使用 JWT 讓你的 RESTful API 更安全  什么是 JWT ?   

    JWT 及時 JSON Web Token,它是基於 RFC 7519 所定義的一種在各個系統中傳遞緊湊和自包含的 JSON 數據形式。   

    緊湊(Compact) :由於傳送的數據小,JWT 可以通過GET、POST 和 放在 HTTP 的 header 中,同時也是因為小也能傳送的更快。   

    自包含(self-contained) : Payload 中能夠包含用戶的信息,避免數據庫的查詢。  JSON Web Token 由三部分組成使用 .

    分割開:   Header   Payload   Signature     一個 JWT 形式上類似於下面的樣子:   xxxxx.yyyy.zzzz     

    Header 一般由兩個部分組成:   alg   typ     alg 是是所使用的 hash 算法例如 HMAC SHA256 或 RSA,typ 是 Token 的類型自然就是 JWT。   {     "alg": "HS256",     "typ": "JWT"   }         

  JSON Web Token 的工作流程: 

     在用戶使用證書或者賬號密碼登入的時候一個 JSON Web Token 將會返回,同時可以把這個 JWT 存儲在local storage、或者 cookie 中,用來替代傳統的在服務器端創建一個 session 返回一個 cookie。  

     當用戶想要使用受保護的路由時候,應該要在請求得時候帶上 JWT ,一般的是在 header 的 Authorization 使用 Bearer 的形式,一個包含的 JWT 的請求頭的 Authorization 如下:

         Authorization: Bearer <token>  

       這是一中無狀態的認證機制,用戶的狀態從來不會存在服務端,在訪問受保護的路由時候回校驗 HTTP header 中 Authorization 的 JWT,

      同時 JWT 是會帶上一些必要的信息,不需要多次的查詢數據庫。

      這種無狀態的操作可以充分的使用數據的 APIs,甚至是在下游服務上使用,這些 APIs 和哪服務器沒有關系,

      因此,由於沒有 cookie 的存在,所以在不存在跨域(CORS, Cross-Origin Resource Sharing)的問題。

 

 

gil鎖的局限性和打破方式?

    局限性:   

      在Cpython解釋器中,同一個進程下開啟的多線程,同一時刻只能有一個線程執行,無法利用多核優勢   

      GIL存在原因   CPython在執行多線程的時候並不是線程安全的,所以為了程序的穩定性,加一把全局解釋鎖,能夠確保任何時候都只有一個Python線程執行。   

      GIL的弊端

           GIL對計算密集型的程序會產生影響。因為計算密集型的程序,需要占用系統資源。GIL的存在,相當於始終在進行單線程運算,這樣自然就慢了。

        IO密集型影響不大的原因在於,IO,input/output,這兩個詞就表明程序的瓶頸在於輸入所耗費的時間,線程大部分時間在等待,所以它們是多個一起等(多線程)還是單個等(單線程)無所謂的。  

         這就好比,你在公交站等公交時,你們排隊等公交(單線程)還是沿着馬路一字排開等(多線程)是無所謂的。公交車(即input,即輸入的資源)沒來,哪種方式都是瞎折騰。

    解決方案(打破方式)   

      multiprocessing   

      multiprocessing是一個多進程模塊,開多個進程,每個進程都帶一個GIL,就相當於多線程來用了。   

      multiprocessing的弊端   多線程與多進程一個不同點在於:

           多線程是共享內存的,即這些線程共用一個內存地址。好處在於便於線程間數據通信和數據同步。   

        多進程,各個進程地址之間是獨立的內存地址。這樣不存內存地址之間通信就麻煩了。  

        綜上所述,如果是IO密集型且對數據通信有需求,使用python 的threading模塊也是可以的。              

    解決方法:   1. 使用多進程執行,此將要面臨解決共享數據的問題,多用queue或pipe解決;   2. 使用Python多線程load C的module執行。

          from ctypes import *   form threading import Thread

          #加載動態庫   lib = cdll.LoadLibrary("./libdeadloop.so")

          #創建一個子線程,讓其執行c語言編寫的函數,此函數是一個死循環   t = Thread(target=lib.DeadLoop)   t.start()

          while True:    pass

 網友博客理解:   

  IL是限制同一個進程中只有一個線程進入Python解釋器。。。。。   

  而線程鎖是由於在線程進行數據操作時保證數據操作的安全性(同一個進程中線程之間可以共用信息,如果同時對數據進行操作,則會出現公共數據錯誤)  

  其實線程鎖完全可以替代GIL,但是Python的后續功能模塊都是加在GIL基礎上的,所以無法更改或去掉GIL,這就是Python語言最大的bug…只能用多進程或協程改善,或者直接用其他語言寫這部分   

  追問         

  GIL本質就是一把互斥鎖,既然是互斥鎖,所有互斥鎖的本質都一樣,都是將並發運行變成串行,以此來控制同一時間內共享數據只能被一個任務所修改,進而保證數據安全。   

  保護不同的數據的安全,就應該加不同的鎖。    

  每執行一個python程序,就是開啟一個進程,在一個python的進程內,不僅有其主線程或者由該主線程開啟的其他線程,還有解釋器開啟的垃圾回收等解釋器級別的線程,   所有的線程都運行在這一個進程內,

    所以:    

    1、所有數據都是共享的,這其中,代碼作為一種數據也是被所有線程共享的(test.py的所有代碼以及Cpython解釋器的所有代碼)    

    2、所有線程的任務,都需要將任務的代碼當做參數傳給解釋器的代碼去執行,即所有的線程要想運行自己的任務,首先需要解決的是能夠訪問到解釋器的代碼。       

  在python的原始解釋器CPython中存在着GIL(Global Interpreter Lock,全局解釋器鎖),因此在解釋執行python代碼時,會產生互斥鎖來限制線程對共享資源的訪問,

  直到解釋器遇到I/O操作或者操作次數達到一定數目時才會釋放GIL。   所以,雖然CPython的線程庫直接封裝了系統的原生線程,但CPython整體作為一個進程,同一時間只會有一個獲得GIL的線程在跑,

  其他線程則處於等待狀態。   這就造成了即使在多核CPU中,多線程也只是做着分時切換而已。   不過muiltprocessing的出現,已經可以讓多進程的python代碼編寫簡化到了類似多線程的程度了

 

我對 GIL的理解:  

  解決多線程之間數據完整性和狀態同步的最簡單方法自然就是加鎖。   

  GIL鎖開始運作主線程做操作主線程完成操作GIL鎖釋放資源   所以多線程共同操作共享資源的時候,有一個線程競得了資源,它就被GIL鎖保護起來,其他線程只能是在那里等着,

  但是這個時候,線程的休眠喚醒,全部會消耗CPU資源,所以嘞,就會慢。   Python語言和GIL解釋器鎖沒有關系,它是在實現Python解析器(CPython)時所引入的一個概念,

  同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行,然而因為CPython是大部分環境下默認的Python執行環境。所以在很多人的概念里CPython就是Python,

  也就想當然的把GIL歸結為Python語言的缺陷,所有GIL並不是python的特性,僅僅是因為歷史原因在Cpython解釋器中難以移除。

     GIL保證同一時刻只有一個線程執行代碼,每個線程在執行過程中都要先獲取GIL

 

    線程釋放GIL鎖的情況:

     在IO操作等可能會引起阻塞的system call之前,可以暫時釋放GIL,但在執行完畢后,必須重新獲取GIL Python 3.x使用計時器(執行時間達到閾值后,當前線程釋放GIL)或Python 2.x,tickets計數達到100

     Python使用多進程是可以利用多核的CPU資源的。   多線程爬取比單線程性能有提升,因為遇到IO阻塞會自動釋放GIL鎖

     GIL只對計算密集型的程序有作用,對IO密集型的程序並沒有影響,因為遇到IO阻塞會自動釋放GIL鎖   當需要執行計算密集型的程序時,

     可以選擇:1.換解釋器,2.擴展C語言,3.換多進程等方案

 

    GIL(Global Interpreter Lock):全局解釋器鎖,python解釋器在執行python字節碼的時候會鎖住解釋器,導致其它的線程不能使用解釋器,從而多線程情況下CPU上不去。  

    lupa是一個python調用lua的第三方庫(https://pypi.python.org/pypi/lupa),lua_code是一段純CPU計算的lua代碼片段,之后開啟了3個線程,可以發現CPU利用率達到了300%  之前在寫一些python程序的時候,

    如果是cpu密集的常常會使用多進程的方式,但是這樣會有一些缺點:   

      1. 進程間共享數據特別麻煩,雖然multiprocessing庫提供了很多進程間共享數據的方法,但是這些方法最后自己會成為瓶頸

         2. 編程復雜度比較高

         3. 主進程和子進程必然需要通信,進程間數據隔離,所以數據需要內存拷貝,成本高  相應的python代碼:

          #python lupa load

          import lupa   lua = lupa.LuaRuntime()

          LIBS = [    "./scripts/foo.lua",   ]  

          llibs = {}

         def get_file_name(filename):    

          import os    

          (_, tmp) = os.path.split(filename)    

          (f_name, ext) = os.path.splittext(tmp)     

          return f_name       

         def load_libs():   

          global LIBS, llibs    for lib_p in LIBS:     f = open(lib_p, 'r')     

          code_str = f.readlines()     filename = get_file_name(lib_p)     

          llibs[filename] = lua.execute('\n'.join(code_str))        

        if __name__ == '__main__':   

           load_libs()    

          print llibs['foo'].sayhi()    

          print llibs['foo'].callback(100, 200, 300, 400)     

          --foo.lualibfoo = {}

             function libfoo.sayhi()    

          return "hi from lupa"   end  function libfoo.callback(a, b, c, d)    return a * b + c - d   endreturn libfoo  

  這樣做有幾點好處:

     1. python寫框架,lua寫回調,每次調用一遍load_libs就相當於一次熱更新

     2. lua代碼本身特別簡單,可以交給策划配置,與熱更新結合效果更好

     3. python多線程結合lua使用可以突破python GIL的限制,后面補充一個demo    

        線程安全:   線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。   不會出現數據不一致或者數據污染。

        線程不安全:就是不提供數據訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據。

 

      

celery在某一時刻突然執行2回? 為什么? 否 怎么解決?  

    Celery是一個用Python開發的異步的分布式任務調度模塊    

  網友遇到的工作問題以及解決方案:   

    使用 Celery Once 來防止 Celery 重復執行同一個任務   在使用 Celery 的時候發現有的時候 Celery 會將同一個任務執行兩遍,我遇到的情況是相同的任務在不同的 worker 中被分別執行,並且時間只相差幾毫秒。

    這問題我一直以為是自己哪里處理的邏輯有問題,后來發現其他人 也有類似的問題,然后基本上出問題的都是使用 Redis 作為 Broker 的,而我這邊一方面不想將 Redis 替換掉,就只能在 task 執行的時候加分布式鎖了。

      不過在 Celery 的 issue 中搜索了一下,有人使用 Redis 實現了分布式鎖,然后也有人使用了 Celery Once。 大致看了一下 Celery Once ,發現非常符合現在的情況,就用了下。

    Celery Once 也是利用 Redis 加鎖來實現, Celery Once 在 Task 類基礎上實現了 QueueOnce 類,該類提供了任務去重的功能,

    所以在使用時,我們自己實現的方法需要將 QueueOnce 設置為 base   @task(base=QueueOnce, once={'graceful': True})   

    后面的 once 參數表示,在遇到重復方法時的處理方式,默認 graceful 為 False,那樣 Celery 會拋出 AlreadyQueued 異常,手動設置為 True,則靜默處理。

       另外如果要手動設置任務的 key,可以指定 keys 參數

       @celery.task(base=QueueOnce, once={'keys': ['a']})   def slow_add(a, b):    sleep(30)    return a + b

 

 

celery 任務突然不執行是為什么?  

  問題:   

    別人推送很多消息給我,用 tornado 接收然后傳到 celery 里面處理   celery 進程剛啟動還是沒問題,運行一天半天 突然里面的任務都不處理了   重新啟動下 就能把之前接收到的推送 一個個繼續處理。。。   

    看不出是什么問題。。   打算修改下配置的處理任務的超時時間,看看能不能解決這個問題。  

  目前解決方案:   

    在服務器加一個定時刷新,開啟時間長,處理大量消息,出現任務超時,端口占用,任務沒有放掉,導致后期任務無法執行,需要服務器定時任務重啟  

  問題:   

    最近在寫一個分布式微博爬蟲,主要就是使用celery做的分布式任務調度。celery確實比較好用,但是也遇到一些問題,我遇到的問題主要集中在定時任務和任務路由這兩個部分。   

    本文不會講解celery的基本使用,如果需要看celery入門教程的話,請點擊這里跳轉。   celery worker -A app_name -l info必須推薦在項目的根目錄運行而且,這里的app_name必須是項目中的Celery實例的完整引用路徑*。

    如果不在項目根目錄運行,那么相關的調用也得切換到app同級目錄下,這一點可以通過命令行進行佐證      

    celery的定時任務會有一定時間的延遲。比如,我規定模擬登陸新浪微博任務每隔10個小時執行一次,那么定時任務第一次執行就會在開啟定時任務之后的10個小時后才會執行。   

    而我抓取微博需要馬上執行,需要帶上cookie,所以不能等那1個小時。這個沒有一個比較好的解決方法,可以使用celery的crontab()來代替schdule做定時,它會在啟動的時候就執行。   

  解決方案

    我采用的方法是第一次手動執行該任務,然后再通過schedule執行。      celery的定時任務可能會讓任務重復。定時器一定只能在一個節點啟動,否則會造成任務重復。   

    另外,如果當前worker節點都停止了,而beat在之后才停止,那么下一次啟動worker的時候,它還會執行上一次未完成的任務,可能會有重復。   

    由於抓取用戶和抓取用戶關注、粉絲的任務耗時和工作量不同,所以需要使用任務路由,將任務按比重合理分配到各個分布式節點上,這就需要使用到celery提供的task queue。   

    如果單獨使用task queue還好,但是和定時任務一起使用,就可能出現問題。我遇到的問題就是定時任務壓根就不執行!開始我的配置大概就是這樣    

      app.conf.update(     

        CELERY_TIMEZONE='Asia/Shanghai',     

        CELERY_ENABLE_UTC=True,     

        CELERY_ACCEPT_CONTENT=['json'],     

        CELERY_TASK_SERIALIZER='json',      

        CELERY_RESULT_SERIALIZER='json',     

        CELERYBEAT_SCHEDULE={      

             'user_task': {       'task': 'tasks.user.excute_user_task',       'schedule': timedelta(minutes=3),      },     

             'login_task': {       'task': 'tasks.login.excute_login_task',       'schedule': timedelta(hours=10),      },    

             },     

        CELERY_QUEUES=(     

             Queue('login_queue', exchange=Exchange('login', type='direct'), routing_key='for_login'),     

             Queue('user_crawler', exchange=Exchange('user_info', type='direct'), routing_key='for_user_info'),      

             Queue('fans_followers', exchange=Exchange('fans_followers', type='direct'), routing_key='for_fans_followers')     )    )

 

    結果過了一天發現定時任務並沒有執行,后來把task加上了一個option字段,指定了任務隊列,就可以了,

    比如     'user_task': {      'task': 'tasks.user.excute_user_task',      'schedule': timedelta(minutes=3),      'options': {'queue': 'fans_followers', 'routing_key': 'for_fans_follwers'}     },

      部分分布式節點一直出現Received task,但是卻不執行其中的任務的情況。這種情況下重啟worker節點一般就可以恢復。   

    但是最好查查原因。通過查看flower的失敗任務信息,才發現是插入數據的時候有的異常未被處理。這一點嚴格說來並不是celery的bug,不過也很令人費解。   

    所以推薦在使用celery的時候配合使用flower做監控。

 

 

 

Redis是否保證事務的一致性?  

    網友遇到問題:   

      ”redis設計之初是簡單高效,所以說在事務操作時命令是不會出錯的,出錯的可能性就是程序的問題“   那這樣的意思就是把鍋拋給程序咯?    

      如果程序能保證百分之百不出錯那么關系型數據庫還要啥事務呢?

      redis事務報錯時仍會執行所有命令,這樣怎么保證一致性呢?    

      或者說白了redis根本就不支持事務只是冠以事務的名號而已。以上純屬個人見解   又專業人士可以解釋大家討論。  

    網友理解:   

      1、Redis事務主要用於不間斷執行多條命令,即是存在引發錯誤的命令。Redis先執行命令,命令執行成功后才會記錄日志,所以出現錯誤時無法回滾。     

        支持完整的acid會讓Redis變得復雜也可能導致性能較低。 此外,使用lua腳本也可以保證Redis不間斷執行多條命令。   

      2、找到網上這個解釋比較到位:      單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增加任何維持原子性的機制,所以 Redis 事務的執行並不是原子性的。     

        事務可以理解為一個打包的批量執行腳本,但批量指令並非原子化的操作,中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成后續的指令不做。    

 

 

 redis事務

      1、基本概念

        1)什么是redis的事務?

          簡單理解,可以認為redis事務是一些列redis命令的集合,並且有如下兩個特點:

          a)事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。

          b)事務是一個原子操作:事務中的命令要么全部被執行,要么全部都不執行。

  

        2)事務的性質ACID

          一般來說,事務有四個性質稱為ACID,分別是原子性,一致性,隔離性和持久性。

          a)原子性atomicity:redis事務保證事務中的命令要么全部執行要不全部不執行。有些文章認為redis事務對於執行錯誤不回滾違背了原子性,是偏頗的。

          b)一致性consistency:redis事務可以保證命令失敗的情況下得以回滾,數據能恢復到沒有執行之前的樣子,是保證一致性的,除非redis進程意外終結。

          c)隔離性Isolation:redis事務是嚴格遵守隔離性的,原因是redis是單進程單線程模式,可以保證命令執行過程中不會被其他客戶端命令打斷。

          d)持久性Durability:redis事務是不保證持久性的,這是因為redis持久化策略中不管是RDB還是AOF都是異步執行的,不保證持久性是出於對性能的考慮。

 

        3)redis事務的錯誤

          使用事務時可能會遇上以下兩種錯誤:

            a)入隊錯誤:事務在執行 EXEC 之前,入隊的命令可能會出錯。比如說,命令可能會產生語法錯誤(參數數量錯誤,參數名錯誤,等等),

              或者其他更嚴重的錯誤,比如內存不足(如果服務器使用 maxmemory 設置了最大內存限制的話)。

            b)執行錯誤:命令可能在 EXEC 調用之后失敗。舉個例子,事務中的命令可能處理了錯誤類型的鍵,比如將列表命令用在了字符串鍵上面,諸如此類。

            注:第三種錯誤,redis進程終結,本文並沒有討論這種錯誤。

  

     2、redis事務的用法

         redis事務是通過MULTI,EXEC,DISCARD和WATCH四個原語實現的。

         MULTI命令用於開啟一個事務,它總是返回OK。

         MULTI執行之后,客戶端可以繼續向服務器發送任意多條命令,這些命令不會立即被執行,而是被放到一個隊列中,當EXEC命令被調用時,所有隊列中的命令才會被執行。

         另一方面,通過調用DISCARD,客戶端可以清空事務隊列,並放棄執行事務。

 

 

 

 

Redis的持久化方式?  

    兩種持久化方式:

      RDB和AOF  深入了解RDB和AOF的作用原理,剩下的就是根據實際情況來制定合適的策略了,再復雜一點,也就是定制一個高可用的,數據安全的策略了。

   

      在RDB方式下,你有兩種選擇,一種是手動執行持久化數據命令來讓redis進行一次數據快照,另一種則是根據你所配置的配置文件 的 策略,達到策略的某些條件時來自動持久化數據。  

      而手動執行持久化命令,你依然有兩種選擇,那就是save命令和bgsave命令。   save操作在Redis主線程中工作,因此會阻塞其他請求操作,應該避免使用。   

      bgSave則是調用Fork,產生子進程,父進程繼續處理請求。子進程將數據寫入臨時文件,並在寫完后,替換原有的.rdb文件。

      Fork發生時,父子進程內存共享,所以為了不影響子進程做數據快照,在這期間修改的數據,將會被復制一份,而不進共享內存。  

      所以說,RDB所持久化的數據,是Fork發生時的數據。在這樣的條件下進行持久化數據,如果因為某些情況宕機,則會丟失一段時間的數據。

      如果你的實際情況對數據丟失沒那么敏感,丟失的也可以從傳統數據庫中獲取或者說丟失部分也無所謂,那么你可以選擇RDB持久化方式。  

      再談一下配置文件的策略,實際上它和bgsave命令持久化原理是相同的。    

 

      AOF持久化方式:

      配置文件中的appendonly修改為yes。開啟AOF持久化后,你所執行的每一條指令,都會被記錄到appendonly.aof文件中。  

      但事實上,並不會立即將命令寫入到硬盤文件中,而是寫入到硬盤緩存,在接下來的策略中,配置多久來從硬盤緩存寫入到硬盤文件。

      所以在一定程度一定條件下,還是會有數據丟失,不過你可以大大減少數據損失。  這里是配置AOF持久化的策略。

      redis默認使用everysec,就是說每秒持久化一次,而always則是每次操作都會立即寫入aof文件中。  

      而no則是不主動進行同步操作,是默認30s一次。當然always一定是效率最低的,個人認為everysec就夠用了,數據安全性能又高。

 

 

   Redis也允許我們同時使用兩種方式,再重啟redis后會從aof中恢復數據,因為aof比rdb數據損失小嘛。    

      深入理解Redis的兩種持久化方式:  RDB每次進行快照方式會重新記錄整個數據集的所有信息。RDB在恢復數據時更快,可以最大化redis性能,子進程對父進程無任何性能影響。

      AOF有序的記錄了redis的命令操作。意外情況下數據丟失甚少。他不斷地對aof文件添加操作日志記錄,你可能會說,這樣的文件得多么龐大呀。

      是的,的確會變得龐大,但redis會有優化的策略,比如你對一個key1鍵的操作,set key1 001 ,  set key1 002, set key1 003。那優化的結果就是將前兩條去掉咯,

      那具體優化的配置在配置文件中對應的是  https://images2015.cnblogs.com/blog/686162/201608/686162-20160809211516715-145676984.png   

      前者是指超過上一次aof重寫aof文件大小的百分之多少,會再次優化,如果沒有重寫過,則以啟動時為主。

      后者是限制了允許重寫的最小aof文件大小。bgrewriteaof命令是手動重寫命令,會fork子進程,在臨時文件中重建數據庫狀態,對原aof無任何影響,

      當重建舊的狀態后,也會把fork發生后的一段時間內的數據一並追加到臨時文件,最后替換原有aof文件,新的命令繼續向新的aof文件中追加。

 

  Redis數據庫簡介:    

      Redis是一種高級key-value數據庫。它跟memcached類似,不過數據可以持久化,而且支持的數據類型很豐富。

      有字符串,鏈表,集 合和有序集合。支持在服務器端計算集合的並,交和補集(difference)等,還支持多種排序功能。

      所以Redis也可以被看成是一個數據結構服務 器。   Redis的所有數據都是保存在內存中,然后不定期的通過異步方式保存到磁盤上(這稱為“半持久化模式”);

      也可以把每一次數據變化都寫入到一個append only file(aof)里面(這稱為“全持久化模式”)。   

  Redis數據庫簡介:   

      默認使用everysec,就是說每秒持久化一次,而always則是每次操作都會立即寫入aof文件中。

      默認使用everysec,就是說每秒持久化一次,而always則是每次操作都會立即寫入aof文件中。

      由於Redis的數據都存放在內存中,如果沒有配置持久化,redis重啟后數據就全丟失了,於是需要開啟redis的持久化功能,將數據保存到磁盤上,

      當redis重啟后,可以從磁盤中恢復數據。redis提供兩種方式進行持久化,一種是RDB持久化(原理是將Reids在內存中的數據庫記錄定時dump到磁盤上的RDB持久化),

      另外一種是AOF(append only file)持久化(原理是將Reids的操作日志以追加的方式寫入文件)。

      那么這兩種持久化方式有什么區別呢,改如何選擇呢?網上看了大多數都是介紹這兩種方式怎么配置,怎么使用,就是沒有介紹二者的區別,在什么應用場景下使用。

 

 

二者的區別   RDB存在哪些優勢呢?

       1). 一旦采用該方式,那么你的整個Redis數據庫將只包含一個文件,這對於文件備份而言是非常完美的。

        比如,你可能打算每個小時歸檔一次最近24小時的數據,同時還要每天歸檔一次最近30天的數據。

        通過這樣的備份策略,一旦系統出現災難性故障,我們可以非常容易的進行恢復。

       2). 對於災難恢復而言,RDB是非常不錯的選擇。因為我們可以非常輕松的將一個單獨的文件壓縮后再轉移到其它存儲介質上。

       3). 性能最大化。對於Redis的服務進程而言,在開始持久化時,它唯一需要做的只是fork出子進程,之后再由子進程完成這些持久化的工作,這樣就可以極大的避免服務進程執行IO操作了。

       4). 相比於AOF機制,如果數據集很大,RDB的啟動效率會更高。

 

     RDB又存在哪些劣勢呢?

       1). 如果你想保證數據的高可用性,即最大限度的避免數據丟失,那么RDB將不是一個很好的選擇。因為系統一旦在定時持久化之前出現宕機現象,此前沒有來得及寫入磁盤的數據都將丟失。

       2). 由於RDB是通過fork子進程來協助完成數據持久化工作的,因此,如果當數據集較大時,可能會導致整個服務器停止服務幾百毫秒,甚至是1秒鍾。

 

     AOF的優勢有哪些呢?

       1). 該機制可以帶來更高的數據安全性,即數據持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。

        事實上,每秒同步也是異步完成的,其效率也是非常高的,所差的是一旦系統出現宕機現象,那么這一秒鍾之內修改的數據將會丟失。

        而每修改同步,我們可以將其視為同步持久化,即每次發生的數據變化都會被立即記錄到磁盤中。可以預見,這種方式在效率上是最低的。至於無同步,無需多言,我想大家都能正確的理解它。

       2). 由於該機制對日志文件的寫入操作采用的是append模式,因此在寫入過程中即使出現宕機現象,也不會破壞日志文件中已經存在的內容。

        然而如果我們本次操作只是寫入了一半數據就出現了系統崩潰問題,不用擔心,在Redis下一次啟動之前,我們可以通過redis-check-aof工具來幫助我們解決數據一致性的問題。

       3). 如果日志過大,Redis可以自動啟用rewrite機制。即Redis以append模式不斷的將修改數據寫入到老的磁盤文件中,同時Redis還會創建一個新的文件用於記錄此期間有哪些修改命令被執行。

        因此在進行rewrite切換時可以更好的保證數據安全性。

       4). AOF包含一個格式清晰、易於理解的日志文件用於記錄所有的修改操作。事實上,我們也可以通過該文件完成數據的重建。

 

     AOF的劣勢有哪些呢?

       1). 對於相同數量的數據集而言,AOF文件通常要大於RDB文件。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。

       2). 根據同步策略的不同,AOF在運行效率上往往會慢於RDB。總之,每秒同步策略的效率是比較高的,同步禁用策略的效率和RDB一樣高效。

       二者選擇的標准,就是看系統是願意犧牲一些性能,換取更高的緩存一致性(aof),還是願意寫操作頻繁的時候,不啟用備份來換取更高的性能,待手動運行save的時候,再做備份(rdb)。

        rdb這個就更有些 eventually consistent的意思了。

 

 

 

常用配置

     RDB持久化配置

     Redis會將數據集的快照dump到dump.rdb文件中。此外,我們也可以通過配置文件來修改Redis服務器dump快照的頻率,在打開6379.conf文件之后,我們搜索save,可以看到下面的配置信息:

       save 900 1              #在900秒(15分鍾)之后,如果至少有1個key發生變化,則dump內存快照。

       save 300 10            #在300秒(5分鍾)之后,如果至少有10個key發生變化,則dump內存快照。

       save 60 10000        #在60秒(1分鍾)之后,如果至少有10000個key發生變化,則dump內存快照。

 

     AOF持久化配置

     在Redis的配置文件中存在三種同步方式,它們分別是:

       appendfsync always     #每次有數據修改發生時都會寫入AOF文件。

       appendfsync everysec  #每秒鍾同步一次,該策略為AOF的缺省策略。

       appendfsync no          #從不同步。高效但是數據不會被持久化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM