如何解決“呈現控件時出錯”的問題

如何解決“呈現控件時出錯”的問題

作者: 玩轉C科技.NET 來源: 博客園 發布時間: 2011-03-15 11:52 閱讀: 1555 次 推薦: 0 原文鏈接 [收藏]
摘要:在考慮控件呈現的時候,為了避免類似異常的發生,我們應該考慮該控件在設計時能夠獲取足夠的資源,對于未能獲取資源的項,我們應該顯式將其區分。

  Webpart部署后在SPD中打開顯示呈現控件時出錯,后查到這篇文章,主要問題是在CreateChildControls中調用了this.page.Header,在設計視圖時,該對象可能還不存在,以后編碼時要注意在CreateChildControls盡量不調用數據庫/文件系統讀取、變量未附初值、調用了類似Page的屬性如Session,Page.Request.QueryString等。

  在制作控件的時候容易遇到呈現控件時出錯發生了未處理的異常。未將對象引用設置到對象的實例。這樣的錯誤,如下圖:(也有可能僅僅只是因為未設置runat="server"標簽而導致該錯誤,請先檢查該項。)

ControlErrorOnRender

  但是在編譯時/運行時也都是沒有錯誤的。

  分析

  如圖所示的情況我們稱之為設計時以區別于編譯時/運行時。

  設計時:在ASP.NET環境中也就是當我們在Visual Studio中使用網頁設計器進行編輯的時候。直接的理解就是在ASPX頁面切換到設計的時候。

  編譯時:直接的理解就是當你進行編譯的時候,通常這個時候的錯誤是由類型檢查,參數匹配等顯式可直接通過語法約束所限制的錯誤。

  運行時:直接的理解就是當你進行預覽/運行的時候。通常這個時候的錯誤則是由具體的異常,邏輯錯誤所組成的。

  讓我們分析控件在設計時的表現,我們的控件在設計時的時候,VS智能地模擬輸出控件在運行時的樣貌,控件的呈現通過了一定順序的方法,并最終形成了當前輸出。按照標準,我們應該是在Render或RenderContents中對控件進行了輸出的操作(事實上其他也是可以,但我們通常也不那么做,或者說更多的呈現控件時出錯的異常主要來自于Render或RenderContents)。

  從錯誤的提示未將對象引用設置到對象的實例。從這一句話來看,也就是說,有一個或者以上的對象的實例在沒有賦初值的情況下就被使用了。

  讓我們窺視一下我們的代碼:

protected override void RenderContents(HtmlTextWriter writer)
{
UpButton.Text = Page.Server.HtmlDecode(UpButton.Text);
DownButton.Text = Page.Server.HtmlDecode(DownButton.Text);
base.RenderContents(writer);
}

  因為該控件在設計的時候需要有一個向上的按鈕和一個向下的按鈕,分別需要用兩個特殊的標點符號向上和向下,而這兩個符號需要通過設置如下所示的代碼編號才可以正確地被瀏覽器呈現:

private string upButtonText = "and;";
private string downButtonText = "or;";

  而這兩個符號在呈現前卻會被頁面進行一個HtmlEncode方法編譯后再輸出,而這兩個特殊的標志卻只能通過直接輸出的方式進行呈現,也就是說在HtmlEncode之后只能將該特殊標志以文本的形式輸出and; or;而不會輸出向上和向下的箭頭。這時候我們需要引入它的反向方法Page.Server.HtmlDecode進行解碼,注意到這里我們使用了Page實例,該實例只有在頁面真實存在的情況下才為非空,否則后續的操作將是對null的操作,而這樣的操作將會顯示未將對象引用設置到對象的實例。這樣的錯誤。

  設計時:我們剛才提到了,設計時僅僅只是模擬頁面呈現的過程,而頁面事實上是不存在的。因此在這個時候Page對象的實例將為空,后續的調用將引發異常。

  假設說我們只有這個方法用于處理當前所需要的行為,那么我們在調用Page的時候必將導致null的對象并致使后續操作發生異常。這個時候我們引入設計模式這個概念(非DesignPattern而是DesignMode),DesignMode是由Control類的一個受保護的(protected)屬性,它獲取一個值,用以指示該組件當前是否處于設計模式。這里的設計模式也就相當于設計時的概念。

  因此我們可以將代碼改造為如下形式:

protected override void RenderContents(HtmlTextWriter writer)
{
if (!DesignMode)
{
UpButton.Text = Page.Server.HtmlDecode(UpButton.Text);
DownButton.Text = Page.Server.HtmlDecode(DownButton.Text);
}
base.RenderContents(writer);
}

  這樣只有在非設計時的時候,我們才引入Page對象的實例,因此設計時的異常將迎刃而解。

  總結

  因此在設計控件的時候,特別是在考慮控件呈現的時候,為了避免類似異常的發生,我們應該考慮該控件在設計時能夠獲取足夠的資源,對于未能獲取資源的項,我們應該顯式將其區分(如上代碼中使用DesignMode來判斷是否為設計時要執行的代碼)。

  擴展

  剛才我們所見到的情形可以歸結為在設計時無法引用具體實例所導致的異常,類似該異常的還會有數據庫/文件系統讀取、變量未附初值、調用了類似Page的屬性如Session,Page.Request.QueryString等。在頁面設計的時候由于以上部分方案特別是調用到Page的相關方法的由于頁面總是會存在,因此我們不會經常看到它們出異常,在設計控件的時候我們更應該注意。

0
0

請先登錄 提交中…

標簽:.NET Sharepoint Webpart 控件

文章列表

VS2010 測試功能之旅:編碼的UI測試(4)-通過編寫測試代碼 方式建立UI測試(上)

VS2010 測試功能之旅:編碼的UI測試(4)-通過編寫測試代碼的方式建立UI測試(上)

作者: RealDigit 來源: 博客園 發布時間: 2011-03-15 11:27 閱讀: 1606 次 推薦: 0 原文鏈接 [收藏]

  回顧

  在之前的入門篇系列中,分別介紹了一個簡單的示例, 操作動作的錄制原理通過修改UIMap.UItest文件控制操作動作代碼的生成對象的識別原理。接下來正式進入我們UI測試的進階篇,在這一章,將講述如何初步通過自己編寫代碼的方式來建立UI測試。

  示例程序

  一個系統的基本功能是增,刪,改,查,其中增和改界面基本一樣,刪就幾乎是一個按鈕的事,所以我做了一個程序示例(下載點我),擁有增和查兩個功能,之后的操作都將會在這個示例之上進行:

  系統主窗口:

  該系統擁有兩個功能,“添加用戶”和“查詢用戶”,點擊添加用戶后,進入添加用戶子窗體:

  這里添加用戶的時候根據情況會出現以下幾個提示框:

  “用戶名不能為空”

  “已有重名用戶”

  “備注不能為空”

  “添加成功!”

  如果在之前的主窗口,點擊查詢用戶,則進入查詢用戶子窗體。

  注:系統默認自帶了5個用戶TestUser1, TestUser2, TestUser3, TestUser4, TestUser5。

  這個窗體不會彈任何提示框,默認進入窗體時,DataGridView里面沒有加載數據,現在進行一個說明:

  查詢條件-用戶名:表示是否按用戶名查詢(非模糊查詢),如果不輸入,默認為不按其查詢。

  查詢條件-用戶類型:有三個選項“所有”,“管理員”,“一般用戶”。

  查詢條件-日期:表示是否按日期查詢,如果勾上了日期CheckBox,則旁邊的DateTimePicker會啟用,然后選擇一個具體的日期。

  按鈕-查詢:就會按以上條件查詢。

  按鈕-重置:用戶名清空,用戶類型變成所有,日期取消勾選。

  文本框-用戶備注:當查詢出數據以后,每選擇DataGridView里面中的一行數據,用戶備注TextBox會自動加載當前行的用戶備注。

  因為篇幅的關系,這里仍然分為上下兩部分,上部分介紹添加用戶窗體,下部分介紹查詢用戶窗體和測試之間的銜接。

  如何設計測試

  首先從前面的分析中,就可以得出添加用戶實際上是檢測是否有那些反例的彈出框彈出,然后正確添加用戶,這里設計了一些檢查點。

步驟序號 操作步驟 檢查點
1 運行主程序exe 檢測系統主窗口是否彈出
2 點擊添加用戶 檢測添加用戶子窗口是否彈出
3 輸入用戶名為空,用戶類型選擇“一般用戶”,備注為空 檢測是否彈出“用戶名不能為空”
4 輸入用戶名為“TestUser1”(系統默認就已有該用戶),用戶類型選擇“一般用戶”,備注為空 檢測是否彈出“已有重名用戶”
5 輸入用戶名為“TestUser6”,用戶類型選擇“一般用戶”,備注為空 檢測是否彈出“備注不能為空”
6 輸入用戶名為“TestUser6”,用戶類型選擇“一般用戶”,備注為“Test” 檢測是否彈出“添加成功!”
7 點擊取消按鈕,并退出主窗體 檢測是否退出添加用戶子窗體和主窗體

  接下來要做的工作就很輕松,我們要將以上的檢查點轉換為代碼。

  對測試進行編碼

  實際上,很多自動化測試項目在編寫的時候都是采用邊錄制邊編寫的方法來進行的,比如復雜的操作可以先錄制下來,然后手工去改某些步驟,這里我們將采用這種方法。

  我們需要新建一個項目,然后在添加一個編碼的UI測試映射,命名為AddUserUIMap.uitest,建立之后,錄制生成器會自動彈出,這個時候,我們什么也不做,直接點擊“生成代碼”,這樣VS2010就會自動生成AddUserUIMap.cs文件和AddUserUIMap.Designer.cs文件,在第二章(下)已經提到,自定義代碼可以編寫到.cs文件下,因為這里不會被覆蓋。

  實現步驟1

  為了實現第一步檢查點,首先我們需要捕獲主窗體對象,首先我們需要打開示例程序,然后點擊錄制生成器的準星。

  從點擊準星的那一刻起,按住鼠標不放,將鼠標挪動到主窗體直到主窗體被藍色框選中,這個時候便可以松開鼠標。

  之后我們可以看到對象庫中識別了該對象,現在點擊對象庫上面的“添加”圖標,就可以將這個對象正式加入對象庫:

  然后選擇錄制生成器的生成代碼。

  之后對象識別代碼就生成在了AddUserUIMap.Designer.cs。

  之后我們就可以進入AddUserUIMap.cs(注,這里是.cs,不是.Designer.cs),實現我們第一個步驟的代碼Step1_LoginSystem()。

public void Step1_LoginSystem()
{
//操作步驟:假設程序在D盤,這句的作用是加載程序
ApplicationUnderTest.Launch(@"D:\TestDemo.exe");

//檢查點:this.UI系統主窗口Window.WaitForControlExist(6000)的作用為,最多花6秒的時間等待UI系統主窗口Window出現,如果沒有出現,返回false,如果出現了,則返回true
Assert.IsTrue(this.UI系統主窗口Window.WaitForControlExist(6000), "運行主程序exe,檢測系統主窗口彈出失敗");
}

  實現步驟2

  第一步就完成了,接下來實現第二步Step2_ClickAddUser(),因為這里大家可能覺得錄制生成的方式更方便,這里我們可以采用錄制的方式先生成代碼,之后再將.Designer.cs生成的代碼遷移到.cs文件下。

  首先打開錄制生成器的錄制。

  然后在菜單中選擇“添加用戶”。

  然后停止錄制,點擊生成代碼,生成Step2_ClickAddUser()。

  生成完畢,我們可以看到在.Designer.cs下生成了如下代碼。

public void Step2_ClickAddUser()
{
#region Variable Declarations
WinMenuItem uI添加用戶MenuItem = this.UI系統主窗口Window.UIMenuStrip1MenuBar.UI用戶管理MenuItem.UI添加用戶MenuItem;
#endregion

// Click '用戶管理' -> '添加用戶' menu item
Mouse.Click(uI添加用戶MenuItem, new Point(41, 7));
}

  這里我們把這些代碼從.Designer.cs剪切到.cs文件下,然后刪除AddUserUIMap.uitest文件下<ExecuteActions>節點中的所有內容,因為根據操作動作的錄制原理可以了解到,.Designer.cs會根據.uitest文件生成代碼,所以需要清空這個節點。

  當然這里僅僅是操作步驟實現了,但我們還要驗證“添加用戶”窗口是否彈出,采用和第一步相同的方式,將添加用戶這個窗口添加到對象庫,然后生成識別代碼。

  之后就可以將Step2_ClickAddUser()的檢查點也實現了,具體代碼如下。

public void Step2_ClickAddUser()
{
//操作步驟:在菜單中選擇添加用戶
WinMenuItem uI添加用戶MenuItem = this.UI系統主窗口Window.UIMenuStrip1MenuBar.UI用戶管理MenuItem.UI添加用戶MenuItem;
Mouse.Click(uI添加用戶MenuItem, new Point(1, 1));

//檢查點:檢查添加用戶子窗口是否彈出
Assert.IsTrue(this.UI添加用戶Window.WaitForControlExist(6000), "點擊添加用戶,檢測添加用戶子窗口彈出失敗");
}

  實現步驟3-6

  接下來,就是第3步到第6步的檢測,3到6步基本上就只有以下幾個動作。

  a.輸入用戶名

  b.選擇用戶類別

  c.填入備注

  d.點擊添加按鈕

  e.彈出提示框,驗證文本,點擊OK

  除了e步驟的“驗證文本”操作的彈出框不是一個對象,其他的操作的對象都是一樣的(OK這個button雖然屬于不同的彈出框,但VS2010默認識別時會把它當成同一個,神奇吧),所以這部分操作代碼是可以重用的,所以我們先打開錄制生成器,然后錄制以上的操作,并生成代碼,和之前一樣,這些代碼都會從.designer.cs文件下剪切到.cs文件。(如何解決e步驟的“驗證文本”操作的彈出框不是一個對象的問題,這個一會兒會介紹)。

