抗議氣候變遷 奧斯卡影后珍芳達被捕

摘錄自2019年10月12日中央通訊社美國報導

奧斯卡影后珍芳達(Jane Fonda)12日在美國國會山莊外被逮捕,當時她參與氣候變遷抗議活動,並在現場發表談話要求當局採取行動保護環境,之後就被警方銬上手銬帶走。

法新社報導,長期投身社會運動的珍芳達在臉書(Facebook)專頁張貼的影片顯示,她在國會大廈的階梯上抗議長達10分鐘後,便與其他數名人士一同遭到拘押。

警方發言人發布聲明說:「美國國會警察局(US Capitol Police)今天逮捕16人,因為他們違法在國會山莊東側抗議。」但聲明並未指明是哪些人被捕。

81歲的珍芳達穿著亮紅色大衣,反覆呼喊氣候變遷相關口號,而後她被警方銬上手銬,並在其他示威者的歡呼聲下被帶走。數個小時後珍芳達就被釋放。

珍芳達近期告訴「洛杉磯時報」(Los Angeles Times),她將搬到華府4個月,並效法瑞典環保少女桑柏格(Greta Thunberg)的熱情,全心全力對抗全球暖化。

珍芳達被捕前曾向現場一小群人發表談話,她痛斥氣候變遷這項「人為危機」,還說自己跟其他環保人士「每週五中午11時,無論晴天、雨天、下雪或暴風雪」,他們都會回到國會山莊展開一連串示威活動。

「紐約時報」(New York Times)報導,珍芳達若因參與違法示威活動被定罪,將面臨最多250美元(約新台幣7660元)罰款,以及最高90天的刑期。

珍芳達曾分別以1971年「柳巷芳草」(Klute)與1978年「歸返家園」(Coming Home)兩部作品,摘下奧斯卡影后殊榮。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

Lens —— 最炫酷的 Kubernetes 桌面客戶端

原文鏈接:https://fuckcloudnative.io/posts/lens/

Kubernetes 的桌面客戶端有那麼幾個,曾經 Kubernetic 應該是最好用的,但最近有個叫 Lens 的 APP 改變了這個格局,功能比 Kubernetic 多,使用體驗更好,適合廣大系統重啟工程師裝逼。它有以下幾個亮點:

Lens 就是一個強大的 IDE,可以實時查看集群狀態,實時查看日誌流,方便排查故障。有了 Lens,你可以更方便快捷地使用你的集群,從根本上提高工作效率和業務迭代速度。

日誌流界面可以選擇显示或隱藏時間戳,也可以指定显示的行數:

Lens 可以管理多集群,它使用內置的 kubectl 通過 kubeconfig 來訪問集群,支持本地集群和外部集群(如EKS、AKS、GKE、Pharos、UCP、Rancher 等),甚至連 Openshift 也支持:

只是與 Openshift 的監控還不太兼容。也可以很輕鬆地查看並編輯 CR:

有了 Lens,你就可以統一管理所有的集群。

③ Lens 內置了資源利用率的儀錶板,支持多種對接 Prometheus 的方式:

④ Lens 內置了 kubectl,它的內置終端會確保集群的 API Server 版本與 kubectl 版本兼容,所以你不需要在本地安裝 kubectl。可以驗證一下:

你會看到本地安裝的 kubectl 版本和 Lens 裏面打開的終端里的 kubectl 版本信息是不一樣的,Lens 確實內置了 kubectl。

⑤ Lens 內置了 helm 模板商店,可直接點擊安裝:

現在 Lens 迎來了最新版 3.5.0,換上了全新的 Logo

