Python異常處理

Python中的異常處理

異常分類

  程序中難免出現錯誤,總共可分為兩種。

 

  1.邏輯錯誤

  2.語法錯誤

 

  對於剛接觸編程的人來說,這兩個錯誤都會經常去犯,但是隨着經驗慢慢的積累,語法錯誤的情況會越來越少反而邏輯錯誤的情況會越來越多(因為工程量巨大)。不論多麼老道的程序員都不可避免出現這兩種錯誤。

 

異常的三大信息

  異常其實就是程序運行時發生錯誤的信號,我們寫代碼的過程中不可避免也最害怕的就是出現異常,然而當程序拋出異常時實際上會分為三部分,即三大信息。

 

 

常用的異常類

  在Python中一切皆對象,異常本身也是由一個類生成的,NameError其實本身就是一個異常類,其他諸如此類的異常類還有很多。

Python中常見的異常類 
AttributeError 試圖訪問一個對象沒有的屬性,比如foo.x,但是foo並沒有屬性x
IOError 輸入/輸出異常;基本上是無法打開文件
ImportError 無法引入模塊或包;基本上是路徑問題或名稱錯誤
IndentationError 語法錯誤(的子類) ;代碼沒有正確對齊
IndexError 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5]
KeyError 試圖訪問字典里不存在的鍵
KeyboardInterrupt Ctrl+C被按下
NameError 試圖使用一個還未被賦予對象的變量
SyntaxError Python代碼非法,代碼不能編譯(其實就是語法錯誤,寫錯了)
TypeError 傳入對象類型與要求的不符合
UnboundLocalError 試圖訪問一個還未被設置的局部變量,基本上是由於另有一個同名的 全局變量,導致你以為正在訪問它
ValueError 傳入一個調用者不期望的值,即使值的類型是正確的

 

異常處理

  我們可以來用某些方法進行異常捕捉,當出現異常時我們希望代碼以另一種邏輯運行,使得我們的程序更加健壯,這個就叫做異常處理。異常處理是非常重要的,本身也並不複雜,千萬不可馬虎大意。

  但是切記不可濫用異常處理,這會使得你的代碼可讀性變差。

 

if else處理異常

  ifelse本身就具有處理異常的功能,他們更多的是在我們能預測到可能出現的範圍內進行規避異常,對於我們不能預測的異常來說就顯得不是那麼的好用。如下:

# ==== if else 處理異常 ====

while 1:
    select = input("請輸入数字0進行關機:").strip()
    if select.isdigit():  # 我們可以防止用戶輸入非数字的字符
        if select == "0":
            print("正在關機...")
            break
    print("輸入有誤,請重新輸入")

# ==== 執行結果 ====

"""
請輸入数字0進行關機:關機
輸入有誤,請重新輸入
請輸入数字0進行關機:關機啊
輸入有誤,請重新輸入
請輸入数字0進行關機:0
正在關機...
"""

  這種異常處理機制雖然非常簡單,但是並不靈活,我們可以使用更加簡單的方式來處理他們。

 

try except處理異常

 

  try:代表要檢測可能出現異常的代碼塊

  except:當異常出現后的處理情況

 

  執行流程:

    try中檢測的代碼塊 —> 如果有異常 —> 執行except代碼塊 —> 執行正常邏輯代碼 —> 程序結束

    try中檢測的代碼塊 —> 如果沒有異常 —> 執行完畢try中的代碼塊 —> 執行正常邏輯代碼 —> 程序結束

 

# ====  try except 執行流程 有異常的情況 ====

li = [1,2,3]

try:
    print("開始執行我try了...")
    print(li[10])  # 出錯點...
    print("繼續執行我try...")
except IndexError as e:
    print("有異常執行我except...")

print("正常邏輯代碼...")

# ==== 執行結果 ====

"""
開始執行我try了...
有異常執行我except...
正常邏輯代碼...
""" 
# ====  try except 執行流程 無異常的情況 ====

li = [1,2,3]

try:
    print("開始執行我try了...")
    print(li[2])
    print("繼續執行我try...")
except IndexError as e:
    print("有異常執行我except...")

print("正常邏輯代碼...")

# ==== 執行結果 ====

"""
開始執行我try了...
3
繼續執行我try...
正常邏輯代碼...
""" 
# ==== try except 處理異常 ====

while 1:
    try:  # try檢測可能出錯的語句,一旦出錯立馬跳轉到except語句塊執行代碼。
        select = int(input("請輸入数字0進行關機:").strip())
        if select == 0:
            print("正在關機...")
            break
        print("輸入有誤,請重新輸入...")
    except ValueError as e:  # 當執行完except的代碼塊后,程序運行結束,其中e代表的是異常信息。
        print("錯誤信息是:",e)
        print("輸入有誤,請重新輸入")

# ==== 執行結果 ====

"""
請輸入数字0進行關機:1
輸入有誤,請重新輸入...
請輸入数字0進行關機:tt
錯誤信息是: invalid literal for int() with base 10: 'tt'
輸入有誤,請重新輸入
請輸入数字0進行關機:0
正在關機...
"""

 

多段except捕捉多異常

  我們可以使用try和多段except的語法來檢測某一代碼塊,可以更加方便的應對更多類型的錯誤,Ps不常用:

# ==== 多段 except 捕捉多異常 ====

while 1:

    li = [1,2,3,4]
    dic = {"name":"Yunya","age":18}

    li_index = input("請輸入索引:")
    dic_key = input("請輸入鍵的名稱:")

    if li_index.isdigit():
        li_index = int(li_index)

    try:
        print(li[li_index])
        print(dic[dic_key])
    except IndexError as e1:  # 注意,先拋出的錯誤會直接跳到其處理的except代碼塊,而try下面的語句將不會被執行。
        print("索引出錯啦!")
    except KeyError as e2:
        print("鍵出錯啦!")

# ==== 執行結果 ====

"""
請輸入索引:10
請輸入鍵的名稱:gender
索引出錯啦!
請輸入索引:2
請輸入鍵的名稱:gender
3
鍵出錯啦!
"""

 

元組捕捉多異常

  使用多段except捕捉多異常會顯得特別麻煩,這個時候我們可以使用(異常類1,異常類2)來捕捉多異常,但是需要注意的是,對比多段except捕捉多異常來說,這種方式的處理邏輯會顯得較為複雜(因為只有一段處理邏輯),如下:

# ====  元組捕捉多異常 ====

while 1:

    li = [1,2,3,4]
    dic = {"name":"Yunya","age":18}

    li_index = input("請輸入索引:")
    dic_key = input("請輸入鍵的名稱:")

    if li_index.isdigit():
        li_index = int(li_index)

    try:
        print(li[li_index])
        print(dic[dic_key])
    except (IndexError,KeyError) as e:  # 使用()的方式可以同時捕捉很多異常。
        print("出錯啦!")

# ==== 執行結果 ====

"""
請輸入索引:10
請輸入鍵的名稱:gender
出錯啦!
請輸入索引:2
請輸入鍵的名稱:gender
3
出錯啦!
"""

  可以看到,不管是那種錯誤都只有一種應對策略,如果我們想要多種應對策略就只能寫if判斷來判斷異常類型再做處理。所以就會顯得很麻煩,如下:

# ====  元組捕捉多異常 ====

while 1:

    li = [1,2,3,4]
    dic = {"name":"Yunya","age":18}

    li_index = input("請輸入索引:")
    dic_key = input("請輸入鍵的名稱:")

    if li_index.isdigit():
        li_index = int(li_index)

    try:
        print(li[li_index])
        print(dic[dic_key])
    except (IndexError,KeyError) as e:
        # 判斷異常類型再做出相應的對應策略
        if isinstance(e,IndexError):
            print("索引出錯啦!")
        elif isinstance(e,KeyError):
            print("鍵出錯啦!")