public void RecordedMethod2()
{
#region Variable Declarations
WinEdit uITbx_UserNameEdit = this.UI添加用戶Window.UITbx_UserNameWindow.UITbx_UserNameEdit;
WinComboBox uI用戶類別ComboBox = this.UI添加用戶Window.UICbx_UserTypeWindow.UI用戶類別ComboBox;
WinEdit uITbx_MemoEdit = this.UI添加用戶Window.UITbx_MemoWindow.UITbx_MemoEdit;
WinButton uI添加Button = this.UI添加用戶Window.UI添加Window.UI添加Button;
WinButton uIOKButton = this.UIOKWindow.UIOKButton;
#endregion

// Type 'comeon' in 'Tbx_UserName' text box
uITbx_UserNameEdit.Text = this.RecordedMethod2Params.UITbx_UserNameEditText;

// Select '一般用戶' in '用戶類別:' combo box
uI用戶類別ComboBox.SelectedItem = this.RecordedMethod2Params.UI用戶類別ComboBoxSelectedItem;

// Type 'comeon' in 'Tbx_Memo' text box
uITbx_MemoEdit.Text = this.RecordedMethod2Params.UITbx_MemoEditText;

// Click '添加' button
Mouse.Click(uI添加Button, new Point(52, 17));

// Click 'OK' button
Mouse.Click(uIOKButton, new Point(24, 7));
}

  接下來就該解決e步驟的“驗證文本”操作的彈出框不是一個對象的問題了。

  在第一章:一個簡單的示例中,里面的彈出框是很難重用的,例如文本為“密碼錯誤”和文本為“登陸成功”的彈出框,VS2010識別時會默認當他是兩個對象,因此在對象庫里面也是兩個對象,但實際上,為了避免對象庫對象過多,完全可以把它當成一個對象,只是說文本不一樣,根據對象的識別原理,我們可以更改他的關鍵標識屬性,讓VS2010把所有的文本不同的彈出框都當成一個對象識別,然后在測試時把它的文本屬性用來比對就OK了。

  現在我們故意讓“用戶名不能為空”的彈出框彈出,然后捕獲上面的。

  然后可以看到對象庫添加了這兩個對象。

  然后我們將其保存,并生成代碼,之后關閉錄制生成器(一定要關閉),進入.uitest文件,尋找到對應的識別代碼。

<TopLevelWindow ControlType="Window" Id="UI用戶名不能為空Window" FriendlyName="用戶名不能為空" SpecialControlType="None" SessionId="920202">
<TechnologyName>MSAA</TechnologyName>
<WindowTitles>
<WindowTitle>用戶名不能為空</WindowTitle>
</WindowTitles>
<SearchConfigurations>
<SearchConfiguration>VisibleOnly</SearchConfiguration>
</SearchConfigurations>
<AndCondition Id="SearchCondition">
<PropertyCondition Name="Name">用戶名不能為空</PropertyCondition>
<PropertyCondition Name="ClassName">Static</PropertyCondition>
<PropertyCondition Name="ControlType">Window</PropertyCondition>
</AndCondition>
<SupportLevel>1</SupportLevel>
<Descendants>
<UIObject ControlType="Text" Id="UI用戶名不能為空Text" FriendlyName="用戶名不能為空" SpecialControlType="None">
<TechnologyName>MSAA</TechnologyName>
<WindowTitles>
<WindowTitle>用戶名不能為空</WindowTitle>
</WindowTitles>
<SearchConfigurations>
<SearchConfiguration>VisibleOnly</SearchConfiguration>
</SearchConfigurations>
<AndCondition Id="SearchCondition">
<PropertyCondition Name="Name">用戶名不能為空</PropertyCondition>
<PropertyCondition Name="ControlType">Text</PropertyCondition>
</AndCondition>
<SupportLevel>1</SupportLevel>
<Descendants />
</UIObject>
</Descendants>
</TopLevelWindow>

  現在我們將其進行修改,根據對象的識別原理,更改部分關鍵標識屬性,并刪掉一些意義不大的代碼,讓其能夠識別所有不同文本的彈出框。

  修改之后的代碼如下:

<TopLevelWindow ControlType="Window" Id="UI彈出框Window">
<TechnologyName>MSAA</TechnologyName>
<AndCondition Id="SearchCondition">
<!--這里刪去了自動生成的<PropertyCondition Name="Name">用戶名不能為空</PropertyCondition>-->
<PropertyCondition Name="ClassName">Static</PropertyCondition>
<PropertyCondition Name="ControlType">Window</PropertyCondition>
</AndCondition>
<SupportLevel>1</SupportLevel>
<Descendants>
<UIObject ControlType="Text" Id="UI彈出框Text">
<TechnologyName>MSAA</TechnologyName>
<AndCondition Id="SearchCondition">
<!--這里刪去了自動生成的<PropertyCondition Name="Name">用戶名不能為空</PropertyCondition>-->
<PropertyCondition Name="ControlType">Text</PropertyCondition>
</AndCondition>
<Descendants />
</UIObject>
</Descendants>
</TopLevelWindow>

  然后我們打開錄制生成器,查看修改過后的對象,可以看到連名字都變了,同時也能夠識別出所有彈出框對象。

  因為目前錄制生成器是從.uitest文件讀出來的,但是designer.cs的代碼還未生成,所以根據操作動作的錄制原理,我們還需要點擊一次“生成代碼”,然后真正起識別作用的代碼就會生成到.designer.cs文件下。

  以后我們就可以通過在代碼中“this.UI彈出框Window.UI彈出框Text ”來控制彈出框了。

  接下來刪掉<ExecuteActions>節點中的所有內容,并將之前錄制到的操作代碼從.Designer.cs文件剪切到.cs文件下并進行修改,一個增加4個方法Step3_AddUserWithNoName(),Step4_AddUserWithOverlapName(),Step5_AddUserWithNoMemo(),Step6_AddUserSuccess(),修改之后的代碼如下所示。

  特別注意:從step4開始檢查點前面都加了一句this.mUI彈出框Window = new UI彈出框Window();之所以這么加是因為上一個操作step3已經識別了“用戶名不能為空”彈出框,個人推測VS2010這時會把這個“UI彈出框Window”對象指向到“用戶名不能為空”彈出框的句柄,然后雖然關閉了“用戶名不能為空”彈出框,但句柄的值依然指向它,這就直接導致從step4開始的時候無法用對象“UI彈出框Window”來識別出“已有重名用戶”的彈出框,所以只好將這個對象重新實例化一次。除了彈出框,其他的窗體也有這樣的特點。

public void Step3_AddUserWithNoName()
{
//操作步驟
this.UI添加用戶Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "";
this.UI添加用戶Window.UICbx_UserTypeWindow.UI用戶類別ComboBox.SelectedItem = "一般用戶";
this.UI添加用戶Window.UITbx_MemoWindow.UITbx_MemoEdit.Text ="";
Mouse.Click(this.UI添加用戶Window.UI添加Window.UI添加Button, new Point(1, 1));

//檢查點:首先檢查3秒內是否有彈出框彈出,如果有,則檢查文本是否是"用戶名不能為空"
bool isPopUp = this.UI彈出框Window.UI彈出框Text.WaitForControlExist(3000);
Assert.IsTrue(isPopUp&&this.UI彈出框Window.UI彈出框Text.DisplayText == "用戶名不能為空", "輸入用戶名為空,檢測彈出“用戶名不能為空”失敗");
Mouse.Click(this.UIOKWindow.UIOKButton, new Point(1, 1));
}

public void Step4_AddUserWithOverlapName()
{
//操作步驟
this.UI添加用戶Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "TestUser1";
this.UI添加用戶Window.UICbx_UserTypeWindow.UI用戶類別ComboBox.SelectedItem = "一般用戶";
this.UI添加用戶Window.UITbx_MemoWindow.UITbx_MemoEdit.Text = "";
Mouse.Click(this.UI添加用戶Window.UI添加Window.UI添加Button, new Point(1, 1));

//檢查點:首先檢查3秒內是否有彈出框彈出,如果有,則檢查文本是否是"已有重名用戶"
this.mUI彈出框Window = new UI彈出框Window();
bool isPopUp = this.UI彈出框Window.UI彈出框Text.WaitForControlExist(3000);
Assert.IsTrue(isPopUp&&this.UI彈出框Window.UI彈出框Text.DisplayText == "已有重名用戶", "輸入重名用戶,檢測彈出“已有重名用戶”失敗");
Mouse.Click(this.UIOKWindow.UIOKButton, new Point(1, 1));
}

public void Step5_AddUserWithNoMemo()
{
//操作步驟
this.UI添加用戶Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "TestUser6";
this.UI添加用戶Window.UICbx_UserTypeWindow.UI用戶類別ComboBox.SelectedItem = "一般用戶";
this.UI添加用戶Window.UITbx_MemoWindow.UITbx_MemoEdit.Text = "";
Mouse.Click(this.UI添加用戶Window.UI添加Window.UI添加Button, new Point(1, 1));

//檢查點:首先檢查3秒內是否有彈出框彈出,如果有,則檢查文本是否是"備注不能為空"
this.mUI彈出框Window = new UI彈出框Window();
bool isPopUp = this.UI彈出框Window.UI彈出框Text.WaitForControlExist(3000);
Assert.IsTrue(isPopUp&&this.UI彈出框Window.UI彈出框Text.DisplayText == "備注不能為空", "輸入備注為空,檢測彈出“備注不能為空”失敗");
Mouse.Click(this.UIOKWindow.UIOKButton, new Point(1, 1));
}

public void Step6_AddUserSuccess()
{
//操作步驟
this.UI添加用戶Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "TestUser6";
this.UI添加用戶Window.UICbx_UserTypeWindow.UI用戶類別ComboBox.SelectedItem = "一般用戶";
this.UI添加用戶Window.UITbx_MemoWindow.UITbx_MemoEdit.Text = "Test";
Mouse.Click(this.UI添加用戶Window.UI添加Window.UI添加Button, new Point(1, 1));

//檢查點:首先檢查3秒內是否有彈出框彈出,如果有,則檢查文本是否是"添加成功!"
this.mUI彈出框Window = new UI彈出框Window();
bool isPopUp = this.UI彈出框Window.UI彈出框Text.WaitForControlExist(3000);
Assert.IsTrue(isPopUp&&this.UI彈出框Window.UI彈出框Text.DisplayText == "添加成功!", "輸入正確,檢測彈出“添加成功!”失敗");
Mouse.Click(this.UIOKWindow.UIOKButton, new Point(1, 1));
}

  實現步驟7

  現在就只差最后一步了,就是關閉兩個窗體,采用之前的方式,先錄制關閉操作,先讓操作代碼生成在Designer.cs,然后在把它剪切到.cs下,最后添加驗證邏輯,錄制就不詳細說明了,最終代碼如下:

public void Step7_CloseWindows()
{
//操作步驟
bool isClosed;
Mouse.Click(this.UI添加用戶Window.UI取消Window.UI取消Button, new Point(1, 1));
//和WaitForControlExist相反,這里是最長等待他3秒關閉,如果3秒內關閉返回true,否則為false
isClosed= this.UI添加用戶Window.WaitForControlNotExist(3000);
Mouse.Click(this.UI系統主窗口Window.UI系統主窗口TitleBar.UICloseButton, new Point(1, 1));
isClosed &= this.UI系統主窗口Window.WaitForControlNotExist(3000);

//檢查點
Assert.IsTrue(isClosed, "點擊退出,檢測是否退出添加用戶子窗體和主窗體失敗");
}

  總結

  在本章上部分,介紹了通過編碼的方式來建立UI測試,在這里首先實現了添加用戶窗體上的操作,相信大家在看完了以后,應該能夠對UI測試有一個更深的認識了,如果需要調用剛才所寫的方法,只需要再新建一個編碼的UI測試,例如將其命名為CodedUITest1.cs,然后在內部添加一個這樣的調用即可。

[TestMethod]
public void CodedUITestMethod1()
{
AddUserUIMapClasses.AddUserUIMap uimap = new AddUserUIMapClasses.AddUserUIMap();
uimap.Step1_LoginSystem();
uimap.Step2_ClickAddUser();
uimap.Step3_AddUserWithNoName();
uimap.Step4_AddUserWithOverlapName();
uimap.Step5_AddUserWithNoMemo();
uimap.Step6_AddUserSuccess();
uimap.Step7_CloseWindows();
}

  示例程序的下載:下載點我

  個人錄制的源代碼下載:下載點我

  下部分將介紹查詢用戶窗體的測試代碼的編寫,以及他們測試的關聯。

0
0

請先登錄 提交中…

標簽:VS2010 測試

文章列表

基于SQL Server 2008 Service Broker構建企業級消息系統

基于SQL Server 2008 Service Broker構建企業級消息系統

來源: InfoQ 發布時間: 2011-03-30 13:37 閱讀: 1578 次 推薦: 0 原文鏈接 [收藏]
摘要:Microsoft 在SQL Server 2005引入了服務代理 (Service Broker 簡稱SSB) 為技術支持代理設計模式和面向消息的中間件 (MOM) 的原則。

  1、引言

  Microsoft 在SQL Server 2005引入了服務代理 (Service Broker 簡稱SSB) 為技術支持代理設計模式和面向消息的中間件 (MOM) 的原則。Service Broker在SQL Server 2008上得到完善, SQL Server Service Broker 為消息和隊列應用程序提供 SQL Server 數據庫引擎本機支持。

  這使開發人員可以輕松地創建使用數據庫引擎組件在完全不同的數據庫之間進行通信的復雜應用程序。開發人員可以使用 Service Broker 輕松生成可靠的分布式應用程序。使用 Service Broker 的應用程序開發人員無需編寫復雜的內部通信和消息,即可跨多個數據庫分發數據工作負荷。因為 Service Broker 會處理會話上下文中的通信路徑,所以這就減少了開發和測試工作。同時還提高了性能。

  企業系統和網站系統都需要處理大量的郵件、短信等消息通知系統。在進行系統設計時,除了對安全、事務等問題給與足夠的重視外,性能也是一個不可避免的問題所在,必須充分地考慮訪問量、數據流量、服務器負荷的問題。解決性能的瓶頸,除了對硬件系統進行升級外,軟件設計的合理性尤為重要。對于一些實時性不是很高的模塊我們可以使用了消息隊列技術來完成異步處理,利用消息隊列臨時存放要操作的數據,將隊列的數據進行異步的處理。本文基于SQL Server 2008 Service Broker、WCF、Windows 服務以及調度框架Quartz.NET實現一個消息通知系統。

  2、消息隊列

  2.1 隊列在異步運作的架構中是非常常用的數據結構

  基于消息的應用程序的工作方式是提交一條消息,應用程序執行其工作。然后,再檢查看是否收到確認消息已得到處理的信息。如果你的應用程序充滿了待處理的請求,通常應該增加另外一條處理隊列來緩解系統的總體處理壓力。

  微軟消息隊列(MSMQ)提供一個開發這類應用程序的框架。它使得應用程序可以在不同種類的網絡間進行通信,并且需要保證消息傳送(guaranteed message delivery)、路由和可配置安全。過去20年來,我們對關系數據庫系統的依賴程度顯著增加。最初,存儲數據并對數據進行某種處理,是建立商業關系數據庫系統的主要目的。隨著關系數據庫系統的發展,其功能和復雜性的變化,它的主要用途已由單一數據存儲轉變為更加主流的商業智能目的、更加復雜的ETL處理、數據報告、數據通知;微軟認為,允許你在數據庫內建立基于消息的應用程序,這樣才有意義。

  Service Broker是SQL Server 2005中新添加的基礎程序,在SQL Server 2008上得到加強,主要用于在數據庫引擎內建立基于消息的應用程序。SQL Server Service Broker是以數據表來實現隊列,并提供標準的T-SQL操作方式,讓系統設計人員可以善用消息溝通的特色設計應用程序。Service Broker應用程序以松散連接的應用程序而開發,它具有高度可擴展性,并提供其它消息平臺所不具備的功能,如消息組協調和鎖定。這些應用程序充分支持事務,并能夠跨越數據庫實例和服務器。SQL Server 2008 Service Broker支持的消息可以達到2G,支持SQL的varbinary 和varbinary(max)數據類型,支持消息優先級,而且“饑餓機制”保障較低優先級的消息也有機會獲得發送。

  2.2 消息系統架構

  消息的整體架構上分為三部分,消息系統客戶端,消息隊列系統,消息隊列發送程序,序列圖如下:

客戶端準備好消息,通過消息客戶端接口發送到消息隊列系統,消息隊列發送程序定時輪詢獲取消息進行發送,發  送的過程中發生錯誤重新放入隊列,發送成功的隊列歸檔到消息數據庫。以郵件發送為例在具體的實現的流程如下:

  上述多個部分協作,共同完成消息的發送任務,在本實現方案總共有六個部分,以下對這幾個部分進行詳細描述。

  1、消息體MessageBase

  自定義消息體的好處很多,采用自己定義的格式可以節省通信的傳遞量等等,也是這個消息系統的消息合約。

  上面圖中我們可以看到我們定義了3種常見的消息類型:郵件、短信和RTX(騰訊通RTX是騰訊公司推出的企業級即時通信平臺),可以根據需要靈活的擴展企業消息的類型。

  2、客戶端組件

  客戶端組件負責驗證消息和將消息輸入消息隊列系統,為了支持在整個企業環境提供服務,采用WCF方式發布,采用TCP和SOAP方式發布,TCP方式的客戶端通過.NET組件包發布,另外通過SOAP方式發布的標準Web Service支持其他跨平臺(C++/Java/PHP/Python/Ruby)的調用,同時為調用進行服務的驗證,需要使用消息服務的業務系統首先需要在系統中注冊,獲得服務調用的appkey,通過SOAP Header進行傳遞,服務端依據系統的注冊信息(appkey和注冊的系統服務器IP)進行驗證。

  3、SQL Server 2008 Service Broker隊列系統

  SQL Server 2008 Service Broker支持會話優先級,可以支持1到10的10個優先級,為目標服務創建10個優先級,只有一個約定,但每個級別都有單獨的發起方服務。所有發起方服務都與一個中心目標服務通信。在系統的中分配了高(8)中(5)低(2)三個優先級,消息也有一個優先級高(1)中(0)低(-1),進入消息系統的優先級等于系統優先級+消息優先級,這樣就使用了1-9優先級,10優先級為系統保留優先級,這樣就可有效的利用Service Broker的優先級和控制業務系統對消息優先級的使用。

  4、消息處理器

  消息處理器從隊列中取出消息,進行發送處理,發送失敗的消息重新放回隊列,并增加重試次數計數,當重試計數超過最大的重試次數,進行歸檔處理,發送成功的消息進行歸檔處理。每個月的數據分表存儲,避免數據量過大的系統性能損耗。

  5、消息隊列調度器

  消息隊列的調度采用Windows 服務承載,使用Quartz.NET進行作業的調度。Quartz.NET是一個開源的作業調度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#寫成,項目地址是http://quartznet.sourceforge.net。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執行一個作業而創建簡單的或復雜的調度。

  它有很多特征,如:數據庫支持,集群,插件,支持cron-like表達式等等。 消息的處理器包裝成Quartz Job加入調度系統。通過添加一系列的消息發送Job來加強消息發送的擴展性。Quartz.net本身支持集群性部署,結合Service Broker的分布式架構和Quartz的分布式部署就可以達到系統擴展性。

0
0

請先登錄 提交中…

標簽:SQL Server 08 Service Broker 企業級消息系統 .NET

文章列表

用NuGet掌管你的Visual Studio擴展

用NuGet掌管你的Visual Studio擴展

作者: 擁有的都是恩典(宋歷) 來源: 博客園 發布時間: 2011-03-17 11:36 閱讀: 3427 次 推薦: 0 原文鏈接 [收藏]
摘要:對于NuGet大家可能還比較陌生,不過你讀完本文之后就會發現原來這樣的小工具可以讓你更方便的掌控Visual Studio擴展。

  如果你使用Visual Studio 2010,那么 NuGet 可以使你的生活更加美好。當你項目里要引用到的一些庫時候,比如JQuery 庫或者 NHibernate, NUnit, log4net 你就可以考慮使用NuGet。它可以輔助你安裝或者更新這些庫。

  當然我不得不繼續說下去從安裝到使用:

  NuGet是一個Visual Studio 的擴展, 首先你必須要安裝它:

  可以到官方網站: http://nuget.org/

  然后設置自動檢查更新

  進入 工具|選項,然后環境|擴展管理器 ,點擊 自動檢查更新,安裝的擴展。

Visual Studio Options - Automatically check for Package Updates

  當你有一個Visual Studio擴展安裝像NuGet,它并不不會去檢查是否有更新可用。 這樣你的擴展可能會過時。

  當有更新的時候你會在系統托盤處得到一個通知:

New extension updates are available

  獲取NuGet程序包資源管理器

  這是一個很好的工具,可以讓我們更好使用 NuGet 點擊這里安裝.

  你可以所有的細節,元數據和文件內容。

NuGet Package Explorer - EFCodeFirst.SqlServerCompact.0.8.8482.1

  你可以直接點擊打開庫,或者在庫里面創建新的文件

image

  你還可以直接從NuGet發布你的修改

Publish package

  你可以訪問 nuget.org 瀏覽更多的相關信息

ScreenShot005

0
0

請先登錄 提交中…

標簽:NuGet Visual Studio 擴展 .NET

文章列表

Silverlight 2.5D RPG游戲技巧與特效處理:(六)流光追影

Silverlight 2.5D RPG游戲技巧與特效處理:(六)流光追影

作者: 深藍色右手 來源: 博客園 發布時間: 2011-03-23 10:33 閱讀: 1030 次 推薦: 0 原文鏈接 [收藏]

  依稀記得《奇跡》里為了讓裝備炫酷“流光”而砸鍋賣鐵;仍舊迷戀每次的跳躍、沖刺、特寫所帶來的動態“追影”。歲月流淌,讓無數玩家無論花費多少時間與金錢都無怨無悔,依舊那天地合一之特性裝備;手握幻象殘光之溢彩神器,踏著御風而行的隨影擦肩而過,陶醉的不僅僅是自己,亦絕非寂寞…

  “流光追影” 效果不僅提升了玩家對于裝備品質的不懈追求,同時在趣味性及耐玩性方面都是優秀網游所必備的要素之一;事實也證明了擁有華麗的“流光追影”裝備效果的游戲業績往往都很不錯,比如基于逐幀手繪的《地下城與勇士》及名作續集《萬王之王3》

  當然,“流光追影”特效在游戲中的應用非常廣泛,除了武器和身體等部位會用到外,其他還有很多場合比如描述物體的運動軌跡以及特寫的慢動作回放等均可用之來修飾以達到提升視覺體驗的目的:

  “流光追影”的實現方式根據需求的不同而顯得多種多樣。本節中,我同樣還是以軟實現的方式為本系列游戲Demo中的主角添置了漂亮而動感的追影與流光效果。首先以追影為例,實現方式與真實影子類似,通過實時復制一個角色當前的實體鏡象并進行透明度逐減動畫而實現;其中我們可以插入一個控制動畫速率的參數以起到調節追影數量的作用,同時也可以增加動態顏色調節,這樣根據角色裝備的顏色以及場景環境的不同,我們可以實時的調整追影顏色使之于游戲整體密切交融:

///<summary>
/// 顯示追影
///</summary>
///<param name="role">所修飾角色</param>
///<param name="equipType">所參照的裝備</param>
///<param name="color">顏色</param>
///<param name="coefficient">系數</param>
publicvoid ShowChasingShadow(Hero role, EquipTypes equipType, Color color, double coefficient) {
  WriteableBitmap writeableBitmap =new WriteableBitmap((int)role.OverallSize.X, (int)role.OverallSize.Y);
  writeableBitmap.Render(role.EquipEntity(equipType), null);
  writeableBitmap.Invalidate();
  Rectangle rectangle =new Rectangle() { Width = role.OverallSize.X, Height = role.OverallSize.Y, Fill =new SolidColorBrush(color) };
  rectangle.OpacityMask =new ImageBrush() { ImageSource = writeableBitmap };
  writeableBitmap =new WriteableBitmap((int)role.OverallSize.X, (int)role.OverallSize.Y);
  writeableBitmap.Render(rectangle, null);
  writeableBitmap.Invalidate();
  EntityObject blurShadow =new EntityObject() {
  ImageSource = writeableBitmap,
  Center = role.Center,
  Position = role.Position,
  Z = role.Z -20
  };
  space.Children.Add(blurShadow);
  Storyboard storyboard =new Storyboard();
  DoubleAnimation doubleAnimation =new DoubleAnimation() {
  From =0.7,
  To =0,
  Duration = TimeSpan.FromMilliseconds(role.HeartInterval * coefficient),
  };
  Storyboard.SetTarget(doubleAnimation, blurShadow);
  Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath(“Opacity"));
  storyboard.Children.Add(doubleAnimation);
  EventHandler handler =null;
  storyboard.Completed += handler =delegate {
  storyboard.Completed -= handler;
  storyboard.Stop();
  space.Children.Remove(blurShadow);
  };
  storyboard.Begin();
}

  接下來是制作流光。它3D游戲中更為多見,通常用粒子渲染實現。當然了,2.5D游戲畢竟還是以逐幀為基礎的平面游戲,在連貫性及層次感方面我們無法做到最真實。不過一樣可以借鑒追影的方案,以拷貝一個角色實體對其進行微錯時閃爍處理還是能達到不錯效果的,代碼與追影的相類似,這里就不羅列了。

  實際游戲設計中,英雄角色的實體通常會劃分為多個層次:鎧甲、武器、頭飾、坐騎等等,不同裝備根據狀態進行流光呈現以展示裝備的稀有或突出地位。于是之前的紙娃娃系統為其實現奠定了充分的基礎,我們完全可以做到基于任何角色部位的裝備進行分層的流光處理,就整體效果而言更接近客戶端網游:

  其實,在我的腦海中還有非常多相當有趣的各式“流光追影”呈現方式,苦于無相關素材而無法一一列舉給大家。僅僅10來行的代碼,描繪的流光溢彩于形于色,或許這就是Silverlight所給我們創造的高效率游戲開發工程模式。

  最后還需說明一下,當前Demo所實現的光影效果中的真實陰影、追影、流光等用到的都是暫時無法利用GPU加速的Rectangle、WriteableBitmap、以及Opacity等對象及屬性,均乃性能消耗之物。可想而知,編寫它們的目的暫時僅以向大家展示Silverlight在特效制作方面所能達到的高度而目前還無法用性能來衡量;但這并非就意味著這些都將成為華而不實之物;就好比在少量應用(比如只針對主角)的場合中,使用它同樣可以起到視覺沖擊與性能一并兼顧的效果;同時,本節也是為了Silverlight 5 及更多優秀的后續版本埋下伏筆,從Silverilght 5目前已知的新特性中我們不難看出Silverlight正朝著更偉大的目標前行著,究其一天我們的游戲理想必將實現!

  本節源碼下載地址:Demo5.rar

  在線演示地址:http://silverfuture.cn

0
0

請先登錄 提交中…

標簽:Silverlight

文章列表

微服務實戰(一):微服務架構的優勢與不足

微服務實戰(一):微服務架構的優勢與不足

作者: Chris Richardson 發布時間: 2015-05-28 19:58 閱讀: 183744 次 推薦: 44 原文鏈接 [收藏]
摘要:本文來自Nginx官方博客,是微服務系列文章的第一篇,主要探討了傳統的單體式應用的不足,以及微服務架構的優勢與挑戰。正如作者所說,微服務架構更適合用于構建復雜的應用,盡管它也有自己的不足。

  英文原文:Introduction to Microservices

  這篇文章作者是Chris Richardson,他是早期基于Java的Amazonite EC2 PaaS平臺CloudFoundry.com的創始人。現在他為企業提供如何開發和部署應用的咨詢服務。他也經常在http://microservices.io上發表有關微服務的文章。

  微服務正在博客、社交媒體討論組和會議演講中獲得越來越多的關注,在Gartner的2014 Hype Cycle上它的排名非常靠前。同時,軟件社區中也有不少持懷疑論者,認為微服務不是什么新東西。Naysayers認為這就是SOA架構的重新包裝。然而,盡管存在著不同的爭論,微服務架構模式卻正在為敏捷部署以及復雜企業應用實施提供巨大的幫助。

  這篇博客是關于如何設計、開發和部署微服務的七篇系列文章中的第一篇。讀者將會從中學到方法,并且和單體式架構模式(譯者注:本文中會將 Monolithic翻譯為單體)進行對比。這一系列文章將描述微服務架構中不同元素。你將了解到微服務架構模式的優缺點,以便決定是否更好的將微服務架構應用到自己的項目中,以及如何應用這一模式。

  首先我們看看為什么要考慮使用微服務。

  開發單體式應用

  假設你正準備開發一款與Uber和Hailo競爭的出租車調度軟件,經過初步會議和需求分析,你可能會手動或者使用基于Rails、Spring Boot、Play或者Maven的生成器開始這個新項目,它的六邊形架構是模塊化的 ,架構圖如下:

1.png
  應用核心是業務邏輯,由定義服務、域對象和事件的模塊完成。圍繞著核心的是與外界打交道的適配器。適配器包括數據庫訪問組件、生產和處理消息的消息組件,以及提供API或者UI訪問支持的web模塊等。

  盡管也是模塊化邏輯,但是最終它還是會打包并部署為單體式應用。具體的格式依賴于應用語言和框架。例如,許多Java應用會被打包為WAR格式,部署在Tomcat或者Jetty上,而另外一些Java應用會被打包成自包含的JAR格式,同樣,Rails和Node.js會被打包成層級目錄。

  這種應用開發風格很常見,因為IDE和其它工具都擅長開發一個簡單應用,這類應用也很易于調試,只需要簡單運行此應用,用Selenium鏈接UI就可以完成端到端測試。單體式應用也易于部署,只需要把打包應用拷貝到服務器端,通過在負載均衡器后端運行多個拷貝就可以輕松實現應用擴展。在早期這類應用運行的很好。

  單體式應用的不足

  不幸的是,這種簡單方法卻有很大的局限性。一個簡單的應用會隨著時間推移逐漸變大。在每次的sprint中,開發團隊都會面對新“故事”,然后開發許多新代碼。幾年后,這個小而簡單的應用會變成了一個巨大的怪物。這兒有一個例子,我最近和一個開發者討論,他正在寫一個工具,用來分析他們一個擁有數百萬行代碼的應用中JAR文件之間的依賴關系。我很確信這個代碼正是很多開發者經過多年努力開發出來的一個怪物。

  一旦你的應用變成一個又大又復雜的怪物,那開發團隊肯定很痛苦。敏捷開發和部署舉步維艱,其中最主要問題就是這個應用太復雜,以至于任何單個開發者都不可能搞懂它。因此,修正bug和正確的添加新功能變的非常困難,并且很耗時。另外,團隊士氣也會走下坡路。如果代碼難于理解,就不可能被正確的修改。最終會走向巨大的、不可理解的泥潭。

  單體式應用也會降低開發速度。應用越大,啟動時間會越長。比如,最近的一個調查表明,有時候應用的啟動時間居然超過了12分鐘。我還聽說某些應用需要40分鐘啟動時間。如果開發者需要經常重啟應用,那么大部分時間就要在等待中渡過,生產效率受到極大影響。

  另外,復雜而巨大的單體式應用也不利于持續性開發。今天,SaaS應用常態就是每天會改變很多次,而這對于單體式應用模式非常困難。另外,這種變化帶來的影響并沒有很好的被理解,所以不得不做很多手工測試。那么接下來,持續部署也會很艱難。

  單體式應用在不同模塊發生資源沖突時,擴展將會非常困難。比如,一個模塊完成一個CPU敏感邏輯,應該部署在AWS EC2 Compute Optimized instances,而另外一個內存數據庫模塊更合適于EC2 Memory-optimized instances。然而,由于這些模塊部署在一起,因此不得不在硬件選擇上做一個妥協。

  單體式應用另外一個問題是可靠性。因為所有模塊都運行在一個進程中,任何一個模塊中的一個bug,比如內存泄露,將會有可能弄垮整個進程。除此之外,因為所有應用實例都是唯一的,這個bug將會影響到整個應用的可靠性。

  最后,單體式應用使得采用新架構和語言非常困難。比如,設想你有兩百萬行采用XYZ框架寫的代碼。如果想改成ABC框架,無論是時間還是成本都是非常昂貴的,即使ABC框架更好。因此,這是一個無法逾越的鴻溝。你不得不在最初選擇面前低頭。

  總結一下:一開始你有一個很成功的關鍵業務應用,后來就變成了一個巨大的,無法理解的怪物。因為采用過時的,效率低的技術,使得雇傭有潛力的開發者很困難。應用無法擴展,可靠性很低,最終,敏捷性開發和部署變的無法完成。

  那么如何應對呢?

  微處理架構——處理復雜事物

  許多公司,比如Amazon、eBay和NetFlix,通過采用微處理結構模式解決了上述問題。其思路不是開發一個巨大的單體式的應用,而是將應用分解為小的、互相連接的微服務。

  一個微服務一般完成某個特定的功能,比如下單管理、客戶管理等等。每一個微服務都是微型六角形應用,都有自己的業務邏輯和適配器。一些微服務還會發布API給其它微服務和應用客戶端使用。其它微服務完成一個Web UI,運行時,每一個實例可能是一個云VM或者是Docker容器。

  比如,一個前面描述系統可能的分解如下:

2.png
  每一個應用功能區都使用微服務完成,另外,Web應用會被拆分成一系列簡單的Web應用(比如一個對乘客,一個對出租車駕駛員)。這樣的拆分對于不同用戶、設備和特殊應用場景部署都更容易。

  每一個后臺服務開放一個REST API,許多服務本身也采用了其它服務提供的API。比如,駕駛員管理使用了告知駕駛員一個潛在需求的通知服務。UI服務激活其它服務來更新Web頁面。所有服務都是采用異步的,基于消息的通訊。微服務內部機制將會在后續系列中討論。

  一些REST API也對乘客和駕駛員采用的移動應用開放。這些應用并不直接訪問后臺服務,而是通過API Gateway來傳遞中間消息。API Gateway負責負載均衡、緩存、訪問控制、API 計費監控等等任務,可以通過NGINX方便實現,后續文章將會介紹到API Gateway。

3.png
  ·微服務架構模式在上圖中對應于代表可擴展Scale Cube的Y軸,這是一個在《The Art of Scalability》書中描述過的三維擴展模型。另外兩個可擴展軸,X軸由負載均衡器后端運行的多個應用副本組成,Z軸是將需求路由到相關服務。

  應用基本可以用以上三個維度來表示,Y軸代表將應用分解為微服務。運行時,X軸代表運行多個隱藏在負載均衡器之后的實例,提供吞吐能力。一些應用可能還是用Z軸將服務分區。下面的圖演示行程管理服務如何部署在運行于AWS EC2上的Docker上。

4.png
  運行時,行程管理服務由多個服務實例構成。每一個服務實例都是一個Docker容器。為了保證高可用,這些容器一般都運行在多個云VM上。服務實例前是一層諸如NGINX的負載均衡器,他們負責在各個實例間分發請求。負載均衡器也同時處理其它請求,例如緩存、權限控制、API統計和監控。

  這種微服務架構模式深刻影響了應用和數據庫之間的關系,不像傳統多個服務共享一個數據庫,微服務架構每個服務都有自己的數據庫。另外,這種思路也影響到了企業級數據模式。同時,這種模式意味著多份數據,但是,如果你想獲得微服務帶來的好處,每個服務獨有一個數據庫是必須的,因為這種架構需要這種松耦合。下面的圖演示示例應用數據庫架構。

5.png
  每種服務都有自己的數據庫,另外,每種服務可以用更適合自己的數據庫類型,也被稱作多語言一致性架構。比如,駕駛員管理(發現哪個駕駛員更靠近乘客),必須使用支持地理信息查詢的數據庫。

  表面上看來,微服務架構模式有點像SOA,他們都由多個服務構成。但是,可以從另外一個角度看此問題,微服務架構模式是一個不包含Web服務(WS-)和ESB服務的SOA。微服務應用樂于采用簡單輕量級協議,比如REST,而不是WS-,在微服務內部避免使用ESB以及ESB類似功能。微服務架構模式也拒絕使用canonical schema等SOA概念。

  微服務架構的好處

  微服務架構模式有很多好處。首先,通過分解巨大單體式應用為多個服務方法解決了復雜性問題。在功能不變的情況下,應用被分解為多個可管理的分支或服務。每個服務都有一個用RPC-或者消息驅動API定義清楚的邊界。微服務架構模式給采用單體式編碼方式很難實現的功能提供了模塊化的解決方案,由此,單個服務很容易開發、理解和維護。

  第二,這種架構使得每個服務都可以有專門開發團隊來開發。開發者可以自由選擇開發技術,提供API服務。當然,許多公司試圖避免混亂,只提供某些技術選擇。然后,這種自由意味著開發者不需要被迫使用某項目開始時采用的過時技術,他們可以選擇現在的技術。甚至于,因為服務都是相對簡單,即使用現在技術重寫以前代碼也不是很困難的事情。

  第三,微服務架構模式是每個微服務獨立的部署。開發者不再需要協調其它服務部署對本服務的影響。這種改變可以加快部署速度。UI團隊可以采用AB測試,快速的部署變化。微服務架構模式使得持續化部署成為可能。

  最后,微服務架構模式使得每個服務獨立擴展。你可以根據每個服務的規模來部署滿足需求的規模。甚至于,你可以使用更適合于服務資源需求的硬件。比如,你可以在EC2 Compute Optimized instances上部署CPU敏感的服務,而在EC2 memory-optimized instances上部署內存數據庫。

  微服務架構的不足

  Fred Brooks在30年前寫道,“there are no silver bullets”,像任何其它科技一樣,微服務架構也有不足。其中一個跟他的名字類似,『微服務』強調了服務大小,實際上,有一些開發者鼓吹建立稍微大一些的,10-100 LOC服務組。盡管小服務更樂于被采用,但是不要忘了這只是終端的選擇而不是最終的目的。微服務的目的是有效的拆分應用,實現敏捷開發和部署。

  另外一個主要的不足是,微服務應用是分布式系統,由此會帶來固有的復雜性。開發者需要在RPC或者消息傳遞之間選擇并完成進程間通訊機制。更甚于,他們必須寫代碼來處理消息傳遞中速度過慢或者不可用等局部失效問題。當然這并不是什么難事,但相對于單體式應用中通過語言層級的方法或者進程調用,微服務下這種技術顯得更復雜一些。

  另外一個關于微服務的挑戰來自于分區的數據庫架構。商業交易中同時給多個業務分主體更新消息很普遍。這種交易對于單體式應用來說很容易,因為只有一個數據庫。在微服務架構應用中,需要更新不同服務所使用的不同的數據庫。使用分布式交易并不一定是好的選擇,不僅僅是因為CAP理論,還因為今天高擴展性的NoSQL數據庫和消息傳遞中間件并不支持這一需求。最終你不得不使用一個最終一致性的方法,從而對開發者提出了更高的要求和挑戰。

  測試一個基于微服務架構的應用也是很復雜的任務。比如,采用流行的Spring Boot架構,對一個單體式web應用,測試它的REST API,是很容易的事情。反過來,同樣的服務測試需要啟動和它有關的所有服務(至少需要這些服務的stubs)。再重申一次,不能低估了采用微服務架構帶來的復雜性。

  另外一個挑戰在于,微服務架構模式應用的改變將會波及多個服務。比如,假設你在完成一個案例,需要修改服務A、B、C,而A依賴B,B依賴C。在單體式應用中,你只需要改變相關模塊,整合變化,部署就好了。對比之下,微服務架構模式就需要考慮相關改變對不同服務的影響。比如,你需要更新服務C,然后是B,最后才是A,幸運的是,許多改變一般只影響一個服務,而需要協調多服務的改變很少。

  部署一個微服務應用也很復雜,一個分布式應用只需要簡單在復雜均衡器后面部署各自的服務器就好了。每個應用實例是需要配置諸如數據庫和消息中間件等基礎服務。相對比,一個微服務應用一般由大批服務構成。例如,根據Adrian Cockcroft,Hailo有160個不同服務構成,NetFlix有大約600個服務。每個服務都有多個實例。這就造成許多需要配置、部署、擴展和監控的部分,除此之外,你還需要完成一個服務發現機制(后續文章中發表),以用來發現與它通訊服務的地址(包括服務器地址和端口)。傳統的解決問題辦法不能用于解決這么復雜的問題。接續而來,成功部署一個微服務應用需要開發者有足夠的控制部署方法,并高度自動化。

  一種自動化方法是使用PaaS服務,例如Cloud Foundry。PaaS給開發者提供一個部署和管理微服務的簡單方法,它把所有這些問題都打包內置解決了。同時,配置PaaS的系統和網絡專家可以采用最佳實踐和策略來簡化這些問題。另外一個自動部署微服務應用的方法是開發對于你來說最基礎的PaaS系統。一個典型的開始點是使用一個集群化方案,比如配合Docker使用Mesos或者Kubernetes。后面的系列我們會看看如何基于軟件部署方法例如NGINX,可以方便的在微服務層面提供緩存、權限控制、API統計和監控。

  總結

  構建復雜的應用真的是非常困難。單體式的架構更適合輕量級的簡單應用。如果你用它來開發復雜應用,那真的會很糟糕。微服務架構模式可以用來構建復雜應用,當然,這種架構模型也有自己的缺點和挑戰。

  在后續的博客中,我會深入探索微服務架構模式,并討論諸如服務發現、服務部署選擇和如何分解一個分布式應用為多個服務的策略。

  待續。。。

44
0

請先登錄 提交中…

標簽:微服務 Docker 架構

文章列表

前端工程師的價值體現在哪里?

前端工程師的價值體現在哪里?

來源: CSDN 發布時間: 2012-10-12 11:50 閱讀: 5236 次 推薦: 17 原文鏈接 [收藏]

  這是一個很老的話題“前端工程師的價值體現在哪里?”。有人說:“前端工程師之于網站的價值猶如化妝師之于明星的價值。”一位好的Web前端開發工程師在知識體系上既要有廣度,又要有深度。當然,Web前端工程師并不是設計師,每天接觸最多的是代碼,代碼,還是代碼。對此,你是如何給自己定位的?你的價值是否能夠得到很好的體現?如今,舊話重提無非是想與開發者們共同探討下前端工程師的價值所在,希望對你有所感悟。

  一起來看下業內資深大牛對前端工程師是如何評價的:

  張克軍 – 豆瓣前端工程師:

  個人認為前端工程師正慢慢演變為產品工程師。Web App,響應性UI等以HTML5技術為基礎的開發將成為前端工程師的主要工作內容,解決產品跨平臺跨設備的實現問題。Javascript, HTML, CSS這些前端工程師熟悉的,多年使用的語言,作為開放標準將被各種平臺所支持。產品形態和數據的分離是形勢所趨。移動時代對產品形態多元化的要求雖然可以靠不同技術分別實現,但要付出巨大的成本。這也是HTML5這個2004年就提出來的標準,直到前兩年才火爆的原因。

  現階段的價值也很大。Web產品交互越來越復雜,用戶使用體驗和網站前端性能優化,這些都需要專業的前端工程師來解決。另外,在項目中還要彌補設計師在交互設計上的不足,前端工程師在開發過程中起著重要的承上啟下的作用。一兩個前端工程師就可以讓整個開發并行起來,讓設計到實現的轉換更順利。明智的公司應該貯備前端工程師資源。

  我不認為前端工程師和產品經理有什么關系。好的前端工程師一定會成為好的交互設計師。前端工程師對信息架構的理解應不亞于專業的交互設計師。

  張經緯 – 前端工程師:

  一、前端工程師所需要掌握的基本技能:

  • HTML/CSS
  • JavaScript
  • PHP/ASP.NET/或者其他廣泛應用在Web領域的編程語言
  • 美術、視覺

  二、前端工程師面向于:

  • 用戶
  • 瀏覽器
  • 數據接口

  三、那么前端工程師的價值體現在哪兒呢?

  1. 為簡化用戶使用提供技術支持(交互部分)
  2. 為多個瀏覽器兼容性提供支持
  3. 為提高用戶瀏覽速度(瀏覽器性能)提供支持
  4. 為跨平臺或者其他基于webkit或其他渲染引擎的應用提供支持
  5. 為展示數據提供支持(數據接口)

  元彥 – 云端工程師:

  關于前端攻城師的價值體現,我覺得主要取決于下面幾個方面:

  1. 與用戶最近,最愿意揣測用戶,是工程師中最了解用戶的

  2. 前端充滿創新

  3. 前端技能JavaScript、HTML、CSS….入門易,深入難

  4. 前后端交互方式多樣,適用場景不同,Ajax(Post、Get)、Comet(輪訓、長輪訓、永久幀、XHR流)、WebSocket

  5. 隨之瀏覽器的發展,很多技術方案開始偏向于前端

  6. 前端不僅僅是Desktop,而有Mobile、Pad、TV……

  高原 – Web工程師:

  作為前端工程師最核心的價值或者說是責任,就是將大伙的所有心血和努力最終要完美地呈現給用戶。在一個技術開發團隊中,無疑離用戶最近的人就是前端,其次是UI、UE和產品,然后是后端、DBA和系統工程師。

  如果說一個技術開發團隊就是一支足球隊的話,那么前端工程師無疑就是前鋒,他接到隊友們傾力傳到腳下的球,他責無旁貸,要做的就是必須將球準確無誤地打入對方的球門….. 他有兩點必須是非常清楚:1. 他必須清楚在對方球門與自己之間存在著哪些阻礙;2. 他必須清楚如何破除這些障礙將球直至門網…..他也應該要是所有隊員中對這兩點最為清楚的人。

  每個球隊都有自己的明星,可以是鋒線殺手、可以是中場戰車、可以是超級后衛、也可以是神奇門將、甚至可以是救火教練。而且相信球場上任何一個位置的優秀球員,都有可能在后場斷球長途盤帶奔襲射門,球進!但任何一支優秀的球隊都必須有兩類分工,前場球員想的是贏得比賽,后場球員想的是不能輸掉比賽,各司其職才能卓越。而作為一名稱職的前鋒,你必須是球隊里,進球最多、射門技術最好的那一個,否則你還有什么價值可言呢….

  不管技術實現的風潮如何變化,一個給用戶的交互界面要有人來實現,這是不辨的硬需求…. 除非以后人機的信息交互不依賴視覺了(直接靠意念鏈接),那時的前端就轉向只做信息的組織與表達形式的設計就好了,因此,前端的最終價值是對人機交互的設計與實現。

  李春平 – 百度研發工程師:

  應該來講有三條路,一個是向前走,一個是向后走,另外一個是一直做前端,深入下去。向前即是前面所說的往用戶體驗與交互設計甚至產品設計師上走,這是最能體現前端價值的了,即用戶體驗,大多人會往這方面走。

  向后走就是做Web開發,往數據庫和后臺開發方面走,不再區分前后端,大家深入產品的研發實現,這條路就是與軟件工程師融合的路,這是的價值就體現在對于業務功能的實現上。

  最后還有的是一直深入做前端開發,比如前端各類庫與框架的架構設計,W3C各種標準深入研究,對于JavaScript語言本身的研究,對瀏覽器的原理分析,對于網絡傳輸協議的原理分析等等,這條路要深入下去很不容易,因為涉及很多原理與根本性的東西,所以走的人也不是很多。

  當然了,還有一些徹底離開了前端甚至軟件開發,轉行專門做產品或者做業務運營。可能會因為有不錯的技術背景有一定的優勢呢。

  胡金埔 – 前端開發者:

  我覺得前端工程師需要分兩個方向來看各自的價值:

  第一個方向:讓用戶更便捷的獲取信息。這是大部門公司前端工程師應該努力做好的事情。拿到psd,產出線上的代碼,這個過程中的每個細節都值得你去用認真的態度做好。表單的各種交互,頁面不同元素(區域)間的信息交互,這都未必是普通ID可以給你指導好的,你需要依賴自己的技術能力和自己對用戶需求的感知去完成。

  這個方向的價值就體現為:信息獲取是否更加便捷?從而你的用戶量是否上漲了?在線預訂是否提高了?

  第二個方向:讓前端更加專業。這是一些研究院的大牛每天做的事情。閱讀規范,比較不同版本的區別,并思考引入新特性的意義(技術或商業)。關注行業的最新發展,找出一些創新點,如果可以的話,站在巨人的肩膀上,不盲目的造輪子。最后,不管是自己的創新還是自己的整理好的當前解決某個問題的最佳方案,都會給整個行業以及自己的公司的其他前端同學的工作帶來更多的支持。

  這個方向的價值更加的技術化,可能短時間無法直接量化,但一段時間后,會從提高的個人生產率,頁面交互的新模型等方面得到體現,甚至會導致新的產品。

  元亮 – 前端工程師:

  • 產品工程師 -Web產品APP化使得前端需要了解產品的設計和交互實現細節,從而使前端代碼結構合理 可擴展。
  • 跨平臺設備實現 -并不單單是各瀏覽器的跨平臺兼容了。現在的物理設備多樣,只要和用戶產生直接互動的實現工作都可以稱為前端。
  • 完美優雅實現交互和設計細節 -用最精簡的代碼和最小的代價還原交互和設計的細節,可用性和可訪問性的提升。
  • 用戶體驗和新技術的結合 -HTNL5的發展使得Web和移動應用有更優和更佳合理的實現方式。
  • 網站前端性能優化 -節省用戶成本,節省公司成本。
  • 可擴展的標準接口 -語義化的頁面使得網站無論從SEO角度和機器可讀性得到更大的提升,標準化可擴展的數據接口使得和后臺的聯系更佳無縫,同時也會大大提升開發效率。
  • 網站形象的業內PR -以最直觀可見的方式展示公司網站和公司形象。

  前端的需求

  • 需要理解產品的邏輯和形成的過程 - 希望參與開發產品頁面的相關人員參與到產品討論的階段,了解產品的需求。以及了解產品的未來的隱性需求!列席即可!
  • 需要了解交互細節 -希望參與開發產品頁面的相關人員參與到交互討論當中,理解交互和設計細節。使頁面結構合理和具有可擴展性!列席即可!如果合理化建議可以提出!
  • 需要了解數據接口 -理解產品相關模塊所需要的數據與相關技術人員溝通形成文檔。
  • 需要技術的積累和新技術的學習 -希望有團隊內的交流活動,頭腦風暴!學習了解最新的行業技術,參加業界的交流!
  • 需要成就感和滿足感。

  結束語:

  前端就是后臺實現和視覺表現的橋梁,是貫穿在整個產品開發過程的紐帶,起到承上啟下的作用,一個好的前端工程師他能夠很好理解產品經理對用戶體驗的要求,也能夠很好地理解后臺工程師對數據邏輯,或者程序邏輯進行分離的要求,并將這些要求轉化成前臺的開發工作。前端就是網站的門面,它的價值遠大于其他的客戶端開發。

  本文部分資料整理自知乎網

17
0

請先登錄 提交中…

標簽:前端工程師

文章列表

從商業角度探討API設計

從商業角度探討API設計

作者: Matt McLarty 來源: infoQ 發布時間: 2015-02-28 17:11 閱讀: 2953 次 推薦: 2 原文鏈接 [收藏]

  為Web設計、實現和維護API不僅僅是一項挑戰;對很多公司來說,這是一項勢在必行的任務。本系列將帶領讀者走過一段旅程,從為API確定業務用例到設計方法論,解決實現難題,并從長遠的角度看待在Web上維護公共API。沿途將會有對有影響力的人物的訪談,甚至還有API及相關主題的推薦閱讀清單。

  如今,API已經成為了每個重要信息技術趨勢的核心內容。移動設計、云計算、物聯網、大數據及社交網絡等應用都依賴于一個基于web的界面與它們的分布式組件進行連接,為全球范圍內的各個商業領域提供具有創新性和顛覆性的解決方法。智能電網(Smart grid)技術改變了能源行業的形態,聯網汽車(Connected Car)解決方案則被視為自動汽車行業中的關鍵因素,亞馬遜使得每個所接觸的行業都產生了具大變化。在所有這些例子中,API的使用既是催化劑,也是促成這一成果的主要力量。

  由于API對于商業的巨大影響,因此有關“API的商機”的各種文章也是層出不窮。在開放性的互聯網上,使用API作為一種外部頻道進行創新及盈利已經成為一種獨特的商業模型。在由Kin Lane所創建的API傳道(Evangelist)網站中可以找到關于這一話題非常全面的信息,Mehdi Medjaoui則在最近的一篇帖子中用精練的語言對此進行了總結。然后,在跨科技領域的API應用范圍內,開放式API模型僅僅表現出其實用性的冰山一角。實際上,Web API的主要能力還沒有從各種使用API實現的解決方案中被發掘出來。從這種意義上說,API的商機本身就是一種商業模式。

  本文將從商業角度對API進行全面的講解分析,無論它是否是開放式并且公開發布的。我會談到嘗試用API為你帶來商業價值的重要性、分析在其中應該使用的數據類型、并學習Aamzon及Twilio的成功經驗。希望這些內容能夠有助于你打造有用的、并且可用的API。

  評估API的商業價值

  API的通用商業價值是可以進行評估的。一切從數據出發,許多公司及組織將他們的數據視為一種負擔,畢竟服務器和存儲方案的價格不菲。但在如今這個越來越趨向于電子化的世界中,很顯然,數據也是一種寶貴的資產。數據提供了各種寶貴的客戶資料,它能夠產生可辨別的商機與新的收益方式。“大數據”狂潮正是追求通過海量數據的分析處理電子中的混亂信息。即將到來的物聯網(IoT)爆炸將使數據的規模呈現指數級的增長,因此對各個公司來說,對于數據進行正確的分析就變得至關重要。

  對于一家公司來說,數據到底是一種資產,還是一種負擔,是取決于以下三個方面的:即數據的可訪問性、準確性和可應用性。每個Web API都在某種程度上提供了某些數據的可用性,而有價值的API則為公司的核心商業數據提供準確的數據。這使得公司能夠達到一種我稱之為“Data-Enabled Disruption”的迭代發展模式,在下文中我會為這種模式做出解釋。此外,在決定應該由API暴露哪些數據及服務時,以及如何實現這些API時,這三個方面的數據屬性也提供了一種有效的方法論。

數據可應用性
  • 這些數據是否有助于我的商業目標決策?
  • 這些數據是否能夠為我的業務帶來獨特的價值?
  • 如果我將這些數據公開化,是否能產生某些商機?
數據準確性
  • 當前提供的數據時效性如何?
  • 數據的來源是否可靠?
  • 數據是否由期望中的用戶所使用?是否用于正確的目的?
數據可訪問性
  • 哪些數據是可以由編程方式獲取的?
  • 有哪些不同的方法可以獲取這些數據?
  • 開發者創建使用這些數據的應用難度有多大?
  • 數據訪問的規模能否滿足客戶的需求?

  如果從API的角度對這套方法論進行驗證,那么可以將數據的這三種屬性合并為API的兩種屬性:

  1. “實用的API”提供準確與合適的數據
  2. “可用的API”提供可訪問的數據

  顯然,最有價值的API應當滿足實用與可用兩個條件。不過,為了更進一步定義這些API屬性,讓我們分別來進行一下分析。

  實用的API

  在開發API時,人們最常見的一種錯誤就是認為所有的數據都是有用的。有一種流傳甚廣的奇談是這么說的:一旦你拿出這些數據,神奇的開發者們就會出現在你面前,他們會撒下一些具有魔力的粉末,讓你的收益得到增長、涌現各種創新的想法、并打通各種商業渠道。但僅僅使用API和開放數據是不足實現這幾點的。正是這種“媒體即訊息”的想法造成了過去十多年間在企業整合這一領域中出現了大量失敗的SOA嘗試。某家超大型企業曾經花費了5千萬美元以上的資金企圖打造一個SOA及私有云的項目。而當我問及他們打算為哪些客戶提供什么樣的服務時,他們立刻就啞口無言了,因為他們只關心如何打造基礎設施。毫無疑問,這個項目最終失敗了。

  從好的方面來說,如果能夠使用正確的API公開正確的數據,那么就能夠實現收益的增長與創新的進步。Google Maps利用Google壓倒性的占有率所提供的基于API的服務,正好填補了市場在這方面的空白。由于這一服務相當便利,因此Google可以將它作為商業產品收取大量費用。由于API的存在,Google Maps也成為了早期iOS平臺上的常駐應用,而蘋果自己推出的替代品在一開始的反響就相當差,這反而突顯了Google Maps的價值。由Facebook與Twitter領銜的社交網絡平臺的發展與成功也離不開API的應用,正是后者促進了他們的web鏈接數目與移動平臺上的應用實現。實用的API甚至對聯邦競選活動產生了深遠的影響

  Amazon的API故事

  正如我在文章開頭所說的一樣,企業從API中獲得商業成功的潛力遠遠大于開放API在表面上的能力。Amazon就充分利用了這種潛力,其實它們的API最初只是在公司內部進行使用。API為Amazon所帶來的這種長遠的成功在任何一個行業中都找不出相似的案例。Amazon為使用其API的客戶提供了寶貴的經驗,幫助這些企業使用它們的API獲得商業上的成功。

  Amazon為我們所上的第一堂課,也是最為明顯的一堂課,就是它是怎樣將API設計為產品與解決方案的構造塊的。Brad Stone在由他編寫的書籍中專門用了一章的篇幅來描述Amazon是怎樣將基礎API發展為它的技術航母的。Kin Lane也對Jeff Bezos(Amazon的CEO)是怎樣從固執的陳舊觀念中慢慢轉變,讓Amazon最終提供編程式訪問能力的過程進行了精彩的總結。按照這些報告及一些其它資料所說,產品經理必須指出他們的提案中的商業價值的最小公分母。隨后技術團隊就會利用這些原始數據創建API,將這些商業價值提供給其他開發者,讓他們繼續創建整個方案中剩余的部分。背后的邏輯在于這些商業價值的增長能夠直接使用,并且能夠非常容易地進行結合,以進行未來的產品開發。這就是為什么Amazon Web Services能夠如此迅速地發布及改善:Amazon對它們的基礎設施服務都進行了API化,從而優化了擴展基礎設施能力的過程。AWS的產生過程就是將這些內部功能轉化為外部產品。正如你所見,Amazon不僅僅保證了他們所提供的API提供了有用的數據,而是走得更遠,他們將這一原則進行了倒轉,保證了整個解決方案中的每一份數據都能夠由API方式進行提供。

  Amazon在API方面的另一個例子是它如何使用基于API的方式進行有價值數據的收集、分析、改善以及分布的。當早期的Amazon還以在線書籍的銷售為主營業務時,Jeff Bezos對于Amazon的核心價值觀念就有了清晰的預期:“我們不是通過銷售盈利,而是通過幫助客戶進行采購決策而盈利。”他始終保持公司的運營與這一核心價值觀相一致,從而產生了策略方向的各種改變,例如提供個性化及擴展頻道等舉措。實際上,從以上核心價值觀中就可以看到,正是這些適用的、準確的、并且可訪問的數據為客戶的決策作出了引導,讓Amazon一步步走向成功,而不是靠著在線圖書或其它商品的銷售獲得的成功。在Amazon,正是API將數據進行不斷積累與改進,這一周期性的過程刺激了Amazon的成長。我將這個360度的周期稱之為數據革新“Data-Enabled Disruption(DED)”,下圖是我對它的總結:

  DED的成功運用,使得Amazon得以擊敗無數競爭對手。由于在整個數據生命周期的每一步都應用了API,因此Amazon能夠持續地改進數據的準確性、適用性及可訪問性。

  我們最后將學習Amazon是怎樣在公司的戰術產出和策略定位之間進行平衡的。自從Jeff Bezos認識到萬維網的潛力,并制定出“提供所有商品的商店”這一愿景之后,他就非常清楚地認識到這種平衡的重要性。Bezos認識到他先要從一個較小的目標開始,經過對市場進行一番分析之后,他認準了在線圖片銷售這一領域,它的發展時機已經成熟,并且供應鏈也十分理想。在隨后的發展中一方面保證快速的執行,一方面始終不忘對未來的愿景設計,這種齊頭并進的發展理念已經成為了Amazon文化的一條根深蒂固的宗旨。每個解決方案既要為公司產生價值,也要為未來的發展鋪好基石。基于API的交付方式就是實現這種原則的一種理想的方式,因為API不僅能夠服務于新的應用與服務,也能夠為未來的各種用例打好基礎。這種迭代式發展的方法論促進了亞馬遜業務的不斷發展(見下圖的說明)。

  圖片中的每個服務都對應著一套外部的API,同時,每套API都是建立于已經完成的API基礎之上的。任何一家打算縱向或橫向進行業務擴展的公司,都可以認真參考一下Amazon是如何打造一套有用的API的方法:持續地收集和獲取可用的數據、使用API作為這些數據的通用訪問點、只交付短期內有用的數據,同時兼顧長期的發展計劃,以此作為公司的競爭力進行不斷擴展。

  可用的API及API設計的重要性

  盡管Amazon在這方面取得了令人矚目的成就,并且API在公司的成功中扮演著重要的角色,但Amazon的API仍然沒有被公認為設計最優秀、使用最簡單的API。隨著API數量的爆炸性增長,并且對于API的必要性的認可度也在不斷增加,API的可用性正是讓那些在行業中處于支配地位的公司,甚至是那些僅僅打算用API建立創新性服務的創業公司能夠獲得成功的關鍵因素。

  移動設備的出現及IT的消費化趨勢,是對于傳統的企業級應用開發的一次全面轉變。在過去,普遍存在著各種功能單一的終端主機、客戶-服務器系統、以及最近出現的web的分布式布局。我過去也曾談及這種我稱之為“層的脫落”的現象,即將業務從n層的web模型轉向以API為中心、以移動和云為優先的設計。這種轉變也包括了代碼開發從Java企業版轉到JavaScript及其衍生語言的情況。所有以上這些都表明,正有一波新的開發者,他們將逐漸成為開發新企業級解決方案的主力。這些開發者們習慣于主動尋找有什么API可以滿足他們所需的功能。當前的公司應當預計到這種轉變的發生,并迎合新一代開發者的需求,方式就是擁抱API的可用性

  讓我們看一下電信業的情況。多年以來,各大電信巨鱷們都在處心積慮地想要打倒競爭者,同時也在積極地推出各種跨網絡的增值服務。這個行業在過去的15年間產生了巨大的革新,包括VOIP的出現、業務及運維服務的整合,以及移動設備服務的革命。在種種革新的進程中,API都扮演了重要的角色。即使在傳統電信服務方面仍占據領先地位,但這些電信巨鱷們也難以從這波革新浪潮中受益。而當他們試圖與Parlay X及OneAPI等創業公司進行合作時,他們遇到的困難比這些小公司更多,Alan Quayle在他的一篇文章中就總結了這一現象。如果這些大佬們都難以抓住這次機遇,又有誰能做得到呢?

  創建于2007年的Twilio,其發展目標就是為客戶提供易于使用的語音及文字消息服務,并且完全在云端進行托管。他們一開始就計劃打造這樣一個平臺,并且意識到API會成為他們第一位的業務方向。SMS和VOIP服務固然很有用,但為了與電信巨鱷們展開競爭,他們所需的不僅僅是一些便利的電話服務而已。

  Twilio最關鍵的洞察力在于:他們已認識到所提供的服務的第一批客戶并非那些調用API的應用的終端用戶,而是那些負責開發這些應用的開發者本人。他們也知道,移動端應用的增長速度必然是最高的。因此,他們定制了一套指標,用以衡量這批客戶對API的滿意程度。除了傳統的終端用戶統計數據,例如端到端的API調用響應時間之外,他們還額外對新開發者注冊這套API所需的時間進行衡量,并且設定了很高的目標。這套指標的設定改善了API的可用性,從而為Twilio建立了對于各大電信巨鱷的領先優勢。當應用開發者們在為應用的開發選擇SMS或VOIP提供者時,Twilio的這套響應迅速的輕量級服務就明顯比起競爭們勝出一籌。有了這套實用的API之后,Twilio就可以理直氣壯地對服務收取費用,通過客戶的每次API調用的付費實現盈利。也正是因為這套實用的API,Twilio提高了公司的名氣,同時也增長了公司的利潤。

  電信之外的各個行業也對數據革新的方向準備就緒了。就拿 Ingenie來說,這家保險業的創業公司在基于精算的定價方法方面推陳出新,對于16-25歲這一階段的年青人會進行適當的懲罰。他們通過在每臺汽車里安裝的一種專利智能設備對每個駕駛員的數據進行收集,隨后依據這些數據為這些駕駛員們提供相應的保險折扣。實用的、可用的API使得Ingenie能夠實現數據帶來的革新,讓他們像Twilio一樣征服了整個保險行業。

  實用的、可用的API指南

  在此進行一下總結,要確保API的成功,可以通過以下幾個步驟來實現:

  • 確保你的API與公司的策略相一致
  • 在API中包含可訪問的、準確的并且適用的數據
  • 確保你的API是實用的,并且可用的
  • 學習Amazon的方式,建立一種規范的文化,迭代式地實現數據帶來的革新
  • 學習Twilio的方式,創建一種優秀API開發者體驗,從而使你的業務更勝于其它各大競爭者。

  只要按照這套指南的方法,你的API終將為你的業務帶來巨大成功,并成為這方面的典范。只有你能夠最好地判斷怎樣為客戶提供實用的API。此外,也請各位拜讀一下本系列中的其它各篇文章,它們會為你實現實用的API提供許多寶貴的建議。

2
0

請先登錄 提交中…

標簽:APi設計 商業

文章列表

云原生基礎及調研

云原生基礎及調研

作者: Cody Chan 來源: Cody’s blog 發布時間: 2019-12-14 20:46 閱讀: 629 次 推薦: 2 原文鏈接 [收藏]

  本文僅用于簡單普及,達到的目的是給沒接觸過或者很少接觸過這方面的人一點感覺,閱讀起來會比較輕松,作者深知短篇幅文章是不可能真正教會什么的,所以也不會出現 RTFM 的內容。

  概念

  提到云原生(Cloud Native)可能部分人會陌生,但是如果說 Serverless 相信很多人就知道了,實際上兩者并不等價。Serverless 是一種理念或者服務交付形態,目標是屏蔽硬件和運維細節,而云原生則是實現此類目標的一種規范以及基礎設施。

  再進一步,介于 Docker 天然的隔離性和高效等特點,以及 Kubernetes 成為事實意義上的 Docker 編排標準,凡是見到云原生或者 Serverless 的地方,幾乎都可以認為是基于 Docker + Kubernetes 的一種實踐。

  演進之路

  單個點展開講太枯燥,索性我們從歷史的角度看看為什么會有云原生。

  Docker

  先申明下,Docker 是一種容器技術(具體可深入 namespacescgroups),而不是虛擬化技術,真正的虛擬化比較常見的是 XenKVM,可能有同學要舉手了:老師,那我們經常用的 VirtualBox 和 VMware 算虛擬化么?當然算!不過大多數情況下,它們用在桌面虛擬化領域。不要急著撕,我說的是大多數,而且虛擬化方案也還有很多。

  可能大家之前經常遇到這樣的場景:為什么在我這可以運行在你那就不行了?為什么剛剛可以運行現在就不行了?最終解決下來,大多是環境不一致導致的問題。這里的環境除了開發環境還包括操作系統。

  所以一般給別人代碼的時候還需要告訴別人此代碼可運行的操作系統版本,所依賴的各種軟件的版本,甚至目錄、磁盤、內存、CPU 都有要求!

  當然這個問題還有更直接的辦法,就是把代碼跑在虛擬機里,然后打包虛擬機!(不要笑,實際上還真有人這么干)為什么此刻你笑了,因為虛擬機太重了,無論從打包的體積還是運行時占用的資源都太重了。

  那有沒有輕點的「虛擬機」呢?嗯,如標題,不過我們叫做容器化,特點:

  • 進程級別的隔離性;
  • 除里面運行的應用本身外幾乎不占用宿主資源;
  • 結構化的配置文件(Dockerfile);
  • 無狀態無副作用(主流方式);
  • 分層的聯合文件系統;

  Docker 讓運行環境變得可編程!

  拿一個最近部署 Sourcegraph 的經歷舉個栗子,官方有個開發者 清單,一堆依賴和環境設置,照著這個部署會爆炸的,好在官方還提供了可快速部署的鏡像,就是這么簡單:

  Kubernetes

太長,以下簡稱 K8S,類似的簡稱形式還有 很多

  Docker 雖然很厲害,但是在成人看來也只是小孩的玩具,稍微大點的公司內部可能服務就多的嚇人,特別是 微服務架構 盛行后。

  Docker 只解決了單個服務的交付問題,一個具備完整形態的應用必然會涉及各種服務依賴,人為組織這些依賴也是會死人的。Docker 把我們從各種跟環境糾纏里解放出來,卻讓我們陷入了更高維度的服務依賴之間的糾纏。

  是個 Docker 用戶應該都會想到去解決這個問題,如你所愿,出現了三國爭霸的局面:Docker SwarmApache MesosGoogle Kubernetes,一定程度上 K8S 成為了現在主流的 Docker 編排標準。有意思的是 K8S 有舵手之意,而 Docker 有集裝箱之意,所以結合下是不是更合理了?

  更有意思的是,K8S 管理 Docker 的過程也是一層層抽象。為了解決一組密切相關容器集合的調度,K8S 的最小的調度單位是 Pod 而不是容器,同一個 Pod 里的容器的資源可以互相訪問。為了管理發布、回滾、擴縮容,又在這之上抽象了一個 Deployment,實際上這是我們最直接使用的單元。為了管理負載均衡和調度,又抽象了一個叫 Service

  以上概念是 K8S 基本概念,不過我想強調的是這個:解決復雜問題很多都是在一層層抽象,這點展開還可以說很多東西。

  K8S 做的比較極致的點就是以上所有資源的管理都是通過聲明式的配置進行,K8S 把容器運維變得可編程!

  Cloud Native

  到這里,如果要直接在生產環境使用 K8S 基本也可以了,我們聊點別的吧。

  都知道 Java 后端廣泛采用的 Web 框架是 Spring MVC,那可是 02 年的老古董了!即使現在有了 Spring Boot,也可以算是一種升級,跟近幾年百花齊放的前端三大框架比少了太多的口水仗。

  百花齊放的原因很大一部分就是前端一開始就沒有形成強有力的最佳實踐!從工程化角度看,太多的重復輪子很容易導致工程的可維護性變差。Web 后端穩定性的特點不太能容忍這樣的事情發生,推導到云上也一樣。

  云原生就是云的(或狹義指 K8S 的)最佳實踐,生而為云,所謂云原生!

  為了達到此目的,還有了 CNCF(云原生計算基金會),有了組織就靠譜多了。這個組織有一個收集(或孵化)了各種最佳實踐的 云原生全景圖譜。比如,一個比較有意思的叫 helm,作為 K8S 應用包管理器,它把一個 K8S 應用抽象成一個包,一鍵就可以部署一個應用,跟很多包管理器一樣,它也有源 KubeApps Hub(甚至有阿里云提供的 國內源)。

  Serverless

  有了云原生,基本各種業務場景都可以找到適合的最佳實踐,Serverless 就是其中一種。個人很不理解為什么這個詞被翻譯成:無服務器架構,Serverless 屏蔽的是運維,所以叫無運維架構更合適。迫于無法接受其中文翻譯,文中還是用 Serverless。

  你可能好奇,為啥這里要把 Serverless 單獨拉出來說下,因為這是 CNCF 的寵兒啊!CNCF 范疇內太多項目了,但是大多還是偏硬,普通業務很難用上并落地,所以抓了個可以落地的當典型,還為其起草了個 白皮書,建議有興趣的可以細品。

  在說屏蔽運維之前,我們先回顧下運維一般包括哪些:

  • 服務器、網絡、存儲等物理資源(IaaS)申請;
  • 測試、發布、擴縮容;
  • 監控、日志;

  要達到屏蔽運維大體就是無需關心以上點,目前業界主流形式有 BaaS 和 FaaS:

  • BaaS(Backend as a Service):此服務做法就是把常見的后端服務抽象出來,比如數據存儲、文件存儲、消息等,客戶端使用這些服務時感覺就像在使用普通的 SDK/API。

圖片來自 Cloudflare

  • FaaS(Function as a Service):BaaS 只在大多數場景好使,某些特殊場景可能就比較麻煩,有些能力可能并沒有提供,但是又必須要在后端寫。完整關心整個后端代碼框架并沒必要,所以就可以抽象簡單一個個 function 讓用戶去完成。目前 Google 采用的是 Knative,這里還有個其它方案的對比 文章

  具體采用何種方式取決于業務形態,大體上就是用靈活性換方便度,給各種云服務一個靈活度排序:IaaS(各種云主機) > CaaS(Docker 等容器服務) > PaaS(BAE、SAE、GAE 等 APP Engine) > FaaS > BaaS > SaaS(各種 Web APP,如 Google Doc)

  歪歪的云計算九層架構,深色的表示留給用戶定制的,靈感來源

  Serverless 為開發者提供了一種屏蔽運維又具備一定靈活度的云服務。

  業界現狀

  本文只關心云原生相關產品,即 Docker/K8S 之上的產品,以下是部分主流產品:

  有條件都可以體驗下,我舉兩個切身的例子:

  • 除了大家見到很多公共云服務,還有很多服務是不適合放到公共云的,需要私有化部署。記得之前給某單位做項目,交付的時候過去裝系統、裝軟件,還要各種現場聯調,來來回回折騰很久。現在用 Docker + K8S 交付就非常輕松了,只需要有一套 K8S 集群,其它都 Docker 鏡像打包帶過去,一個配置文件輕松搞定編排!
  • 回想下你們做的系統,是不是很多幾乎都沒人用了,但是還是不能下線?是不是有的系統可能只有幾個接口也要從申請機器到申請各種中間件走一遍流程?我們姑且稱這些為長尾應用,這些應用是團隊歷史包袱變重的重要因素。如果采用 FaaS 或 BaaS 的方式做,你會發現新的人生,而與此相關的配套設施,業界主流的是 CLI 和 WebIDE,無論哪種,都會讓你爽。

  以上兩個例子雖然只反應了業界一部分現狀,可見一斑。

  總結

  本文簡單介紹了云原生的一些基本概念,從演進角度解釋了為什么會有云原生,本質就是抽象抽象再抽象,最后調研了國內外的主流現狀,讀到這希望你有點感覺了,進一步了解需要讀者自行實踐。

  參考資料

  正文里均以外鏈形式列出。

2
0

請先登錄 提交中…

標簽:云計算 云原生

文章列表

K8s GC設計原則

K8s GC設計原則

作者: 要沒時間了 來源: 知乎 發布時間: 2019-12-14 23:31 閱讀: 1128 次 推薦: 3 原文鏈接 [收藏]

  Ref

  Warning:設計文檔的對應的 k8s 版本為1.7

Q: What is GC of Kuernetes ?

  GC 是 Garbage Collector 的簡稱。從功能層面上來說,它和編程語言當中的「GC」 基本上是一樣的。它清理 Kubernetes 中「符合特定條件」的 Resource Object。(在 k8s 中,你可以認為萬物皆資源,很多邏輯的操作對象都是 Resource Object。)

Q: What are dependent mechanisms to clear needless resource objects?

  Kubernetes 在不同的 Resource Objects 中維護一定的「從屬關系」。內置的 Resource Objects 一般會默認在一個 Resource Object 和它的創建者之間建立一個「從屬關系」。

  當然,你也可以利用 ObjectMeta.OwnerReferences 自由的去給兩個 Resource Object 建立關系,前提是被建立關系的兩個對象必須在一個 Namespace 下。

 1 // OwnerReference contains enough information to let you identify an owning 2 // object. Currently, an owning object must be in the same namespace, so there 3 // is no namespace field. 4 type OwnerReference struct { 5 // API version of the referent. 6 APIVersion string `json:"apiVersion" protobuf:"bytes,5,opt,name=apiVersion"` 7 // Kind of the referent. 8 // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds 9 Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` 10 // Name of the referent. 11 // More info: http://kubernetes.io/docs/user-guide/identifiers#names 12 Name string `json:"name" protobuf:"bytes,3,opt,name=name"` 13 // UID of the referent. 14 // More info: http://kubernetes.io/docs/user-guide/identifiers#uids 15 UID types.UID `json:"uid" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` 16 // If true, this reference points to the managing controller. 17 // +optional 18 Controller *bool `json:"controller,omitempty" protobuf:"varint,6,opt,name=controller"` 19 // If true, AND if the owner has the "foregroundDeletion" finalizer, then 20 // the owner cannot be deleted from the key-value store until this 21 // reference is removed. 22 // Defaults to false. 23 // To set this field, a user needs "delete" permission of the owner, 24 // otherwise 422 (Unprocessable Entity) will be returned. 25 // +optional 26 BlockOwnerDeletion *bool `json:"blockOwnerDeletion,omitempty" protobuf:"varint,7,opt,name=blockOwnerDeletion"` 27 }

   OwnerReference 一般存在于某一個 Resource Object 信息中的 metadata 部分。

   OwnerReference 中的字段可以唯一的確定 k8s 中的一個 Resource Object。兩個 Object 可以通過這種方式建立一個 owner-dependent 的關系。

  K8s 實現了一種「Cascading deletion」(級聯刪除)的機制,它利用已經建立的「從屬關系」進行資源對象的清理工作。例如,當一個 dependent 資源的 owner 已經被刪除或者不存在的時候,從某種角度就可以判定,這個 dependent 的對象已經是異常(無人管轄)的了,需要進行清理。而 「cascading deletion」則是被 k8s 中的一個 controller 組件實現的: Garbage Collector

  所以,k8s 是通過 Garbage Collector 和 ownerReference 一起配合實現了「垃圾回收」的功能。

Q: What is the relationship like ?(owner-dependent)

  我們可以通過一個實際的例子來了解這個「從屬關系」:

 1 apiVersion: extensions/v1beta1 2 kind: ReplicaSet 3 metadata: 4 annotations: 5 deployment.kubernetes.io/desired-replicas: "2" 6 deployment.kubernetes.io/max-replicas: "3" 7 deployment.kubernetes.io/revision: "1" 8 creationTimestamp: 2018-09-07T07:11:52Z 9 generation: 1 10 labels: 11 app: coffee 12 pod-template-hash: "3866135192" 13 name: coffee-7dbb5795f6 14 namespace: default 15 ownerReferences: 16 - apiVersion: apps/v1 17 blockOwnerDeletion: true 18 controller: true 19 kind: Deployment 20 name: coffee 21 uid: 4b807ee6-b26d-11e8-b891-fa163eebca40 22 resourceVersion: "476159" 23 selfLink: /apis/extensions/v1beta1/namespaces/default/replicasets/coffee-7dbb5795f6 24 uid: 4b81e76c-b26d-11e8-b891-fa163eebca40 25 spec: 26 replicas: 2 27 ....

  上面截取了一個 ReplicaSet Object 中的 metadata 的部分信息。

  我們可以注意到,它的 ownerReferences 字段標識了一個 Deployment Object。我們都清楚的是,ReplicaSet 會創建一系列的 Pod。通過 spec.replicas:2 可以知道,他會創建兩個pod。

1 root@xr-service-mesh-lab:~/istio-1.0.2# kubectl get pods | grep coffee 2 coffee-7dbb5795f6-6crxz 1/1 Running 0 9d 3 coffee-7dbb5795f6-hv7tr 1/1 Running 0 5d 4 root@xr-service-mesh-lab:~/istio-1.0.2#

  讓我們來觀察其中一個 Pod:

 1 apiVersion: v1 2 kind: Pod 3 metadata: 4 annotations: 5 cni.projectcalico.org/podIP: 192.168.0.14/32 6 creationTimestamp: 2018-09-07T07:11:52Z 7 generateName: coffee-7dbb5795f6- 8 labels: 9 app: coffee 10 pod-template-hash: "3866135192" 11 name: coffee-7dbb5795f6-6crxz 12 namespace: default 13 ownerReferences: 14 - apiVersion: apps/v1 15 blockOwnerDeletion: true 16 controller: true 17 kind: ReplicaSet 18 name: coffee-7dbb5795f6 19 uid: 4b81e76c-b26d-11e8-b891-fa163eebca40 20 resourceVersion: "76727" 21 selfLink: /api/v1/namespaces/default/pods/coffee-7dbb5795f6-6crxz 22 uid: 4b863e4d-b26d-11e8-b891-fa163eebca40

  我們可以看出,pod 中的 ownerReferences 所標識的 Object 正式我們上面看到過的 ReplicaSet。最后讓我們來檢查一下 ReplicaSet 所對應的 Deployment 的情況:

 1 apiVersion: extensions/v1beta1 2 kind: Deployment 3 metadata: 4 annotations: 5 deployment.kubernetes.io/revision: "1" 6 creationTimestamp: 2018-09-07T07:11:52Z 7 generation: 1 8 labels: 9 app: coffee 10 name: coffee 11 namespace: default 12 resourceVersion: "476161" 13 selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/coffee 14 uid: 4b807ee6-b26d-11e8-b891-fa163eebca40

  對比一下 ReplicaSet Object 中 ownerReference 標識的 Object 可知,這個 Deployment 是 ReplicaSet 的 owner。至此,我們通過觀察三個 Object 中的 ownerReference 的信息,可以建立起如下的「從屬關系」:

  • Deployment(owner)—> ReplicaSet (dependent)
  • ReplicaSet (owner) —> Pod (dependent)

Q: What is the working mechanism of Garbage Collector?

  一個 Garbage Collector 通常由三部分實現:

  • Scanner: 它負責收集目前系統中已存在的 Resource,并且周期性的將這些資源對象放入一個隊列中,等待處理(檢測是否要對某一個Resource Object 進行 GC 操作)
  • Garbage Processor: Garbage Processor 由兩部分組成
  • Dirty Queue: Scanner 會將周期性掃描到的 Resource Object 放入這個隊列中等待處理
  • Worker:worker 負責從這個隊列中取出元素進行處理
  • 檢查 Object 的 metaData 部分,查看 ownerReference 字段是否為空
  • 如果為空,則本次處理結束
  • 如果不為空,檢測 ownerReference 字段內標識的 Owner Resource Object是否存在
  • 存在:則本次處理結束
  • 不存在:刪除這個 Object

  其實,在有了 Scanner 和 Garbage Processor 之后,Garbage Collector 就已經能夠實現「垃圾回收」的功能了。但是有一個明顯的問題:Scanner 的掃描頻率設置多少好呢?太長了,k8s 內部就會積累過多的「廢棄資源」;太短了,尤其是在集群內部資源對象較多的時候,頻繁的拉取信息對 API-Server 也是一個不小的壓力。

  k8s 作為一個分布式的服務編排系統,其內部執行任何一項邏輯或者行為,都依賴一種機制:「事件驅動」。說的簡單點,k8s 中一些看起來「自動」的行為,其實都是由一些神秘的「力量」在驅動著。而這個「力量」就是我們所說的「Event」。任意一個 Resource Object 發生變動的時候(新建,更新,刪除),都會觸發一個 k8s 的事件(Event),這個事件在 k8s 的內部是公開的,也就是說,我們可以在任意一個地方監聽這些事件。

  總的來說,無論是「事件的監聽機制」還是「周期性訪問 API-Server 批量獲取 Resource Object 信息」,其目的都是為了能夠掌握 Resource Object 的最新信息。兩者是各有優勢的:

  1. 批量拉取:一次性拉取所有的 Resource Object,全面
  2. 監聽 Resource 的 Event:實時性強, 且對 API—SERVER 不會造成太大的壓力

  綜上所述,在實現 Garbage Collector 的過程中,k8s 向其添加了一個「增強型」的組件:Propagator

  • Propagator: Propagator 由三個部分構成
  • EventQueue:負責存儲 k8s 中資源對象的事件(Eg:ADD,UPDATE,DELETE)
  • DAG(有向無環圖):負責存儲 k8s 中所有資源對象的「owner-dependent」 關系
  • Worker:從 EventQueue 中,取出資源對象的事件,根據事件的類型會采取以下兩種操作
  • ADD/UPDATE: 將該事件對應的資源對象加入 DAG,且如果該對象有 owner 且 owner 不在 DAG 中,將它同時加入 Garbage Processor 的 Dirty Queue 中
  • DELETE:將該事件對應的資源對象從 DAG 中刪除,并且將其「管轄」的對象(只向下尋找一級,如刪除 Deployment,那么只操作 ReplicaSet )加入 Garbage Processor 的 Dirty Queue 中

  在有了 Propagator 的加入之后,我們完全可以僅在 GC 開始運行的時候,讓 Scanner 掃描一下系統中所有的 Object,然后將這些信息傳遞給 Propagator 和 Dirty Queue。只要 DAG 一建立起來之后,那么 Scanner 其實就沒有再工作的必要了。「事件驅動」的機制提供了一種增量的方式讓 GC 來監控 k8s 集群內部的資源對象變化情況。

Q: How can I delete the owner and reserve dependents ?

  沒錯,需求就是這么奇怪,k8s 還兼容一種情況:刪除 owner,留下 dependents。剩余的 dependents 被稱為是「orphan」

  你想怎么實現?

  如果暫時先不看設計文檔中關于這部分的內容,根據之前對 k8s GC 的了解,讓你來實現這個功能,你會怎么做呢?這里給出一下筆者的想法:

  首先,我們先來根據上面對于 GC 的了解,給出一幅大致架構圖:

  在上圖中,我用三種顏色分別標記了三條較為重要的處理過程:

  • 紅色:worker 從 dirtyQueue 中取出資源對象,檢查其是否帶有 owner ,如果沒帶,則不處理。否則檢測其 owner是否存在,存在,則處理下一個資源對象,不存在,刪除這個 object。
  • 綠色: scanner 從 api-server 中掃描存在于 k8s 集群中的資源對象并加入至 dirtyQueue
  • 粉色:propagator.worker 從 eventQueue 中取出相應的事件并且獲得對應的資源對象,根據事件的類型以及相應資源對象所屬 owner 對象的情況來進行判定,是否要進行兩個操作:
  • 從 DAG 中刪除相應節點(多為響應 DELETE 事件的邏輯)
  • 將有級聯關系但是 owner 不存在的對象送入 diryQueue 中

  其中紅色是「數據處理」過程,而綠色和粉色是「數據收集」的過程。在「數據處理」的過程中(即我們上面分析過的 GC 的 Worker 的工作過程),worker 做的較為重要的工作有兩步:

  • 檢查資源對象信息的「ownerReference」字段,判斷其是否處在一個級聯關系中
  • 若資源對象有所屬 owner 且不存在,則刪除這個對象

  此時,回頭看下我們的需求:「owner 刪除,dependents 留下」。如果想在「數據處理」這條鏈路上做些修改達到我們目的的話,唯一可行的辦法就是:在刪除了 dependents 對應的 owner 對象之后,同時刪除 dependents 信息中 「ownerReference」字段和對應的值。這樣一來,在檢測資源對象是否應該被刪除的過程就會因為其沒有「ownerReference」字段而放過它,最終實現了 dependents 對象的“孤立”。

  k8s 是怎么實現的?

  如果你了解 gRPC-intercepter 的工作機制,那么會加快你理解下面的內容

  k8s 在系統內部實現了一種類似「刪除攔截器鏈」的機制:即在刪除某個資源對象的「刪除鏈路」上,執行一個或多個「攔截邏輯」。并且這種「攔截邏輯」可以自主實現,然后像插件一樣注入到這個刪除鏈路上。這種機制在 k8s 當中統稱為: Finalizers 。

   Finalizers 的聲明非常簡單,就是一個 []string 。這個 Slice 的內部填充的是要執行攔截器的名稱。它存在于任何一個資源對象的 Meta 信息中: apimachinery/types.go at master · kubernetes/apimachinery · GitHub

   Finalizers 中的攔截器在其宿主資源對象觸發刪除操作之后順序執行(資源對象的deletionTimestamp不為 nil),每執行完一個,就會從 Finalizers 中移除一個,直到 Finalizers 為空的 Slice,其宿主資源對象才可以被真正的刪除。

  于「刪除 owner 但是不刪除 dependents」 的需求,k8s 則是實現了一個:orphan finalizer。一般情況下,正常利用 GC 級連刪除一個資源對象是不會涉及到 orphan finalizer 的。它執行的是我們之前提到的 GC 的工作邏輯。如果你想啟用這個特性,就需要在刪除資源對象的時候,根據 K8s 版本的不同,將名為 DeleteOption.OrphanDependents 的參數賦值為 True(1.7版本以前)

  或者將 DeleteOption.PropagationPolicy 參數賦值為 metav1.DeletePropagationOrphan :apimachinery/types.go at 9dc1de72c0f3996657ffc88895f89f3844d8cf01 · kubernetes/apimachinery · GitHub

  通過這個參數的注釋也可以看出:如果設置好之后,將會在它的 Finalizers 中加入 orphan finalizer。而加入 orphan finalizer 這部分的邏輯是在 api-server 的 package 中:apiserver/store.go at master · kubernetes/apiserver · GitHub

  加入了 orphan finalizer 之后,在 GC 的 worker 從 dirtyQueue 中取出 owner 資源對象進行處理的時候,就會執行它的邏輯:刪除 dependents 的 OwnerReference 部分: kubernetes/garbagecollector.go at 0972ce1accf859b73abb5a68c0adf4174245d4bf · kubernetes/kubernetes · GitHub。最終,在「保留 dependents」 的邏輯完成之后,orphan finalizer 也會從相應資源對象的 Finalizers 中刪除。

  一個隱含的 Race 問題

  對于 Controller 來說,它會周期性的通過 Selector 來尋找它所創建的資源。

  如果在篩選到了符合自己 label 的 資源,但是發現它的 Meta.OwnerReference 字段中沒有自己相關的信息的時候,就會執行一個Adoption 的操作,也就是將和自己有關的 OwnerReference 信息注入到這個 Pod 的 Meta 部分。這種邏輯雖然看起來是比較「保險」,但是實際上它和 orphan finalizer 的邏輯是有沖突的。前者是對 dependents 增加 OwnerReference 信息, 后者則是刪除它。兩個邏輯在執行的時候,如果不保證「互斥」的話,很可能就會出現一個很嚴重的競爭問題:指定了 orphan finalizer 的 對象,其 dependents 最終也會被刪除。

  借鑒操作系統對于「競爭」問題的處理方式,對 OwnerReference操作的的邏輯(即臨界區),應該被「互斥」機制保護起來。而在 k8s 中,實現這種互斥保護機制的方式也很簡單:Controller 在想執行 Adoption 操作之前,會檢查一下當前資源對象的meta.DeletionTimestamp。如果這個字段的值為非 nil,那么就證明這個資源對象已經在被刪除中了。所以就不會再繼續執行 Adoption 操作。

  但是仔細想一下,這種「互斥」保護機制的實現方式,看起來是借助了一個「鎖變量」(meta.DeletionTimestamp)的幫助。不過,我們并不需要擔心這個字段的「競爭」問題,因為能修改它的操作,只有「刪除」操作,而刪除操作是肯定會發生在 orphan finalizer 執行之前的。也就是說,當 orphan finalizer 執行的時候,這個值早就被設置進去了。 kubernetes/replica_set.go at 7f23a743e8c23ac6489340bbb34fa6f1d392db9d · kubernetes/kubernetes · GitHub

Q: How Kubernetes defines delete operation of resource object?

  K8s 在對資源對象「刪除」操作的定義上,思考了一個較為重要的問題:「刪除」操作真正完成的標志是什么?(達到什么樣的條件才可以通知用戶「刪除」操作成功)。這個問題出現的源頭是在用戶側,當用戶在使用 k8s 提供的資源對象的「刪除」操作時,有個問題會影響到他們:

  1. 「刪除」操作成功多久后才可以在同一個 ns 下創建同名資源對象?

  如果你了解過構建一個 k8s 集群所需要的服務組件,就可以很清楚的知道:k8s 中的資源對象的信息都是存于一個key-value 的數據庫當中的(etcd),且是以名字來做索引的。

  通過命令行 kubectl get xxx 查詢的資源對象的信息都來自于那。而且,當 kubelet 組件刪除掉其所在節點上的一些資源的時候,會調用 API-Server 提供的接口刪除掉key-value 數據庫中相應的記錄。所以,在 k8s 中,給「刪除」操作下了這樣一個定義:

在沒有 orphanFinalizer 參與的前提下,直到被刪除對象及其「管轄」對象的信息在 key-value 數據庫中都被清除,才認為該對象真正的被 GC 回收。即達到了返回給用戶「刪除成功」的標準。

  本質上來說,上述所表示的刪除操作是「同步」的。因為有「級聯關系」(owner-dependent) 關系的存在,刪除一個資源對象往往影響的不是他自己,還有他的 dependents。只有將因它出現的所有資源都刪除,才可以認為這個對象被刪除了。

  若想指定這種同步的刪除模式,需要在兩個不同的位置設置兩個參數:

  1. dependents 對象 meta 信息中 OwnerReference.BlockOwnerDeletion
  2. 在發送刪除對象請求時,設置 DeleteOptions.PropagationPolicy

   OwnerReference.BlockOwnerDeletion 參數大多數情況下在相應的 dependents 對象創建的時候就設置進去了。

  如果想在 dependents 對象創建之后更新這個參數的值,可能需要使用 admission controller (1.7及以上版本)提供的一些權限相關的功能。

   DeleteOptions.PropagationPolicy 一共有3個候選值:

 1 // DeletionPropagation decides if a deletion will propagate to the dependents of 2 // the object, and how the garbage collector will handle the propagation. 3 type DeletionPropagation string 4 5 const ( 6 // Orphans the dependents. 7 DeletePropagationOrphan DeletionPropagation = "Orphan" 8 // Deletes the object from the key-value store, the garbage collector will 9 // delete the dependents in the background. 10 DeletePropagationBackground DeletionPropagation = "Background" 11 // The object exists in the key-value store until the garbage collector 12 // deletes all the dependents whose ownerReference.blockOwnerDeletion=true 13 // from the key-value store. API sever will put the "foregroundDeletion" 14 // finalizer on the object, and sets its deletionTimestamp. This policy is 15 // cascading, i.e., the dependents will be deleted with Foreground. 16 DeletePropagationForeground DeletionPropagation = "Foreground" 17 )

  再結合 OwnerReference.BlockOwnerDeletion 參數的注釋

1 // If true, AND if the owner has the "foregroundDeletion" finalizer, then 2 // the owner cannot be deleted from the key-value store until this 3 // reference is removed. 4 // Defaults to false. 5 // To set this field, a user needs "delete" permission of the owner, 6 // otherwise 422 (Unprocessable Entity) will be returned. 7 // +optional 8 BlockOwnerDeletion *bool `json:"blockOwnerDeletion,omitempty" protobuf:"varint,7,opt,name=blockOwnerDeletion"`

  我們可以了解到。同步刪除的開啟方式如下:

  1. DeleteOptions.PropagationPolicy = DeletePropagationForeground
  2. OwnerReference. BlockOwnerDeletion = True

  開啟之后,在刪除身份為 owner 的資源對象的時候,就會先將 denpendents 對象中 OwnerReference.BlockOwnerDeletion 為 true 的資源對象先刪除,然后再刪除 owner 身份的對象。這里的「刪除」就指的是我們前面說過的「真正的刪除」:從 k8s 存儲資源對象信息的 key-value 數據庫中刪除所有與其相關的信息。需要注意的是, OwnerReference.BlockOwnerDeletion 為 false 的dependent 對象不會阻礙 owner 對象的刪除操作。

  Foreground 是 k8s 提供的兩種級聯刪除方式其中之一,另外一種為 Background。通過上面相關的注釋可以看到 Foreground 級聯刪除也是通過 Finalizer 來實現的,查看 Finalizer 相關的定義可知,標準的 Finalizer,一個是 orphan 的,另一個就是Foreground的:

1 // These are internal finalizer values for Kubernetes-like APIs, must be qualified name unless defined here 2 const ( 3 FinalizerOrphanDependents string = "orphan" 4 FinalizerDeleteDependents string = "foregroundDeletion" 5 )

  API-Server 的 Delete 函數,在接受到刪除請求的時候,會檢查 DeleteOptions.PropagationPolicy 參數,若其值為 DeletePropagationForeground , API-Server 隨即會對該資源對象進行 Update 操作:

  1. 插入 FinalizerDeleteDependents Finalizer
  2. 設置 ObjectMeta.DeletionTimestamp 為當前值

  然后,在 GC 處理 owner 對象的 Update 事件的邏輯中,還會給 owner 對象打上一個「正在刪除 dependents」 對象的標簽。之后,我們會將 owner 對象管轄的 dependent 對象和他自己都加入到 dirtyQueue。dirtyQueue 的 worker 在處理 owner 對象的時候,會檢查 owner 對象 「正在刪除 dependents」的標簽是否存在,如果仍有 dependent 對象沒有被刪掉,owner 會被輪詢處理。而 dependent 對象將會被正常刪除。當 dependent 對象相應的刪除事件被 Propagator 感知到后,會將其從 DAG 和其 owner 的 dependents 信息中刪除。幾個循環之后,dependents 機會被刪光,而 owner 對象中的 finalizer 和自身也會隨之被刪掉。

  Background 模式的級聯刪除不會因 dependent 對象而影響 owner 對象的刪除操作。當我們發送給 API-Server 刪除一個 owner 身份的對象的請求之后,這個資源對象會立即被刪除。它「管轄」的 dependent 對象會以「靜默」的方式刪除。

Q: What problems GC handles in k8s?

  通過對 k8s GC 設計文檔的閱讀,可以大致的概括一下:GC 主要是按照用戶的需求來清理系統中「異常」的資源,用戶可以自定義「清理方式」和「清理策略」。不難發現,在 GC 中,到底是保留一個資源還是刪除一個資源都參照了資源之間的「從屬關系」。資源的「從屬關系」可以大致分為幾個形態:

  1. 無從屬關系:這部分資源基本不會被 GC 做處理
  2. 有從屬關系
  1. 不符合用戶預期:刪除異常資源
  2. 符合用戶預期:解綁異常資源之間的級聯關系

  在有從屬關系的資源之間,即使被探測到關系異常,也并不代表一定要將他們都清除。如果有 Orphan Finalizer 的存在,可能某種「異常」正是用戶想要的。所以,這就回到了我們一開始所說到的「清理策略」問題。GC 有一定的默認的清理策略,但是用戶可以通過加入 Finalizer 的形式來修改「清理策略」,從而保持一個「符合用戶期望」的資源之間的從屬關系。

  同時,用戶可以還可以通過參數來制定特定的「清理方式」,如 Foreground 或者 Background。總體上來說,GC 的行為會受到如下幾個因素的影響:

  • 默認:
  • 依據:資源之間默認的從屬關系
  • 行為:刪除級聯關系異常的資源
  • 方式:Foreground 或者 Background
  • 可定制
  • 依據:用戶定義的從屬關系(通過 Finalizer)
  • 行為:刪除級聯關系異常的資源
  • 方式:Foreground 或者 Background

  目前看來,GC 主要是解決了「資源清理」 的問題。那么再抽象一點來看的話,GC 解決的是「資源管理」這個大問題中的一個關于「清理」的小問題。既然說到「資源管理」,那么肯定就不止「清理」一個問題需要處理:

  其中,對于資源的創建部分,除了正常的新建操作之外,controller 還有定期執行一個「Adoption」 的操作,用來維護其創建的資源之間那些「本應該建立但是卻斷開的從屬關系」。而對于更新操作來說,controller_manager 需要處理用戶對于資源的擴縮容請求,如將 deployment.replicaset.replicacount 減少或者增大,相應資源對應的 controller 需要對可見資源的數量進行調整。至于「資源超賣」的問題,一定會涉及到 scheduler。因為物理資源是固定的,「超賣」本質上來說就是按照實時的需求,動態的調整服務所在的 Node,以便恰好滿足服務對于資源的需求。

  如果不把資源管理問題討論的范圍局限在 k8s 中的話,那么「審計」和「復用」同樣也是「資源管理」問題中不得不考慮的兩個點。前者可以增加整個系統資源的「可控性」,后者則可以最大限度的提升資源的利用率,從而降低成本。其實「復用」和「超賣」的目的是一樣的,都是想最大限度的利用物理資源。不過這兩個功能筆者暫時還沒有去查看它們是否在 k8s 已經實現。

  總結

  之所以去了解 k8s 的 GC,是因為在將 k8s 集群從1.7版本升級至1.9版本的過程中,因為我錯誤的設置了資源之間的從屬關系,導致該資源被 GC 給回收掉了。問題在1.7版本沒有出現的原因是那時 k8s 還沒有支持對自定義資源(CRD)的級聯刪除。通過對 GC 設計理念的了解,我們可以初步的感受到 k8s 對于「資源管理」這個問題域中「資源清理」這個小問題的解決思路。以此為起點,我們可以順藤摸瓜,去觀察 k8s 對于「資源管理」問題域中的其他難題是如何處理的。后續,我也將會根據設計文檔中的思路,在代碼級別上去了解 GC 的實現細節,從而貢獻出更加詳細的 blog。

3
0

請先登錄 提交中…

標簽:k8s

文章列表