穩定性也提升了很多,快去試試吧。

Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

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
"""

 

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

【其他文章推薦】

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

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

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

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

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

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

都在講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講》筆記二

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

畢業了,我的四年大學:平凡但不平庸(寫給每一位想要認真學習的小夥伴)

去年十月份的時候,我分享了一篇關於我三年大學的文章:普普通通,我的三年大學,說實話,這篇文章還是激勵了不少小夥伴,給很多人帶來了動力。寫這篇那會,我剛結束了自己的秋招,結束秋招的那一刻,可以說是 2019 年心情最放鬆的時刻了,對於沒有讀研的學生來說,秋招那一戰,可以算是整個大學最重要的一戰了,在這裏,也希望各位小夥伴,也要早點做好準備。

時間是過的真快,一不小心就在公司實習了差不多三個月了,大學最後的作業 —- 答辯和論文,也都處理完畢了,可以說我的大學,即將要畫上一個句號了。

我的四年大學,可以說是普普通通,在校期間也沒啥輝煌的戰績,更準確的說是,0 戰績:沒有參加過什麼大賽,甚至沒有參加過比賽;在學校也沒拿過獎學金,連個三好學生都不給;也沒去用心融入一個社團,成為裏面的一員;更沒有去做過兼職,發過海報。

但正是因為普普通通,我才要分享自己的四年大學。一個簡單的原因就是,我的公眾號里有 65% 是在校生,並且我相信大部分人和我一樣,都是普普通通,所以我希望我在大學的做法,或許可以給你們一個參考。不過說實話,雖然我的大學沒有什麼輝煌的戰績,但我還算滿意,至少是按照我自己的計劃走過來的,並且自己的目標也都實現了。

大一

大一第一學期這部分的事情最多了,所以會寫的多一些,因為我覺得,在大一,我的思維發生了很大的改變,大一過後,我的想法、計劃就基本有着落了,後面基本事按照自己的計劃,去做自己喜歡的事了。

大一第一學期的第一要務:轉專業

看過我之前文章的讀者可能都知道,我大一第一學期不是計算機專業的,讀的專業是木材科學與工程。當然,這不是我自己選擇的專業,而是高考分數不夠,被調劑的專業,我自己熱愛的專業是計算機類專業,還沒有進入大學之前,我就說我一定要讀編程相關的專業了。

說實話,我很慶幸自己最後轉到了計算機類的專業,一個很現實的原因就是,學編程,真的能夠掙錢,而且你願意努力學的話,你一畢業就能夠拿到很高的工資,本科畢業,有好些可以拿到年薪 50 萬,你敢信?就算工資很低,也都能年薪十幾萬,當然,我不能暴露自己的工資,暴露了就要被公司勸退了。

是的,我讀計算機專業,有兩個重要的原因,一個原因就是熱愛,一個是家裡窮,我想掙錢,改變家裡的現狀。所以在我大一第一學期那一會,我的目標就很明確,這學期的任務就是轉專業,所以我了解了轉專業的各種規則,了解到轉專業的一個最基本的要求就是數學和英語需要 85 分以上才能參与考試。

85 分很難嗎?不難;但是如果需要你保證 85 分難嗎?說實話,有一些,因為萬一考不好呢?考不好就得大二轉了,所以我第一學期,英語的平時分,我給刷到了 100 分,也就是滿分。數學是我的強項,但我依然不敢掉以輕心,在考前把 2006-2015 年的試卷都刷了一兩遍。

有這樣的準備,數學和英語的分數那必須杠杠,實不相瞞,大一學期的績點是我整個大學的巔峰,然而,三等獎依然沒有拿到!!!因為我只學了這兩門,其他隨意,不掛科即可。

當然,85 分只是一個門檻,轉專業的競爭還是挺激烈。我選了軟件工程,參与人數大概是差不多 60 人,不過學院只接 15 個人(後來好像是錄取了 20 人),所以為了穩一些,我在第一學期就把 c 語言自學了一遍,把學校的 OJ 題庫,第一學期相關的編程題,給刷了一兩遍。

說實話,有了這樣的準備,轉專業想不成功都難,考試 2 小時,我不到半個小時就離開考場了,然而我居然是第三個離開考場的,而不是第一個。第一個和第二個離開考場的,單挑嗎?

有必要加入社團嗎?

對於剛步入大學的同學來說,社團是一個比較有趣的玩意,絕大部分人都會去加入自己喜歡的社團吧。

我進入大學之前,很多人說,在大學,要多交點人脈,多認識一些朋友,因為這些人會成為你後面很重要的資源,所以我去加入一些社團,其實是想多認識一些人,然而,我去面試了兩個社團,都在二面被刷了,說實話,對於面試,我還是有點恐懼的,反正就是挺緊張,後來我就乾脆不去面試了,不加入社團了。

後來我就加入了他們的會員,之前面試那個,是成為他們的幹部。成為會員則不需要面試,不過需要交會員費用,所以我就順便成為了幾個社團的會員,例如羽毛球,愛心社團啥的。

不過,我去參与了一兩次社團的活動之後,就沒在去了,一個簡單的原因就是,我不大喜歡,我還是喜歡去做自己喜歡的事。我是一個懶散,喜歡自由,不喜歡被束縛的人,所以我覺得,我還是不去參加這些活動了。

回答剛才的問題,有必要加入社團嗎?,可能很多人會覺得,必須要加入社團,不然大學就不完整了,我的想法則是,加入社團不是一個必選項,加與不加,我覺得都沒事,看你自己的喜歡。加入社團能學到很多東西嗎?能學到一些,但這些沒啥的,對於以後找工作,我覺得屬於可有可無。如果你自己願意學習,學習能力比較強,在哪裡都可以學到很多東西。

擺脫的社團之後,我基本就什麼組織也沒參加了,然後我宿舍也有一個和我一樣比較逗比+沙雕的,我倆就經常去外面溜達,看到有趣的活動,就去參与一波,反正完全看心情,報名了活動,交了錢,心情不好就不去參加了,美滋滋(感覺要被噴,哈哈)

有必要多認識些人嗎?

我剛才說了,我希望自己在大學多認識些人,多泛交些朋友,後來經過第一學期的感悟,我發現,這沒必要,在大學,大家基本都在忙各自的事情,我本來還想在大學找幾個摯友,以後一起干大事,但經過一學期的觀察,發現這很難,當然,很難不代表不存在。

總之,對於現在還在大一大二的學生,如果你有這方面的疑問,那麼我給的建議是,沒必要刻意去交朋友,其實後面大家都各自去做自己的事情了,畢竟找到一些經的起時間考驗的志同道合的朋友,很不容易。

第二學期:落差之后的折騰

大一學期其實可以寫的還有非常非常多,因為第一學期,我的任務就兩門課程,還是非常閑,期間也發生了非常多改變我想法多事情,不過一不小心就兩千多字了,還是不繼續寫了,後面的時光可能就沒有第一學期那麼豐富了,相對比較枯燥了。

經過了第一學期,成功進入了軟件工程,自己也沒參与什麼社團,並且大學想要干點事,例如創業之類的,因為沒讀大學之前,經常聽說創業這事……總之,進入大學的時候,感覺前途一片光明,我覺得我要干非常非常多的事,但進入大學之後,發現並沒有啥戰績。

慶幸的事,我完成了一個非常堅定的目標,那便是轉專業。轉過來軟工之後,我也想干點大事,例如 acm 拿個牛逼的獎牌,或者寫個牛逼的軟件出來,因為經常聽到某某人開發了一個 xx,然後就成名了。

然而,學了 c 語言,發現啥也寫不出來,學了算法,發現 acm 那些題也太難了,一道題做一天,還是沒做出來,答案也看不懂,發現自己並不是大神,腦子也並沒有大佬轉的快。

後來,我就不打算參加 acm 了,感覺如果自己要拿到名次,肯定會花很多時間,並且不一定拿的到,加上我看到班裡也有人退出 acm 集訓隊了,這更加堅定了我的想法。

聽說數據結構與算法很重要,所以我早早就把數據結構與算法這門課學完了,我第二學期學的最多的就是數據結構 + 算法這兩門課,雖然不參加 ACM,但算法還是得學,會點算法聽說會顯的牛逼一些。

暑假的折騰

大一暑假那會,雖然數據結構與算法學的還不錯,不過發現啥東西的做不出來,C 語言寫的程序都是黑乎乎的界面,然後我就學了 windows 程序編程,這樣我就可以寫個程序給身邊的人玩了,畢竟我是學編程的,至少得寫個作品出來給別人玩啊。

在暑假花了十幾天,把那本 900 多頁的windows程序編程刷了 700 多頁,寫了個計算器,後來發現身邊的人還是玩不了,因為很多人沒電腦,於是我就對 windows 編程沒興趣了,想着寫個程序能夠在手機運行就好了,於是花了十幾天學了 android 編程,刷完了《第一行代碼》這本書,順便入門了 Java,跟着書寫了個天氣預報,還是挺開心。

不過我又改變主意了,想着要是能掙到錢就更好了,於是我發現把 app 上傳到商店,然後植入廣告,就可以掙錢了,於是我買了一些實戰類 android 項目的書籍,寫了幾個 app,自己改版之後傳到了應用商店。

然而,沒啥人下載,於是我又放棄搞安卓了,後來想寫個網站,然後放一些資源,讓別人來下載好像也有機會掙錢,於是我學習了 HTML,CSS,JavaScript,然後又不了了之……

這兩個月的暑假,我感覺自己搞了好多東西,好多都是半途放棄,實不相瞞,驅動我去折騰這些事有兩個原因:掙錢 + 裝逼。說實話,我做什麼事情,都需要驅動力,我覺得驅動力對我來說太重要了,這個驅動力可以很虛,但必須得有,而我又是一個俗人,能夠掙錢,是我最大的驅動力。當初我玩斗地主,驅動力是掙 Q 幣,後來我發現這些 Q 幣好像不能充 QQ會 員還是怎麼的,我就放棄不玩了。

這段折騰,我覺得讓我慢慢摸清了自己的方向,所以在這裏,對於大一或者大二的同學,如果你們有自己感興趣的,或許可以去嘗試一波。別人可能會說,這搞一下,那搞一下,會導致樣樣都會,但樣樣不精。而我的想法是:完全可以去嘗試,大學的學習,不存在精通這一說法,大一大二多嘗試,大三確定自己的方向來學習完全來得及。

大二

經過大一的洗禮,我覺得我的目標相對比較明確了,該玩的玩了,該折騰的折騰了,現在得好好規劃下自己的未來了。我了解到校招時大廠非常看重基礎,於是我大二就一直在學習基礎,例如計算機網絡,算法,操作系統這些,怎麼學?

得有動力啊,於是我報名了中級軟考,這算是我一個動力,這門考試會考整個大學涉及到的知識,於是為了搞定這個軟考,我大二把很多課程都學了,後面軟考也順利通過了。

中級軟考有必要參加嗎?答是隨便,這個證沒啥含金量的,我的目的是讓他督促我學習基礎知識,適合用來複習知識吧。

總之,大一,我學了很多數據結構和算法相關知識,大二,我學了很多基礎知識 + Java 的知識,並且大二比較專心,啥比賽,啥活動也沒參加,我說了,我喜歡做自己喜歡做的事,喜歡跟着自己的步伐走,別人的建議,我可能會參考下,但我無論做什麼事,都有自己的想法和思考。

有人也有問,有必要加入實驗室嗎?,我沒加過實驗室,但我想說的是,加與不加,都沒關係,重點是你想學習什麼,想成為什麼樣的人,實驗室,更多的是一種氣氛,但不一定適合你。要是我加入實驗室,我可能會把實驗室當作一個學習的場地,進而去學習自己喜歡的東西。

也有人問,那些基礎知識很枯燥,有沒有什麼辦法?,答是沒有,有些本來就枯燥,但枯燥的東西,往往是決定你我之間的區別,如果都很有趣,那大家肯定也都學,正是因為困難,所以才有了人與人之間的區別。

大三

其實我大三基本就處於複習 + 寫作 + 運營公眾號了,關於寫作和運營公眾號這個事,我不想說太多,因為我覺得我可以再寫兩篇文章來說這些事了。我只能說,運營一個公眾號,很不容易,我希望你,好好積累,好好準備秋招或者考研。我之所以能夠在大三寫出那麼多原創文章,一個很重要的原因就是,我大一大二積累了很多,所以大三就輸入的很少,基本處於輸出和折騰公眾號的過程。

大三的第二學期就是春招了,也就是找暑假實習,不過在大三,一般都面臨兩個選擇:讀研 還是 找工作?,實不相瞞,我從來沒想過讀研,讀研從來都沒在我的字典里。

為什麼?

之前也有挺多小夥伴問我為什麼沒讀研的,不過這個話題,我覺得我可以寫一篇關於我自己為何沒有讀研的原因了,如果你們感興趣,我後面寫一篇吧。

總的來說就是,大三處於複習的過程,之前我也曬過自己的思維導圖:當初為了有機會進大廠,帥地狠心複習了這9門核心知識,熬夜整理成思維導圖送給大家

不過說實話,其實我大三花在寫文章 + 弄公眾號的時間,佔比非常非常多,公眾號給我的學習狀態,帶來了很多負面影響,但幸運的是,我的公眾號做的不錯,給我帶來了不少收入,同時也幫忙了不少人,很多人都來感謝過我,這讓我很開心。

大三,能說的太多,但更多的都是非技術學習,我這裏就不說了,這篇文章字數也挺多了,有機會後面再說。

大四

大三暑假,也就是 2019 年 9月份,我就結束了自己的秋招了,很幸運,找到了自己喜歡的公司與城市,2019 年這一年,真的可以說是非常幸運,找到了工作 + 有了自己的公眾號,所以大四,過的很輕鬆,畢竟沒有找工作的壓力,所以大四上學期,都是在 玩 + 為工作準備 + 運營公眾號

到了大四第二學期,也就是 2020 年的 2 月份底,我就來公司實習了,一直實習到至今,關於實習到感悟,有機會再寫篇文章吧。

總結

說實話,我的大學,0 比賽 0 獎學金 0 證書,算是平凡但不平庸,但我始終都有一個明確目標支撐我去學習與探索,總的來說就是,我的大學做對了三件事,一是選擇了編程,二是學習正確的技能,三是入坑了寫作。

說實話,如果你們願意學習,你們也是可以做到的,至少,你們畢業后的薪資會對的起你們平時的學習。

我是帥地,一個即將畢業,步入社會大學的學生,希望在未來,我們共同成長,也歡迎大家見證我的成長!

最後,獻上我備戰校招的思維導圖 + 提升內功的 PDF 吧

九大思維導圖助你拿到心儀的 offer

打開計算機網絡的思維導圖長這樣

由於鏈接容易失效,不方便更新,大家可以在我的微信公眾號帥地玩編程回復思維導圖,即可獲取九大思維導圖,相信一定可以在面試時助你一臂之力。

作者簡潔

作者:大家好,我是帥地,從大學、自學一路走來,深知算法計算機基礎知識的重要性,所以申請了一個微星公眾號『帥地玩編程』,專業於寫這些底層知識,提升我們的內功,帥地期待你的關注,和我一起學習。 轉載說明:未獲得授權,禁止轉載

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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  ?

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

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

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

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

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

日產瞄準中國電動車市場 目標拿下 20% 市占

  日本汽車大廠日產汽車(Nissan)10 日宣佈,旗下中國大陸合資公司東風汽車有限公司的乘用車部門「東風日產乘用車公司(以下稱東風日產)」自 10 日起將在大陸開賣東風日產自有品牌電動車「Venucia e30」,售價為 26 萬 7,800 元人民幣,目標為在 2018 年於中國電動車市場拿下 20% 市佔率。   Venucia e30 是以日產於 2010 年在日本開賣的電動車「Leaf」的車台、技術為根基,由日產與東風日產所攜手研發的車款,而日產也將成為第一家進軍中國電動車市場的日系車廠。   Venucia e30 初期將在北京、上海、廣州、深圳、大連、武漢、天津、鄭州和杭洲等 9 個都市販售,並計劃在 2015 年將販售區域擴及至中國全國。   據日本媒體共同通信指出,Venucia e30 約 4 小時可充飽電、充飽電狀態下的行駛距離為 175km;Venucia e30 將在廣州生產、2018 年銷售目標為 5 萬台。     (圖片來源:)

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

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維修中心