# ==== 執行結果 ====

"""
請輸入索引:10
請輸入鍵的名稱:gender
索引出錯啦!
請輸入索引:2
請輸入鍵的名稱:gender
3
鍵出錯啦!
"""

 

萬能異常Exception

  我們可以捕捉Exception類引發的異常,它是所有異常類的基類。(Exception類的父類則是BaseException類,而BaseException的父類則是object類)

# ====  萬能異常Exception ====

while 1:

    li = [1,2,3,4]
    dic = {"name":"Yunya","age":18}

    li_index = input("請輸入索引:")
    dic_key = input("請輸入鍵的名稱:")

    if li_index.isdigit():
        li_index = int(li_index)

    try:
        print(li[li_index])
        print(dic[dic_key])
    except Exception as e:  #使用 Exception來捕捉所有異常。
        # 判斷異常類型再做出相應的對應策略
        if isinstance(e,IndexError):
            print("索引出錯啦!")
        elif isinstance(e,KeyError):
            print("鍵出錯啦!")

# ==== 執行結果 ====

"""
請輸入索引:10
請輸入鍵的名稱:gender
索引出錯啦!
請輸入索引:2
請輸入鍵的名稱:gender
3
鍵出錯啦!
"""

 

 

try except else聯用

  這種玩法比較少,else代表沒有異常發生的情況下執行的代碼,執行順序如下:

 

  try中檢測的代碼塊 —> 如果有異常 —> 終止try中的代碼塊繼續執行 —> 執行except代碼塊 —> 執行正常邏輯代碼 —> 程序結束

  try中檢測的代碼塊 —> 如果沒有異常 —> 執行完畢try中的代碼塊 —> 執行else代碼塊 —> 執行正常邏輯代碼 —> 程序結束

 

# ====  try except else聯用 有異常的情況====

li = [1,2,3]

try:
    print("開始執行我try了...")
    print(li[10])  # 出錯點...
    print("繼續執行我try...")
except IndexError as e:
    print("有異常執行我except...")
else:
    print("沒有異常執行我else...")
print("正常邏輯代碼...")

# ==== 執行結果 ====

"""
開始執行我try了...
有異常執行我except...
正常邏輯代碼...
"""
# ====  try except else聯用 無異常的情況====

li = [1,2,3]

try:
    print("開始執行我try了...")
    print(li[2])
    print("繼續執行我try...")
except IndexError as e:
    print("有異常執行我except...")
else:
    print("沒有異常執行我else...")
print("正常邏輯代碼...")

# ==== 執行結果 ====

"""
開始執行我try了...
3
繼續執行我try...
沒有異常執行我else...
正常邏輯代碼...
"""

 

try except finally聯用

  finally代表不論拋異常與否都會執行,因此常被用作關閉系統資源的操作,關於try,except,else,finally他們的優先級如下:

 

  有異常的情況下:

    try代碼塊

    終止try代碼塊繼續執行

    except代碼塊

    finally代碼塊

    正常邏輯代碼

 

  無異常的情況下:

    try代碼塊

    else代碼塊

    finally代碼塊

    正常邏輯代碼

 

# ====  try except else finally 執行流程 有異常的情況 ====

li = [1,2,3]

try:
    print("開始執行我try了...")
    print(li[10])  # 出錯點...
    print("繼續執行我try...")
except IndexError as e:
    print("有異常執行我except...")
else:
    print("沒有異常執行我else...")
finally:
    print("不管有沒有異常都執行我finally...")

print("正常邏輯代碼...")

# ==== 執行結果 ====

"""
開始執行我try了...
有異常執行我except...
不管有沒有異常都執行我finally...
正常邏輯代碼...
"""
# ====  try except else finally 執行流程 無異常的情況 ====

li = [1,2,3]

try:
    print("開始執行我try了...")
    print(li[2])
    print("繼續執行我try...")
except IndexError as e:
    print("有異常執行我except...")
else:
    print("沒有異常執行我else...")
finally:
    print("不管有沒有異常都執行我finally...")

print("正常邏輯代碼...")

# ==== 執行結果 ====

"""
開始執行我try了...
3
繼續執行我try...
沒有異常執行我else...
不管有沒有異常都執行我finally...
正常邏輯代碼...
"""

 

自定義異常

raise主動拋出異常

  在某些時候我們可能需要主動的去阻止程序的運行,主動的拋出一個異常。可以使用raise來進行操作。這個是一種非常常用的手段。

# ====  raise使用方法  ====

print("----1----")
print("----2----")
print("----3----")
raise Exception("我也不知道是什麼類型的異常...")
print("----4----")
print("----5----")
print("----6----")

# ==== 執行結果 ====

"""
----1----
----2----
----3----
Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/learn/元類編程.py", line 6, in <module>
    raise Exception("我也不知道是什麼類型的異常...")
Exception: 我也不知道是什麼類型的異常...

Process finished with exit code 1
"""

 

自定義異常類

  前面已經說過一切皆對象,異常也來自一個對象。因此我們也可以自己來定製一個對象。注意,自定義異常類必須繼承BaseException類。

# ====  自定義異常類  ====

class MyError(BaseException):
    pass

raise MyError("我的異常")



# ==== 執行結果 ====

"""
Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/learn/元類編程.py", line 6, in <module>
    raise MyError("我的異常")
__main__.MyError: 我的異常
"""

 

擴展:斷言assert

  斷言是一個十分裝逼的使用,假設多個函數進行計算,我們已經有了預期的結果只是在做一個算法的設計。如果函數的最後的結果不是我們本來預期的結果那麼寧願讓他停止運行也不要讓錯誤繼續擴大,在這種情況下就可以使用斷言操作,使用斷言會拋出一個AssertionError類的異常。

# ====  斷言assert  ====

def calculate():
    """假設在做非常複雜的運算"""
    return 3+2*5

res = calculate()

assert res == 25  # AssertionError

print("算法測試通過!你真的太厲害了")

# ==== 執行結果 ====

"""
Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/learn/元類編程.py", line 8, in <module>
    assert res == 25
AssertionError
"""

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

在樹莓派上讀取土壤濕度傳感器讀數-Python代碼實現及常見問題(全面簡單易懂)

本篇文章簡單介紹了如何在樹莓派上配置土壤濕度傳感器以讀取土壤濕度及代碼實現。

主要包含有以下4個模塊:

一、土壤濕度傳感器常見類型及介紹

二、實驗所需設備

三、設備連線方式與Python代碼實現

四、常見問題及注意事項

需要哪個模塊的內容直接跳轉去看即可~

 

一、土壤濕度傳感器常見類型及介紹

 土壤濕度傳感器,又名土壤水分傳感器、土壤墒情傳感器、土壤含水量傳感器等。顧名思義,主要用來測量土壤相對含水量,做土壤墒情監測。在智能農業,農業灌溉和林業防護等領域極廣。該傳感器價格低廉,如果想在家製作一個簡易的智能自動化作物灌溉系統,有了它,再加上溫濕度傳感器、樹莓派/Arduino就可以輕鬆完成。

常見的土壤傳感器分為兩類,電阻型和電容型土壤濕度傳感器。但原理大同小異,都是測量土壤中水分的體積含量,並以電壓表示水分值。

(一)電阻型土壤濕度傳感器

常見的有傳感器型號有YL-69(下圖左)和FC-28(下圖右)。這是一種低技術含量的傳感器這類傳感器由兩部分組成,帶探針的傳感器,A to D(模擬信號轉数字信號)电子板(校準靈敏度主板),兩者用母對母杜邦線連接。                

            它根據土壤的介電常數(土壤的導電能力)來估計土壤體積水含量,工作時,使用兩個探針讓電流通過土壤,然後讀取電阻來獲得濕度水平。水分越多,土壤導電越容易(電阻越小),而土壤乾燥,導電越差(電阻越大)。土壤中的濕度是一個連續變化的一系列值,為模擬信號,使用A to D接線板之後可以將從環境中得來的模擬信號轉成数字信號,在該板上有兩個指示燈,PWR-LED和DO-LED,前者檢測是否插好電源,如果電源的正負極連接正確,則會亮起,如下圖(左)所示。在這裏我使用的是YL-69型號,燈為綠色,也有一些廠商生產的傳感器指示燈為紅色。該傳感器在輸出数字信號時,可以使用改錐調節板上的電位計(藍色中間有十字架的部位)來提前設定閾值大小,一旦土壤濕度達到或大於閾值,則DO-LED亮起,如下圖(中)所示。

 电子板從左到右的標記為AO、DO、GND、VCC,如下圖(右)所示。AO和DO為信號引腳,如果需要模擬信號,則連接AO,輸出的模擬值是介於所提供的電壓值到0V間的變化的電壓值,如果輸出0V,則代表土壤導電性不好,即水分含量低,可以用這個電壓值來估計土壤濕度。如果需要数字信號輸出則連接DO,簡單的輸出0和1,可直接通過信號燈判斷土壤中水分是否低於閾值,高於則“開”,低則“關”。GND表示接地,VCC連接電源,但在這個項目中,我們將單獨利用模擬輸出。

               

 該傳感器的優點是價格低廉,且有指示燈,觀察方便,但由於土壤環境是酸性的(acidic),隨着時間的推移,土壤里的化學物質會使得探針氧化(oxidize)導致測量不準確,所以需要時不時的進行更換以保證測量的準確性。

(二)電容型傳感器

相比較前一類型的傳感器,這類傳感器就顯得“光禿禿”了,只有一個組件,沒有指示燈,且只能輸出模擬信號。它區別於電阻傳感器,利用電容感應原理來檢測土壤濕度,避免了電阻式傳感器極易被腐蝕的問題,生命周期較長,缺點是不能用指示燈判斷傳感器是否正常工作,它同時只提供模擬信號。如圖設計DF Robot的一款傳感器,內置穩壓芯片,支持3.3V-5.5V寬電壓工作。DFRobot-Gravity接口具有兼容性,可直接和Gravity IO擴展板相連接。輸出電壓為0-3VDC。

在自動化澆灌系統中,濕度傳感器用於測量土壤中水分,可以提前預設一個閾值,一旦土壤中的水分低於此閾值,則啟動連接着蓄水池(家用拿礦泉水瓶裝滿水替代即可)的水泵噴水,等到水分值超過預設值,則水泵暫停工作。

 

二、實驗所需設備

樹莓派 3 b+/樹莓 4 b/樹莓派Zero W

MCP3008

麵包板

跳線(公對母,母對母,最好多準備一些)

土壤傳感器(電容式、電阻式均可)

一杯水(可以將傳感器放入水中來觀察濕度讀數的變化,若沒有條件也可直接用手握住傳感器的探針)

MCP3008

由於樹莓派沒有模擬信號引腳,所以沒有辦法直接輸出模擬信號數值,此時我們需要使用MCP3008集成電路。

MCP3008 IC(Integrated Circuit)是一個8通道,10位的具有SPI串行接口的A / D轉換器(模擬-数字轉換器),共有16個引腳,可以用來解決模擬引腳問題(MCP3004也是ADC,不過為4路,體型更小)MCP3008使用SPI總線協議從樹莓派接收模擬輸入值。它有8個模擬輸入(ch0-ch7),另外一列的8個引腳中有4個電源和地引腳和4個連接樹莓派的引腳,它產生範圍為0-1023的輸出值(注意:0表示0V, 1023表示3.3V)。

三、設備連線方式與代碼實現

在此實驗中,主要介紹和使用的FC-28型號土壤濕度傳感器,但其它型號的傳感器使用,連線均與此相同。

(一)設備連線方式

1.電路圖及說明

MCP3008共有16個引腳,其中8個用於記錄模擬輸入值,位於CH0-CH7(引腳1-8),4個通信引腳通過SPI協議方法與樹莓派通信,還有2個電源引腳,2個接地引腳。

(二)代碼實現

1. 啟用樹莓派的SPI接口

樹莓派的SPI接口與SSH、VNC服務相同,是默認關閉的,需要我們在配置中打開此服務才可以使用。

按照以下步驟啟動終端並輸入以下命令:

(1)打開樹莓派配置選項

sudo raspi-config

(2)導航到Interface選項,啟用SPI接口。

(3)重啟樹莓派

reboot

2. 安裝spidev庫

光啟用SPI接口,但是樹莓派還是無法讀取傳感器傳過來的值,spidev庫將幫助通過SPI接口讀取傳感器值。

 使用以下命令安裝spidev庫:

sudo apt-get install git python-dev
git clone git://github.com/doceme/py-spidev
cd py-spidev/
sudo python setup.py install

3. 安裝numpy庫

我們從傳感器獲得的值還是電壓值而非土壤濕度值,土壤濕度需要使用百分比的形式體現,為了將輸出值轉換為百分比,還需要使用numpy庫。我們從MCP3008 IC接收到的輸出值是在前面提到的0-0123範圍內的數值,仍需要將把這些值映射到0-100,以得到一個百分比。

使用以下命令安裝numpy模塊:

sudo apt-get install python-numpy

4. Python代碼

# Importing modules
import spidev # To communicate with SPI devices
from numpy import interp  # To scale values
from time import sleep  # To add delay


# Start SPI connection
spi = spidev.SpiDev() # Created an object
spi.open(0,0) 


# Read MCP3008 data
def analogInput(channel):
  spi.max_speed_hz = 1350000
  adc = spi.xfer2([1,(8+channel)<<4,0])
  data = ((adc[1]&3) << 8) + adc[2]
  return data


while True:
  output = analogInput(0) # Reading from CH0
  output = interp(output, [0, 1023], [100, 0])
  output = int(output)
  print("Moisture:", output)
  sleep(0.1)

當從土壤濕度傳感器讀取模擬輸出值時,它以百分比測量濕度,使用來自numpy庫的特定interp模塊進行映射得到從0-100的值。

四、常見問題及注意事項

(一)常見問題

1. 持續輸出0或100,無論探針是否放入水中均沒有變化

2. 沒有操作探針,但讀數呈有規律地變化

針對以上出現有以下幾種解決方案

1.檢查樹莓派的SPI服務有無正確打開

2.先檢查線有沒有接穩,查看是不是線的連接順序(傳感器的正負極有沒有接反,與樹莓派的連線有沒有串行)有誤

3. 線是否有生鏽或損壞(之前第一次做實驗時,各種調試都出現不了正確結果,後來才發現是有幾根線生鏽了所以不通)

(二)注意事項 

1. 盡量使用長線,便於看清連線位置

2. 盡量不要使用拼接線(一根公對公,一根母對母拼接成公對母的),這樣需要照顧的線更多,也更容易出紕漏

3. 一定一定要有耐心,出現問題后按照順序逐一排查。因為涉及到的連線較多,對硬件小白來說,很容易眼花繚亂想放棄,但是太簡單的東西誰都能做,能攻破學習或者生活中一個個難關的人才能有所成長啊~

如果你在配置土壤濕度傳感器時或使用樹莓派時遇見了什麼問題,歡迎在評論區寫下,看到了會及時答覆。期待與大家一起學習。

文字及圖片部分來源:https://maker.pro/raspberry-pi/tutorial/interfacing-soil-moisture-sensor-with-raspberry-pi

轉發請標明來源。祝大家學派happy!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

都在講DevOps,但你知道它的發展趨勢嗎?

根據最近的一項集體研究,DevOps的市場在2017年創造了約29億美元的產值,預計到2022年,這個数字將達到約66億美元。人工智能的融入和安全性的融入,加上向自動化的巨大轉變,可合理預測,在2020年,DevOps將成為軟件工程的主流模式。

DevOps具有以下優勢:

●對需求變更的迅速響應

●超快的交付速度及靈活的安全部署

●建立完善的協作溝通渠道

●快速識別代碼中的錯誤或漏洞

●讓團隊將注意力集中在其他關鍵的事情上,而不是集中在安全特性上

 

越來越多的企業正採用DevOps的產品交付模式:根據Statista的統計數據,全面採用DevOps的企業數量從2017年的約10%增長到了2018年的17%。

而devops也將在2020年迎來新趨勢。

 

自動化成為焦點

實施DevOps產品交付模式的組織已經見證了極高的效率和超快速的部署速度。在提到DevOps時,我們主要討論的是DevOps自動化,零接觸自動化是未來的發展方向。在DevOps生命周期的7C(持續發展、持續集成、持續測試、持續反饋、持續監測、持續部署、持續運維)中,應用自動化是未來的關鍵,因為預計這將是2020年的主要目標之一。

注意力從CI管道轉移到DevOps的裝配線

DevOps的重要目標是改進交付過程的計劃階段和自動化階段之間的協作。這不僅僅關乎CI(持續集成),更重要的是關乎CD(持續交付)。許多組織正在投入額外的精力和時間來使公司軟件開發的整個過程自動化。因此,對於這些組織來說,現在是聯繫DevOps諮詢服務提供商的時候了。預計到2020年,注意力將從CI管道轉移到DevOps的裝配線。裝配線的一些共同優點如下:

●原生集成

●堅固的嵌套可見性

●適當互用性的完美持續交付

●基於團隊的分析以及商業智能

●快速實現和擴展“一切皆代碼”理念

 

對無服務器架構的使用增加

使用無服務器架構可以將DevOps提升到更高的水平,這並不意味着沒有服務器,而是使用雲服務的整體架構。FaaS(Function as a Service,功能即服務)和BaaS(Backend as a Service,後端即服務)是無服務器架構的兩個關鍵方面。通過採用這種無服務器體繫結構,企業可以節省時間、降低成本,並擁有具有彈性的、靈活的工作流。

“一切皆代碼”的概念

程序編碼是IT部門及其服務系統的骨幹。對DevOps自動化工具和腳本的充分理解將支配整個2020年。這個特定IT領域的前景與產品的未來取決於開發人員、測試人員及運維人員的技術能力。現在,隨着交付周期的縮短,需要引入代碼來提高軟件生產周期的效率。“一切皆代碼”的概念是在DevOps內部完成代碼的SDLC的實踐。如果軟件測試人員還不開始學習編程和編寫測試腳本,工作很可能會受到阻礙。

更好的嵌入式安全性

隨着安全漏洞的出現,越來越多的大小企業意識到網絡安全的重要性。2020年,DevOps預計將迅速將安全問題納入流程。DevSecOps首先在應用程序的開發生命周期中注入安全性,這有助於減少各種缺陷和漏洞,增加業務信譽。公司轉向DevSecOps促使項目中每個人都擔負安全方面的責任,這將在軟件開發過程中帶來很棒的協作,因為它確保了軟件開發過程始終保持完美、高效和可操作。

人工智能的興起和數據科學的飛速發展

隨着人工智能驅動的應用程序大量增加,數據科學正在推動越來越多的公司在其工作流程中採用DevOps理念。隨着數據科學和開發團隊在軟件開發、部署以及人工智能驅動的應用程序管理方面的效率越來越高,這將會進一步推動數據科學的發展。

2020年的主要目標是實現零接觸自動化。 持續不斷的人工智能和數據科學熱潮改變着遊戲規則。 許多應用程序都引入了人工智能,這已經促使多個DevOps團隊通過人工智能和數據科學實現自動化,數據科學團隊和開發團隊相輔相成地提高彼此的技能與交付水平。

對無服務器架構的使用增加

使用無服務器架構可以將DevOps提升到更高的水平,這並不意味着沒有服務器,而是使用雲服務的整體架構。FaaS(Function as a Service,功能即服務)和BaaS(Backend as a Service,後端即服務)是無服務器架構的兩個關鍵方面。通過採用這種無服務器體繫結構,企業可以節省時間、降低成本,並擁有具有彈性的、靈活的工作流。

Kubernetes長足發展

Kubernetes提供了基於容器技術的分佈式架構領先方案產品,因自身性能及易用性,已經成為應用廣泛的容器技術。伴隨着各類企業進一步通過深度採用容器技術來運行它們的雲原生應用,K8s將會迎來更廣的普及、更大的發展。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

使用 Masstransit中的 Request/Response 與 Courier 功能實現最終一致性

簡介

  目前的.net 生態中,最終一致性組件的選擇一直是一個問題。本地事務表(cap)需要在每個服務的數據庫中插入消息表,而且做不了此類事務 比如:創建訂單需要 餘額滿足+庫存滿足,庫存和餘額處於兩個服務中。masstransit 是我目前主要用的方案。以往一般都用 masstransit 中的 sagas 來實現 最終一致性,但是隨着併發的增加必定會對sagas 持久化的數據庫造成很大的壓力,根據stackoverflow 中的一個回答 我發現了 一個用  Request/Response 與 Courier 功能 實現最終一致性的方案 Demo地址。

Masstransit 中 Resquest/Response 功能 

 消息DTO

    public class SampleMessageCommand
    {
    }

 消費者

    public class SampleMessageCommandHandler : IConsumer<SampleMessageCommand>
    {
        public async Task Consume(ConsumeContext<SampleMessageCommand> context)
        {
            await context.RespondAsync(new SampleMessageCommandResult() { Data = "Sample" });
        }
    }

 返回結果DTO

 

    public class SampleMessageCommandResult
    {
        public string Data { get; set; }
    }

 調用方式與註冊方式略過,詳情請看 官方文檔。

  

  本質上使用消息隊列實現 Resquest/Response,客戶端(生產者)將請求消息發送至指定消息隊列並賦予RequestId和ResponseAddress(臨時隊列 rabbitmq),服務端(消費者)消費消息並把 需要返回的消息放入指定ResponseAddress,客戶端收到 Response message  通過匹配 RequestId 找到 指定Request,最後返回信息。

Masstransit 中 Courier  功能

  通過有序組合一系列的Activity,得到一個routing slip。每個 activity(忽略 Execute Activities) 都有 Execute 和 Compensate 兩個方法。Compensate 用來執撤銷 Execute 方法產生的影響(就是回退 Execute 方法)。每個 Activity Execute 最後都會 調用 Completed 方法把 回退所需要的的信息記錄在message中,最後持久化到消息隊列的某一個消息中。

 餘額扣減的Activity ,這裏的 DeductBalanceModel 是請求扣減的數據模型,DeductBalanceLog 是回退時需要用到的信息。

public class DeductBalanceActivity : IActivity<DeductBalanceModel, DeductBalanceLog>
    {
        private readonly ILogger<DeductBalanceActivity> logger;
        public DeductBalanceActivity(ILogger<DeductBalanceActivity> logger)
        {
            this.logger = logger;
        }
        public async Task<CompensationResult> Compensate(CompensateContext<DeductBalanceLog> context)
        {
            logger.LogInformation("還原餘額");
            var log = context.Log; //可以獲取 所有execute 完成時保存的信息
            //throw new ArgumentException("some things were wrong");
            return context.Compensated();
        }

        public async Task<ExecutionResult> Execute(ExecuteContext<DeductBalanceModel> context)
        {

            logger.LogInformation("扣減餘額");
            await Task.Delay(100);
            return context.Completed(new DeductBalanceLog() { Price = 100 });
        }
    }

 

      扣減庫存 Activity

    public class DeductStockActivity : IActivity<DeductStockModel, DeductStockLog>
    {
        private readonly ILogger<DeductStockActivity> logger;
        public DeductStockActivity(ILogger<DeductStockActivity> logger)
        {
            this.logger = logger;
        }
        public async Task<CompensationResult> Compensate(CompensateContext<DeductStockLog> context)
        {
            var log = context.Log;
            logger.LogInformation("還原庫存");
            return context.Compensated();
        }

        public async Task<ExecutionResult> Execute(ExecuteContext<DeductStockModel> context)
        {
            var argument = context.Arguments;
            logger.LogInformation("扣減庫存");
            await Task.Delay(100);
            return context.Completed(new DeductStockLog() { ProductId = argument.ProductId, Amount = 1 });
        }
    }

       生成訂單 Execute Activity

    public class CreateOrderActivity : IExecuteActivity<CreateOrderModel>
    {
        private readonly ILogger<CreateOrderActivity> logger;
        public CreateOrderActivity(ILogger<CreateOrderActivity> logger)
        {
            this.logger = logger;
        }
        public async Task<ExecutionResult> Execute(ExecuteContext<CreateOrderModel> context)
        {
            logger.LogInformation("創建訂單");
            await Task.Delay(100);
            //throw new CommonActivityExecuteFaildException("當日訂單已達到上限");
            return context.CompletedWithVariables(new CreateOrderResult { OrderId="111122",Message="創建訂單成功" });
        }
    }

  組裝 以上 Activity 生成一個 Routing Slip,這是一個有序的組合,扣減庫存=》扣減餘額=》生成訂單

            var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddActivity("DeductStock", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductStock_execute"), new DeductStockModel { ProductId = request.Message.ProductId }); builder.AddActivity("DeductBalance", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductBalance_execute"), new DeductBalanceModel { CustomerId = request.Message.CustomerId, Price = request.Message.Price }); builder.AddActivity("CreateOrder", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/CreateOrder_execute"), new CreateOrderModel { Price = request.Message.Price, CustomerId = request.Message.CustomerId, ProductId = request.Message.ProductId });
var routingSlip = builder.Build();

  執行 Routing Slip

await bus.Execute(routingSlip);

  

      這裡是沒有任何返回值的,所有activity都是 異步執行,雖然所有的activity可以執行完成或者由於某個Activity執行出錯而全部回退。(其實這裡有一種更壞的情況就是 Compensate 出錯,默認情況下 Masstransit 只會發送一個回退錯誤的消息,後面講到創建訂單的時候我會把它塞到錯誤隊列里,這樣我們可以通過修改 Compensate bug后重新導入到正常隊列來修正數據),這個功能完全滿足不了 創建訂單這個需求,執行 await bus.Execute(routingSlip) 后我們完全不知道訂單到底創建成功,還是由於庫存或餘額不足而失敗了(異步)。

     還好 routing slip 在執行過程中產生很多消息,比如 RoutingSlipCompleted ,RoutingSlipCompensationFailed ,RoutingSlipActivityCompleted,RoutingSlipActivityFaulted 等,具體文檔,我們可以訂閱這些事件,再結合Request/Response 實現 創建訂單的功能。

實現創建訂單(庫存滿足+餘額滿足)長流程

創建訂單 command 

    /// <summary>
    /// 長流程 分佈式事務
    /// </summary>
    public class CreateOrderCommand
    {
        public string ProductId { get; set; }
        public string CustomerId { get; set; }
        public int Price { get; set; }
    }

  事務第一步,扣減庫存相關 代碼

  public class DeductStockActivity : IActivity<DeductStockModel, DeductStockLog>
    {
        private readonly ILogger<DeductStockActivity> logger;
        public DeductStockActivity(ILogger<DeductStockActivity> logger)
        {
            this.logger = logger;
        }
        public async Task<CompensationResult> Compensate(CompensateContext<DeductStockLog> context)
        {
            var log = context.Log;
            logger.LogInformation("還原庫存");
            return context.Compensated();
        }

        public async Task<ExecutionResult> Execute(ExecuteContext<DeductStockModel> context)
        {
            var argument = context.Arguments;
            logger.LogInformation("扣減庫存");
            await Task.Delay(100);
            return context.Completed(new DeductStockLog() { ProductId = argument.ProductId, Amount = 1 });
        }
    }
    public class DeductStockModel
    {
        public string ProductId { get; set; }
    }
    public class DeductStockLog
    {
        public string ProductId { get; set; }
        public int Amount { get; set; }
    }

 事務第二步,扣減餘額相關代碼

public class DeductBalanceActivity : IActivity<DeductBalanceModel, DeductBalanceLog>
    {
        private readonly ILogger<DeductBalanceActivity> logger;
        public DeductBalanceActivity(ILogger<DeductBalanceActivity> logger)
        {
            this.logger = logger;
        }
        public async Task<CompensationResult> Compensate(CompensateContext<DeductBalanceLog> context)
        {
            logger.LogInformation("還原餘額");
            var log = context.Log;
            //throw new ArgumentException("some things were wrong");
            return context.Compensated();
        }

        public async Task<ExecutionResult> Execute(ExecuteContext<DeductBalanceModel> context)
        {

            logger.LogInformation("扣減餘額");
            await Task.Delay(100);
            return context.Completed(new DeductBalanceLog() { Price = 100 });
        }
    }
    public class DeductBalanceModel
    {
        public string CustomerId { get; set; }
        public int Price { get; set; }
    }
    public class DeductBalanceLog
    {
        public int Price { get; set; }
    }

 事務第三步,創建訂單相關代碼

 public class CreateOrderActivity : IExecuteActivity<CreateOrderModel>
    {
        private readonly ILogger<CreateOrderActivity> logger;
        public CreateOrderActivity(ILogger<CreateOrderActivity> logger)
        {
            this.logger = logger;
        }
        public async Task<ExecutionResult> Execute(ExecuteContext<CreateOrderModel> context)
        {
            logger.LogInformation("創建訂單");
            await Task.Delay(100);
            //throw new CommonActivityExecuteFaildException("當日訂單已達到上限");
            return context.CompletedWithVariables(new CreateOrderResult { OrderId="111122",Message="創建訂單成功" });
        }
    }
    public class CreateOrderModel
    {
        public string ProductId { get; set; }
        public string CustomerId { get; set; }
        public int Price { get; set; }
    }
    public class CreateOrderResult
    {
        public string OrderId { get; set; }
        public string Message { get; set; }
    }

   我通過 消費 創建訂單 request,獲取 request 的 response 地址與 RequestId,這兩個值 返回 response 時需要用到,我把這些信息存到 RoutingSlip中,並且訂閱 RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted | RoutingSlipEvents.CompensationFailed 三種事件,當這三種消息出現時 我會根據 事件類別 和RoutingSlip中 之前加入的 (response 地址與 RequestId)生成 Response ,整個過程大概就是這麼個意思,沒理解可以看demo。這裏由於每一個事物所需要用到的 RoutingSlip + Request/Response 步驟都類似 可以抽象一下(模板方法),把Activity 的組裝 延遲到派生類去解決,這個代理類Masstransit有 ,但是官方沒有顧及到 CompensationFailed 的情況,所以我乾脆自己再寫一個。

    public abstract class RoutingSlipDefaultRequestProxy<TRequest> :
        IConsumer<TRequest>
        where TRequest : class
    {
        public async Task Consume(ConsumeContext<TRequest> context)
        {
            var builder = new RoutingSlipBuilder(NewId.NextGuid());

            builder.AddSubscription(context.ReceiveContext.InputAddress, RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted | RoutingSlipEvents.CompensationFailed);
            
            builder.AddVariable("RequestId", context.RequestId);
            builder.AddVariable("ResponseAddress", context.ResponseAddress);
            builder.AddVariable("FaultAddress", context.FaultAddress);
            builder.AddVariable("Request", context.Message);

            await BuildRoutingSlip(builder, context);

            var routingSlip = builder.Build();

            await context.Execute(routingSlip).ConfigureAwait(false);
        }

        protected abstract Task BuildRoutingSlip(RoutingSlipBuilder builder, ConsumeContext<TRequest> request);
    }


 這個 是派生類 Routing slip 的拼裝過程 

    public class CreateOrderRequestProxy : RoutingSlipDefaultRequestProxy<CreateOrderCommand>

    {
        private readonly IConfiguration configuration;
        public CreateOrderRequestProxy(IConfiguration configuration)
        {
            this.configuration = configuration;
        }
        protected override Task BuildRoutingSlip(RoutingSlipBuilder builder, ConsumeContext<CreateOrderCommand> request)
        {
            builder.AddActivity("DeductStock", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductStock_execute"), new DeductStockModel { ProductId = request.Message.ProductId });

            builder.AddActivity("DeductBalance", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductBalance_execute"), new DeductBalanceModel { CustomerId = request.Message.CustomerId, Price = request.Message.Price });

            builder.AddActivity("CreateOrder", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/CreateOrder_execute"), new CreateOrderModel { Price = request.Message.Price, CustomerId = request.Message.CustomerId, ProductId = request.Message.ProductId });

            return Task.CompletedTask;
        }
    }

  構造response 基類,主要是對三種情況做處理。

 

    public abstract class RoutingSlipDefaultResponseProxy<TRequest, TResponse, TFaultResponse> : IConsumer<RoutingSlipCompensationFailed>, IConsumer<RoutingSlipCompleted>,
        IConsumer<RoutingSlipFaulted>
        where TRequest : class
        where TResponse : class
        where TFaultResponse : class
    {
        public async Task Consume(ConsumeContext<RoutingSlipCompleted> context)
        {
            var request = context.Message.GetVariable<TRequest>("Request");
            var requestId = context.Message.GetVariable<Guid>("RequestId");

            Uri responseAddress = null;
            if (context.Message.Variables.ContainsKey("ResponseAddress"))
                responseAddress = context.Message.GetVariable<Uri>("ResponseAddress");

            if (responseAddress == null)
                throw new ArgumentException($"The response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");

            var endpoint = await context.GetResponseEndpoint<TResponse>(responseAddress, requestId).ConfigureAwait(false);

            var response = await CreateResponseMessage(context, request);

            await endpoint.Send(response).ConfigureAwait(false);
        }

        public async Task Consume(ConsumeContext<RoutingSlipFaulted> context)
        {
            var request = context.Message.GetVariable<TRequest>("Request");
            var requestId = context.Message.GetVariable<Guid>("RequestId");

            Uri faultAddress = null;
            if (context.Message.Variables.ContainsKey("FaultAddress"))
                faultAddress = context.Message.GetVariable<Uri>("FaultAddress");
            if (faultAddress == null && context.Message.Variables.ContainsKey("ResponseAddress"))
                faultAddress = context.Message.GetVariable<Uri>("ResponseAddress");

            if (faultAddress == null)
                throw new ArgumentException($"The fault/response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");

            var endpoint = await context.GetFaultEndpoint<TResponse>(faultAddress, requestId).ConfigureAwait(false);

            var response = await CreateFaultedResponseMessage(context, request, requestId);

            await endpoint.Send(response).ConfigureAwait(false);
        }
        public async Task Consume(ConsumeContext<RoutingSlipCompensationFailed> context)
        {
            var request = context.Message.GetVariable<TRequest>("Request");
            var requestId = context.Message.GetVariable<Guid>("RequestId");

            Uri faultAddress = null;
            if (context.Message.Variables.ContainsKey("FaultAddress"))
                faultAddress = context.Message.GetVariable<Uri>("FaultAddress");
            if (faultAddress == null && context.Message.Variables.ContainsKey("ResponseAddress"))
                faultAddress = context.Message.GetVariable<Uri>("ResponseAddress");

            if (faultAddress == null)
                throw new ArgumentException($"The fault/response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");

            var endpoint = await context.GetFaultEndpoint<TResponse>(faultAddress, requestId).ConfigureAwait(false);

            var response = await CreateCompensationFaultedResponseMessage(context, request, requestId);

            await endpoint.Send(response).ConfigureAwait(false);
        }
        protected abstract Task<TResponse> CreateResponseMessage(ConsumeContext<RoutingSlipCompleted> context, TRequest request);

        protected abstract Task<TFaultResponse> CreateFaultedResponseMessage(ConsumeContext<RoutingSlipFaulted> context, TRequest request, Guid requestId);
        protected abstract Task<TFaultResponse> CreateCompensationFaultedResponseMessage(ConsumeContext<RoutingSlipCompensationFailed> context, TRequest request, Guid requestId);
    }

 Response 派生類 ,這裏邏輯可以隨自己定義,我也是隨便寫了個 CommonResponse和一個業務錯誤拋錯(犧牲了一點性能)。

    public class CreateOrderResponseProxy :
            RoutingSlipDefaultResponseProxy<CreateOrderCommand, CommonCommandResponse<CreateOrderResult>, CommonCommandResponse<CreateOrderResult>>
    {

        protected override Task<CommonCommandResponse<CreateOrderResult>> CreateResponseMessage(ConsumeContext<RoutingSlipCompleted> context, CreateOrderCommand request)
        {

            return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
            {
                Status = 1,
                Result = new CreateOrderResult
                {
                    Message = context.Message.Variables.TryGetAndReturn(nameof(CreateOrderResult.Message))?.ToString(),
                    OrderId = context.Message.Variables.TryGetAndReturn(nameof(CreateOrderResult.OrderId))?.ToString(),
                }
            });
        }
        protected override Task<CommonCommandResponse<CreateOrderResult>> CreateFaultedResponseMessage(ConsumeContext<RoutingSlipFaulted> context, CreateOrderCommand request, Guid requestId)
        {
            var commonActivityExecuteFaildException = context.Message.ActivityExceptions.FirstOrDefault(m => m.ExceptionInfo.ExceptionType == typeof(CommonActivityExecuteFaildException).FullName);
            if (commonActivityExecuteFaildException != null)
            {
                return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
                {
                    Status = 2,
                    Message = commonActivityExecuteFaildException.ExceptionInfo.Message
                });
            }
            // system error  log here
            return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
            {
                Status = 3,
                Message = "System error"
            });
        }

        protected override Task<CommonCommandResponse<CreateOrderResult>> CreateCompensationFaultedResponseMessage(ConsumeContext<RoutingSlipCompensationFailed> context, CreateOrderCommand request, Guid requestId)
        {
            var exception = context.Message.ExceptionInfo;
            // lg here context.Message.ExceptionInfo
            return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
            {
                Status = 3,
                Message = "System error"
            });           
        }
    }

對於  CompensationFailed 的處理 通過 ActivityCompensateErrorTransportFilter 實現 發送到錯誤消息隊列,後續通過prometheus + rabbitmq-exporter + alertmanager 觸發告警 通知相關人員處理。

  public class ActivityCompensateErrorTransportFilter<TActivity, TLog> : IFilter<CompensateActivityContext<TActivity, TLog>>
        where TActivity : class, ICompensateActivity<TLog>
        where TLog : class
    {
        public void Probe(ProbeContext context)
        {
            context.CreateFilterScope("moveFault");
        }

        public async Task Send(CompensateActivityContext<TActivity, TLog> context, IPipe<CompensateActivityContext<TActivity, TLog>> next)
        {
            try
            {
                await next.Send(context).ConfigureAwait(false);
            }
            catch(Exception ex)
            {
                if (!context.TryGetPayload(out IErrorTransport transport))
                    throw new TransportException(context.ReceiveContext.InputAddress, $"The {nameof(IErrorTransport)} was not available on the {nameof(ReceiveContext)}.");
                var exceptionReceiveContext = new RescueExceptionReceiveContext(context.ReceiveContext, ex);
                await transport.Send(exceptionReceiveContext);
            }
        }
    }

註冊 filter 

    public class RoutingSlipCompensateErrorSpecification<TActivity, TLog> : IPipeSpecification<CompensateActivityContext<TActivity, TLog>>
        where TActivity : class, ICompensateActivity<TLog>
        where TLog : class
    {
        public void Apply(IPipeBuilder<CompensateActivityContext<TActivity, TLog>> builder)
        {
            builder.AddFilter(new ActivityCompensateErrorTransportFilter<TActivity, TLog>());
        }

        public IEnumerable<ValidationResult> Validate()
        {
           yield return this.Success("success");
        }
    }


            cfg.ReceiveEndpoint("DeductStock_compensate", ep =>
            {
                ep.PrefetchCount = 100;
                ep.CompensateActivityHost<DeductStockActivity, DeductStockLog>(context.Container, conf =>
                 {
                     conf.AddPipeSpecification(new RoutingSlipCompensateErrorSpecification<DeductStockActivity, DeductStockLog>());
                 });

            });

 

實現創建產品(創建完成+添加庫存)

實現了 創建訂單的功能,整個流程其實是同步的,我在想能不能實現最為簡單的最終一致性 比如 創建一個產品 ,然後異步生成它的庫存 ,我發現是可以的,因為我們可以監聽到每一個Execute Activity 的完成事件,並且把出錯時的信息通過 filter 塞到 錯誤隊列中。

這裏的代碼就不貼了,詳情請看 demo

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

一條update SQL語句是如何執行的

  一條更新語句的執行過程和查詢語句類似,更新的流程涉及兩個日誌:redo log(重做日誌)和binlog(歸檔日誌)。比如我們要將ID(主鍵)=2這一行的值加(c:字段)1,SQL語句如下:

update T set c=c+1 where ID=2;
  • redo log

  重做日誌是InnoDB引擎特有的,是物理日誌,記錄在“某個數據頁上做了什麼修改“。大小是固定,可以進行配置大小。假如我們配置一組4個文件,圖中write pos是當前記錄的位置,往後推移並且循環;checkpoint是當前要擦除的位置,移動規律和前者一樣。兩者之間的位置可以記錄新的操作

  

  如果write pos 追上checkpoint,就移動checkpoint擦除一些記錄。所以即使數據可以發生異常重啟,InnoDB也可以保證之前提交的記錄不會丟,這就是MySQL的crash_safe能力。

  • binlog

   歸檔日誌是MySQL的server層的實現的,所有引擎都可以使用。binlog記錄的是sql語句的原始邏輯,比如根劇’id’字段查詢所有的信息;相比redo log的循環寫入,binlog是追加寫的,binlog文件寫到一定大小後會切換到下一個,不會覆蓋以前的日誌。

  Binlog有兩種模式,statement 格式的話是記sql語句, row格式會記錄行的內容,記兩條,更新前和更新后都有。

 文章開頭的更新語句在InnoDB中的執行流程如下:深色代表在執行器中執行的,淺色是在存儲引擎中。

  最後寫入redolog分為了prepare和commit兩步,用來保證兩個日誌寫入的一致性,這就是“兩階段提交”。比如我們執行“update T set status = 1“時:

  • 如果寫入redolog成功,但寫binlog失敗,重啟恢復時,數據庫發現沒有commit,那麼事務本身回滾;備份恢復時沒有binlog,數據庫里的status值不變。
  • 如果在commit失敗,重啟恢復時redolog和binlog一致,重新commit;備份恢復時有binlog,直接恢復。

  總的來說binlog記錄了對數據庫所有的邏輯操作,可以通過binlog來備份出一份完全相同的庫;因為redolog是InnoDB引擎特有的,如果使用其他引擎,那麼就要依賴binlog來記錄操作。

Q定期全量備份的周期“取決於系統重要性,有的是一天一備,有的是一周一備”。那麼在什麼場景下,一天一備會比一周一備更有優勢呢?或者說,它影響了這個數據庫系統的哪個指標?

A一天一備,那麼如果需要恢複數據的話,只要保證當天的binlog完整即可;一周一備的話就要保證一周的binlog完整;同時頻繁全量備份需要更多存儲空間,如何選擇取決於業務的重要性,對應的指標是RTO(目標恢復時間)。

 — 《MySQL實戰45講》筆記二

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

BMW i3 在美銷量 首度超越 Tesla Model S

 

BMW i3 純電動汽車今年 8 月在美銷量為 1,025 輛,首次超過特斯拉 Model S,後者 8 月銷量僅為 600 輛。

寶馬 i3 純電動車型在美國市場已上市 3 個月,之前由於寶馬內部的運轉效率問題,其銷量一直低於特斯拉 Model S。但該問題解決後,寶馬 i3 的銷量也迎頭趕上。特斯拉 8 月整體需求較去年同期下降 54%,7 月更是下降了 72%。與 2013 年同期相比,特斯拉今年上半年銷量下降了 26%。

專家稱,特斯拉目前正處發展壯大的階段,而寶馬在歐洲和亞洲市場則以紮穩腳跟。在產品創新度、更新速度及售價方面,特斯拉 Model S 的優勢愈來愈不明顯,而隨著寶馬 i3 的銷量逐步上升,預計歐洲將會成為其最大銷售市場。

 

(圖片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

看準中國市場 LG 化學在南京建電動車電池工廠

韓國 LG 化學今(10)日表示,2015 年將在中國建電動車電池工廠,此舉是在押注身為全球最大汽車市場的中國需求將持續增加。

LG 化學稱,這座工廠設在中國南京,將滿足上汽集團等中國汽車製造商和通用汽車 (GM) 等全球性企業的需求,工廠耗資數億美元,預計 2020 年綜合營收將達到 1 兆韓元 ( 約 9.899 億美元)。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

雷諾與法國富豪攜手 搶進電動車市場

法國雷諾集團(Renault SA)與法國富豪波洛黑(Vincent Bollore)決定合作,共同製造電動車,瞄準零排放汽車日益強勁的需求,及正於各地興起的環保汽車租用潮流。   雷諾汽車將於 2015 下半年開始生產波洛黑的電動車款「藍車(Bluecar)」,但並未透露生產目標。該車款自 2011 年起,被使用於巴黎名為「Autolib」的電動車租賃共享計畫中,此計畫同時也於里昂及波爾多等地運行。   此外,雷諾汽車與波洛黑亦宣布,將成立一策略聯盟,其中波洛黑擁有 70% 股權,雷諾股權則達 30%。該聯盟將於法國及歐洲其他地方提供車輛共享服務。另外並進行研究,以協助雷諾製造一款 3 人座、使用波洛黑所生產電池的電動車。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

Jmeter系列(31)- 獲取並使用 JDBC Request 返回的數據

如果你想從頭學習Jmeter,可以看看這個系列的文章哦

https://www.cnblogs.com/poloyy/category/1746599.html

 

前言

  • Jmeter 使用 JDBC Request 獲取數據庫中數據,很多人都會用,因為測試中,有時候需要大量的用戶進行登錄,然後獲取數據庫中真實的數據用於測試
  • 前面也詳細講到 JDBC Request 的具體使用,一般是通過 Variable names 和 Result variable name 來獲取返回的數據
  • 這篇文章主要講的就是把 Variable names 和 Result variable name 獲取到的數據提取出來,給到 HTTP 請求使用

 

Variable names + Foreach控制器

線程組結構樹

 

JDBC Request

 

調試取樣器運行結果

有 100 條記錄

 

ForEach控制器

 

循環運行的結果( mobile:${mobile} )

 

Variable names + 循環控制器

和上面的栗子只是換了個控制器而已,沒太大變化

線程組結構樹

 

循環控制器

填寫 100,是代表循環100次

 

計數器

從 1 開始,遞增加到 100為止,每次遞增 1

  • 初始值=1
  • 每次增加 1
  • 最大的值=100(包含)
  • 新變量 num

 

循環控制器內的 Debug Sampler

 ${__V()} 是關聯函數,後面講到

 

循環運行的結果( mobile:${mobile} )

 

Result variable name + Foreach控制器

線程組結構樹

 

JDBC Request

 

正則提取器

 

重點

Applu to 選中 Jmeter Variable Name to use,因為要從 Jmeter Variables 中拿到 result_mobile 變量進行提取

 

調試取樣器運行結果

正則提取后的值是不是跟上面 Variable names 獲取的值列表很像,是的!然後再結合 ForEach控制器就好啦

 

ForEach控制器

變量前綴是正則提取器里的引用名稱

 

循環運行的結果( mobile:${mobile} )

 

Result variable name + 循環控制器

和上面的栗子只是換了個控制器而已,沒太大變化

線程組結構樹

 

循環控制器

填寫 100,是代表循環100次

 

計數器

 

用戶參數

重點一

  •  ${__BeanShell(vars.getObject(“result_mobile”).get(${num}).get(“mobile”))} 
  •  ${__BeanShell()} :執行BeanShell腳本,一般比較短的腳本可以用此方法來寫,後面會再詳細講解這個函數

重點二

  •  vars.getObject(“result_mobile”).get(${num}).get(“mobile”) 
  • result_mobile:是一個數組,即 JDBC Request 里的 Result variable name,每個元素的格式都是 {mobile=158000480001} 
  • ${num}:上面計數器的值,每次遞增 1,這裡是數組下標的意思
  • 總結:獲取 result_mobile 數組,每次取數組中第 num 個元素,從元素中取 mobile 鍵的值【這是固定寫法,只改Object 名、鍵名就行了】

 

循環運行的結果( mobile:${user_mobile} )

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

ASP.NET Core Blazor Webassembly 之 漸進式應用(PWA)

Blazor支持漸進式應用開發也就是PWA。使用PWA模式可以使得web應用有原生應用般的體驗。

什麼是PWA

PWA應用是指那些使用指定技術和標準模式來開發的web應用,這將同時賦予它們web應用和原生應用的特性。
例如,web應用更加易於發現——相比於安裝應用,訪問一個網站顯然更加容易和迅速,並且你可以通過一個鏈接來分享web應用。
在另一方面,原生應用與操作系統可以更加完美的整合,也因此為用戶提供了無縫的用戶體驗。你可以通過安裝應用使得它在離線的狀態下也可以運行,並且相較於使用瀏覽器訪問,用戶也更喜歡通過點擊主頁上的圖標來訪問它們喜愛的應用。
PWA賦予了我們創建同時擁有以上兩種優勢的應用的能力。
這並不是一個新概念——這樣的想法在過去已經在web平台上通過許多方法出現了多次。漸進式增強和響應式設計已經可以讓我們構建對移動端友好的網站。在多年以前的Firefox OS的生態系統中離線運行和安裝web應用已經成為了可能。
PWAs, 不但如此,更是提供了所有的甚至是更多的特性,來讓web更加優秀。

引用自MDN

說人話就是PWA可以讓你的web程序跟一般應用一樣運行,有桌面圖標,能離線,沒有瀏覽器地址欄,一切看起來想個普通的程序/APP。

新建Blazor PWA程序

使用VS新建一個Blazor程序,選擇Webassembly模式,勾選支持PWA。

支持PWA的Blazor程序主要是多了幾個東西:

  1. manifest.json
  2. service-worker.js

manifest.json

manifest.json是個清單文件,當程序被安裝到設備上的時候會讀取裏面的信息,名稱是什麼,圖標是什麼,什麼語言等等。

{
  "name": "BlazorPWA",
  "short_name": "BlazorPWA",
  "start_url": "./",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#03173d",
  "icons": [
    {
      "src": "icon-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ]
}

service-worker.js

service-worker用來跑一些後台任務。它跟瀏覽器主進程是隔離的,也就是說跟原來的JavaScript運行時是分開,當然了它不會阻塞頁面。我們可以用它來完成一些功能,比如對所有的fetch/xhr請求進行過濾,哪些請求走緩存,哪些不走緩存;比如在後台偷偷給你拉一些數據緩存起來。

// Caution! Be sure you understand the caveats before publishing an application with
// offline support. See https://aka.ms/blazor-offline-considerations

self.importScripts('./service-worker-assets.js');
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));

const cacheNamePrefix = 'offline-cache-';
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/ ];
const offlineAssetsExclude = [ /^service-worker\.js$/ ];

async function onInstall(event) {
    console.info('Service worker: Install');

    // Fetch and cache all matching items from the assets manifest
    const assetsRequests = self.assetsManifest.assets
        .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
        .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
        .map(asset => new Request(asset.url, { integrity: asset.hash }));
    await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}

async function onActivate(event) {
    console.info('Service worker: Activate');

    // Delete unused caches
    const cacheKeys = await caches.keys();
    await Promise.all(cacheKeys
        .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
        .map(key => caches.delete(key)));
}

async function onFetch(event) {
    let cachedResponse = null;
    if (event.request.method === 'GET') {
        // For all navigation requests, try to serve index.html from cache
        // If you need some URLs to be server-rendered, edit the following check to exclude those URLs
        const shouldServeIndexHtml = event.request.mode === 'navigate';

        const request = shouldServeIndexHtml ? 'index.html' : event.request;
        const cache = await caches.open(cacheName);
        cachedResponse = await cache.match(request);
    }

    return cachedResponse || fetch(event.request);
}

項目里有2個service-worker.js文件,一個是開發時候的沒邏輯,還有一個是發布時候的有一些緩存的邏輯。

運行一下

如果是PWA程序,在瀏覽器地址欄有個+號一樣的圖標,點擊可以把程序安裝到本地。

安裝完了會在桌面生成一個圖標,打開會是一個沒有瀏覽器地址欄的界面。

這樣一個PWA程序已經可以運行了。

離線運行

如果只是這樣,僅僅是沒有瀏覽器地址欄,那PWA也太沒什麼吸引力了。個人覺得PWA最大的魅力就是可以離線運行,在沒有網絡的情況下依然可以運行,這樣才像一個原生編寫的程序。

修改service-worker

離線的原理也很簡單,就是請求的數據都緩存起來,一般是緩存Get請求,比如各種頁面圖片等。

// In development, always fetch from the network and do not enable offline support.
// This is because caching would make development more difficult (changes would not
// be reflected on the first load after each change).

self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
self.addEventListener('install', event => event.waitUntil(onInstall(event)));

async function onInstall(event) {
    console.info('Service worker: Install');
}


async function onFetch(event) {
    let cachedResponse = null;
    const cache = await caches.open('blazor_pwa');
    if (event.request.method === 'GET') {
        const request = event.request;
        cachedResponse = await caches.match(request);
        if (cachedResponse) {
            return cachedResponse;
        }
        var resp = await fetch(event.request)
        cache.put(event.request, resp.clone());
        return resp;
    }

    return fetch(event.request);
}

修改一下sevice-worker.js,把GET請求全部緩存起來。這裏為了演示圖方便,其實情況顯然不會這麼簡單粗暴。為了能緩存頁面,顯然必須先在線運行成功一次。

模擬離線

當我們修改完上面的js,然後在線正常一次后,可以看到所有GET請求的資源都被緩存起來了。

我們可以用chrome來模擬離線情況:

選擇offline模式,然後刷新我們的頁面,如果依然可以正常運行則表示可以離線運行。

總結

使用Blazor可以快速的開發PWA應用。利用PWA跟Blazor Webassembly的特性,可以開發出類似桌面的應用程序。或許這是跨平台桌面應用開發除了electron的又一種方案吧。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準