作者: paul

SOLID重點複習-1.OCP開放-封閉法則

SOLID重點複習-1.OCP開放-封閉法則

提出者:Betrand Meyer在19888年提出

OCP(The Open-Closed Principle):開放-封閉原則

如果程式的區塊一旦變動,就會產生一連串的反應,導致相關使用到的模組都必須更動,那麼這個程式碼就具備bad smell

OCP建議我們應該進行重構

 

如果OCP原則應用正確的話,那麼,以後再進行”同樣”的需求變動時,就只需要增加新的程式碼,而不用再調整”已經”正常運行的程式碼

其特徵為2:

1.對於擴展是開放的(open for extension)

2.對修改是封閉的(closed for modification)

如何可以「不變動模組原始碼但改變行為呢?」,OOP讓他能做到的機制就是抽象(Abstract)

建立可以固定能”描述”可能行為的抽象體作為抽象基底類別

 

模組針對抽象體進行操作。透過抽象體的衍生,就可以擴展此模組的行為

不遵循OCP的簡單設計的程式如下,Client直接使用了ServerImplementA的類別方法。

using System;
					
public class Program
{
	public class Client
	{
		public void run()
		{
			var server = new ServerImplementA();
			server.Start();
		}
	}
	
	public class ServerImplementA
	{
		public ServerImplementA()
		{
			
		}
		public void Start()
		{
			Console.Write("ServerImplement A Running");
		}
	}
	
	
	public static void Main()
	{
		new Client().run();
	}
}

註:這種直接透過實作達成目的,雖然沒有滿足ocp的要求,不是oop的設計原則,但是並不一定是不好的哦。

因為能解決問題的程式就是好程式,若這樣的程式需求變動可能性低或目的性明確的話,或許這種程序式的程式設計風格(Procedural programming Style)反而意圖更明確好懂呢。

此例中:Client使用了Implement A的類別,這是一種既不開放又不封閉的Client

 

若遵循OCP來做設計,使用策略模式(Strategy)可以重構他

using System;
					
public class Program
{
	public class Client
	{
		public void run(string type)
		{
			ClientInterface client = null;
			if (type=="A")
			  client = new ServerImplementA();
			if (type=="B")
			  client = new ServerImplementB();
			
			if (client!=null)
				client.Start();
		}
	}
	
	
	interface ClientInterface
	{
		void Start();
	}
	
	
	public class ServerImplementA:ClientInterface
	{
		public ServerImplementA()
		{
		}
		
		public void Start()
		{
			Console.WriteLine("ServerImplement A Running");
		}
	}
	
	public class ServerImplementB:ClientInterface
	{
		public ServerImplementB()
		{
		}
		
		public void Start()
		{
			Console.WriteLine("ServerImplement B Running");
		}
	}
	
	
	public static void Main()
	{
		Client client = new Client();
		client.run("A");
		client.run("B");
	}
}

我們透過封裝Client的固定行為”Run”,拉到抽象體(策略模式的抽象載體為interface)

註:書中的案例發生了Client若使用了Server的程式時,抽象體為什麼要叫ClientInterface呢?

作者提到:抽象類別和它們的客戶的關係要比實作它們的類別關係更密切一點

 

那麼,當我們要擴充B的實作的時候,只需要針對B去”增加”實作,然後Client的使用端,不用再去改到使用行為,當然策略模式使用interface來解決抽象體

那麼簽章異動時(例如方法,參數),那麼仍然是會面對到策略模式帶來的副作用!

 

另一種樣版方法模式(Template Method)

using System;
					
public class Program
{
	public class Client
	{
		public void run(string type)
		{
			ServerInterface client = null;
			//封閉的地方
			if (type=="A")
			  client = new ServerImplementA();
			//開放的地方
			if (type=="B")
			  client = new ServerImplementB();
		}
	}
	
	
	public abstract class ServerInterface
	{
		public ServerInterface()
		{
			this.Start();
		}
		
		public abstract void Start();
	}
	
	//不變的地方
	public class ServerImplementA:ServerInterface
	{
		public ServerImplementA()
		{
		}
		
		public override void Start()
		{
			Console.WriteLine("ServerImplement A Running");
		}
	}
	
	//開放的地方
	public class ServerImplementB:ServerInterface
	{
		public ServerImplementB()
		{
		}
		
		public override void Start()
		{
			Console.WriteLine("ServerImplement B Running");
		}
	}
	
	
	public static void Main()
	{
		//不變的地方
		Client client = new Client();
		
		//開放的地方
		client.run("A");
		client.run("B");
	}
}

透過抽象類別的實作,其實針對使用端來說,差不多差不多,都是為了抽象化,讓Client使用上足以一致性

以我抽象類別實作的好處是你可以將部分共同的實作封裝在抽象類別中!讓重覆的行為更被集中!以上面的例子,就是將Start的 行為抽到Constructor去

而抽象體使用介面或是抽象類別來達成哪個好?以結果論則是視使用上是否足夠抽象來決定

 

這兩個模式是滿足OCP最常用的方法

 

參考書摘-無瑕的程式碼-敏捷完整篇 物件導向原則、設計模式與C#實踐

 

.net throw ex 與 throw 的不同之處(重新理解)

.net throw ex 與 throw 的不同之處(重新理解)

前幾天保哥在.net社群提示一個人的詢問 throw ex跟throw差在哪,他寫了類似以下範例的程式,但沒有明說差別
仔細想想,我似乎也沒特別想過這個問題
以往例外處理的議題就是不要不處理就往最外面丟,或是不要都不做事把錯誤隱藏
但往外丟本身,我習慣確實是throw,而不是throw一個變數,通常我要丟變數的時候,要不就是重新包成有意義的new exception,例如把function的意義多做描述。
否則就是不會new一個似是而非的錯誤”訊息”。
實際查了一下,stackflow網站上有人提出來:
throw ex resets the stack trace (so your errors would appear to originate from HandleException)
throw doesn’t – the original offender would be preserved.
結果我實際寫了一個winform看了一下差別,一開始我還把這兩個例子都直接寫在”同”一個function裡,結果想說,沒有不見呀…
結果原來是在同一個function裡面看不出來,因為不算是跨stack

以下是看的出來的例子..

 //for test args = new[] {"appreset"};
            try
            {
                try
                {
                    test1();
                }
                catch (Exception b)
                {
                    throw b;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }


            try
            {
                try
                {
                    test2();
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }



   private static void test2()
        {
            try
            {
                throw new Exception("OK", new Exception("b"));
            }
            catch (Exception)
            {
                throw;
            }
        }

        private static void test1()
        {
            try
            {
                throw new Exception("OK", new Exception("b"));
            }
            catch (Exception a)
            {
                throw a;
            }
        }
定期搬移目錄到指定日期目錄Bat範例

定期搬移目錄到指定日期目錄Bat範例

實驗worked!!

記錄一下

set sourcePath=D:\A
set targetPath=D:\B

for /f "skip=1 delims=" %%x in ('wmic os get localdatetime') do if not defined X set X=%%x
set yyyyMMdd=%X:~0,8%

mkdir D:\B\%yyyyMMdd%

xcopy /F /Q /E /S %sourcePath% %targetPath%\%yyyyMMdd%

del /F /q "D:\A\*.*"
FOR /D %%p IN ("%sourcePath%\*.*") DO rmdir "%%p" /s /q
SQL Server 誤刪資料庫的資料怎麼辦

SQL Server 誤刪資料庫的資料怎麼辦

今天同事忘了下where條件

不小心誤更新了四千多筆的資料表

一時間協助查詢解決方法,畢竟不是dba

操作實務不足,也不敢馬上給指令

深怕把資料庫搞壞,連現行資料都弄不回來

 

事後,經過比較各個blog,MSDN,還有跟同事的討論後,實證驗正過以下做法可行,足以做為SOP

也同時花了點時間理解原理

 

發現原來我認知的誤區就是我以為資料庫靠transaction log就可以rollback回去過去時間點(減法)

但其實關鍵點反而是用加法在還原時間點加回來交易紀錄,見上圖所示,我寫在黑板記錄一下

看參考部落格時還想說為啥要先restore 資料..後來才想通

因此語法真的誤刪(人人似乎都有這個黑歷史)的話,別緊張,Follow以下SOP,應該可以救的回來,前提是資料庫是在完整模式下

 

範例資料:

USE [DBName]
GO
/****** Object:  Table [dbo].[Test]    Script Date: 2020/4/28 上午 08:54:31 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Test](
	[a] [nvarchar](50) NULL,
	[b] [nvarchar](50) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Test] ([a], [b]) VALUES (N'1', N'2')
GO
INSERT [dbo].[Test] ([a], [b]) VALUES (N'4', N'3')
GO
INSERT [dbo].[Test] ([a], [b]) VALUES (N'5', N'6')
GO
INSERT [dbo].[Test] ([a], [b]) VALUES (N'8', N'7')
GO
INSERT [dbo].[Test] ([a], [b]) VALUES (N'9', N'10')
GO
INSERT [dbo].[Test] ([a], [b]) VALUES (N'11', N'12')
GO

原本的資料

不小心誤刪了或漏下了where條件

 

 

 

 

 

 

這個時候,不要緊張,先找到上一次完整備份的時間點

以我的範例是8:55分

Step 0.網站停機或下線 (注意,千萬不能第一時間又做了一次完整備份)

避免寫入更多資料,複原時會遺失更多資料

Step1.切換單人模式並重新離線上線(確保資料庫是無人連線)

ALTER DATABASE DBName
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;

 

Step 1.進行完整的交易記錄備份 (ex.9:00分當下)

這時,你的DB應該會是在還原模式下:

 

Step2.還原上一次的資料備份檔案(記得要比欲複原的時間點更早,ex:AM 08:55那一版本的Bak)

注意:還原計畫只能包含資料,不能勾選到剛剛的交易記錄

還原完整備份的bak時,復原狀態要選擇Restore With No Recovery

 

 

Step3.還原交易記錄,並指定還原時間點(ex:8:56分)

 

還原成功後,再進行將資料庫切回多人模式

ALTER DATABASE DBName
SET Multi_User

檢查資料,已經回復了,可喜可賀

 

 

想法有誤的話,歡迎大家回饋指教

 

 

參考blog:

https://kknews.cc/zh-tw/code/q4lj5x8.html

https://blog.miniasp.com/post/2010/04/21/SQL-Server-Full-Differential-Transaction-Backup

C# 工作平行程式庫的例外處理機制

C# 工作平行程式庫的例外處理機制

最近2個月又重回C#懷抱了,昨晚追查一個底層ThreadPool Exception,然後會造成w3wp的Exception, 會造成集區重啟的異常

後來發現有一個非同步呼叫射後不理的部分,並沒有合理的包裝例外,然後又會造成.net framework底層無法Handle的例外

Legacy Code的用法是改一半的非同步呼叫方法

function method1()
{
       method2();
}

async Task<ResultObj> function method2()
{
       //略
}

後來查詢多方說法後(StackOverFlow網友建議),建議這種射後不理的整合寫法,可以採用TPL(工作型非同步執行庫)

正規TPL的非同步方法在Handle的時候如下:

其例外處理範例,因為Task的例外會拋出來的是AggregateException方法。因此可以例用以下方法來走訪所有的Exception

   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      try
      {
          task1.Wait();
      }
      catch (AggregateException ae)
      {
          foreach (var e in ae.InnerExceptions) {
              // Handle the custom exception.
              if (e is CustomException) {
                  Console.WriteLine(e.Message);
              }
              // Rethrow any other exception.
              else {
                  throw;
              }
          }
      }
   }

 

後續改寫一版後,能明確抓到射後不理的Exception,也不會造成集區崩潰就變成以下的版本了

Task listenerTask = Task.Run(() => proxy.callService<ResultObject>(inputObj)).ContinueWith( 
	t => { logException(t, proxy.ServiceName); }, TaskContinuationOptions.OnlyOnFaulted);

private static void logException(Task<BaseAddOutput> t, string serviceName)
{
	var message = string.Empty;
    if (t.Exception != null)
       message = t.Exception.Message;
    if (t.Exception.InnerException != null)
       message = t.Exception.InnerException.Message;
    Debug.Log(ConfigUtil.GetBoolean("Debug"), "system call " + serviceName + " error, msg:" + message);
}

 

當然,若不是要射後不理的話,建議還是要讓function async / wait化,來確保執行流程順序中的工作都有被完成

 

MS Docs參考

https://docs.microsoft.com/zh-tw/dotnet/standard/parallel-programming/task-based-asynchronous-programming

關於實作秒殺壓力測試的架構驗證

關於實作秒殺壓力測試的架構驗證

之前為了一個案子,實作了一個POC秒殺的情境,透過python、nginx、lucust、docker來模擬,但那時候必竟是架構新手,對於POC驗證的環節

只想的到以下的方式,以下提供參考…若有盲點或不足之處,歡迎指導…

 

一  測試內容

二  測試方法

三  測試方法

四  測試目標

五  測試環境

5-1 壓力測試部署圖

5-2 壓測用戶端與服務端主機規格

六  系統部署

6-1 系統部署架構圖

6-2相關系統容器配置表

七  性能測試結果與分析

7-1 測試步驟與結果摘要

7-2 劇情1 線上用戶30,000,開放10,000個號碼牌

7-3 劇情2線上用戶30,000,開放30,000個號碼牌

7-4 結果與發現

 

 

一  測試內容

本次測試是針對搶票系統進行壓力測試,在秒殺產品的場景中,需求遠遠大於供給,因此考慮巨大流量的情況下,我們在設計交易的接口中,加入了號碼牌機制,從入場、選位到結帳三關卡皆透過號碼牌的控制來監控與管理入場選擇產品的人流。並逐步縮小對於後端API的請求,達到集中運算資源與降低關鍵接口負載壓力的目的。這次我們主要驗證的功能包含了領取號碼牌選位結帳的功能。

 

二  測試方法

本次採用了python開源的分散式壓力測試工具Locust.io,這個工具的特性是可以快速佈署壓測用戶端的集群,並透過統一的Master發動壓力測試,並具備Dashboard,提供效能統計表與分析圖型,近期Amazon Web Service廠商已經善用其可分散式佈署的特性,透過259台用戶端,將壓力測試提升到每秒超過10萬次的後端請求的等級,請參考相關AWS官方連結

 

這次測試採用容器技術動態建立Locust測試集群,並透過Http協議發出GET請求,伺服器端主要透過Nginx+Tornado建立Load Balance與非阻塞式的Web Server,並結合容器技術動態擴充WebServer群,包含主要關鍵流程API,包含取得號碼牌請求、模擬選位請求、模擬結帳請求。關鍵流程之間,透過Redis快取技術協調並模擬用戶狀態(包含入場前、入場選位前、選位後、結帳前),最終透過MongoDB儲存持久化交易結果。

 

三  測試場景

測試場景包含了1取得號碼牌API,其主要目的是第一關控制流量,但也因為要面對第一線最多的請求流量,因此需要的資源最多。其2為模擬選位API,因應用戶取得號碼牌後的狀態變動,我們透過快取保存用戶狀態,以模擬用戶端行為。用戶在具備號碼牌的狀態下,呼叫模擬選位API,會有自動配位的邏輯處理,亂數決定客戶購買1~4張的張數,並動態查詢仍有空位的連續座位區域並隨機決定。此時會透過鎖定機制,保存在快取內,並非同步創建訂單到NoSQL的儲存體中,其狀態為未付款(Pay Status=1),並將此用戶加入到待付款的佇列中。最終為模擬結帳API,此API將會隨機取得佇列中的排隊付款用戶,並同步更新NoSQL的付款狀態(Pay Status=2),此時會標記其用戶已付款完成。

本次測試樣本將模擬銷售單一場次、6區域,依2回合進行測試,分別開放token限制為1萬、3萬,分別模擬請求流量的變化。

四  測試目標

測試獲取單一物理主機的伺服器在反應時間3秒內,其單位承載量RPS為多少,模擬併發用戶為多少。

 

採用號碼牌分流搶票流程下,在上述系統效能需求下,最終全數票券銷售完成到結帳狀態(模擬完成金流後,寫回訂單資料押付款完成的狀態)需要多久的時間。

 

五 測試環境

  5-1 壓力測試部署圖

  5-2 壓測用戶端與服務端主機規格

環境 機器型號 作業系統 硬體cpu 硬體mem
用戶端 Linux虛擬機 CentOS 7 4核 8G
服務端 Linux虛擬機 CentOS 7 4核 8G

 

六  系統部署

6-1 系統部署架構圖

6-2相關系統容器配置表

容器環境 佈署應用程式 數量# 備註
用戶端主機 Locust Master 1 統一發動測試端與測試結果Dashboard
Locust Slave 10 接受發動測試指令,執行測試腳本
服務端主機 Nginx 3 用於3個關鍵流程的Reverse Proxy分流關卡
Tornado(Token API) 5 主要面對最大流量領取號碼牌請求
Tornado(Reserve API) 3 面對已過濾擁有號碼牌的用戶進行選位模擬
Tornado(Admin API) 1 初始化資料預熱

Dashboard監控資訊

開放TokenLimit接口

MongoDb(NoSQL) 1 主要儲存最終訂單持久化與結帳後模擬金流回填狀態。
Redis Cache Server 1 主要處理關鍵流程間狀態控管

 

 

七  性能測試結果與分析

7-1 測試步驟與結果摘要

步驟一.Dashboard 初始化

步驟二.Locust配置與啟動

步驟三.抽樣觀測Server狀態與銷售狀態

步驟四 銷售完畢後,並待所有結帳佇列消耗完成後,搜集Client端數據

 

7-2 劇情1  模擬線上30,000用戶,開放10,000個號碼牌,銷售總票數為10,000張

7-2-1 Locust壓測用戶端統計資訊

開放10,000個號碼牌,銷售總票數為10,000張
模擬線上用戶數 30,000
用戶平均爬升數/秒 2,000
銷售總花費時數 1分40秒
有效訂單數(已付款,已進入數據庫) 3,971
總請求數(擷至全售完) 117,678
總平均RPS(Request per Second) 1,176.8
取號碼牌成功請求總數(HttpStatus=200) 32,268
模擬選位成功請求總數(HttpStatus=200) 10,903
模擬結帳成功請求總數(HttpStatus=200) 7,610
服務器狀態 全數存活

 

 

 

7-2-2 Locust壓測期間趨勢圖

7-2-3容器正常服務

 

7-3 劇情2  模擬線上30,000用戶,開放20,000個號碼牌,銷售總票數為10,000張

7-3-1 Locust壓測用戶端統計資訊

開放20,000個號碼牌,銷售總票數為10,000張
模擬用戶數 30,000
用戶平均爬升數/秒 2,000
銷售總花費時數 2分17秒
有效訂單數(已付款,已進入數據庫) 3,962
總請求數(擷至全售完) 156,029
總平均RPS(Request per Second) 1,138.9
取號碼牌成功請求總數(HttpStatus=200) 36,606
模擬選位成功請求總數(HttpStatus=200) 14,776
模擬結帳成功請求總數(HttpStatus=200) 6,984
服務器狀態 全數存活

 

 

7-2-2 Locust壓測期間趨勢圖

7-2-3容器正常服務

 

7-4 結果與發現

從相同的壓測用戶參數,並透過號碼牌來控制流程API的流量,我們發現號碼牌發放的控制,可以有效提升服務效率,銷售的時間更短。同時,這次銷售10,000張票在我們配置的架構下(Nginx與Tornado),單台主機可以支撐爆量請求而不會造成應用程式崩潰與異常銷售,仍然可以將其票券全數售完,並正常寫入資料庫。而本次透過容器建置所有的系統與API,說明未來管理上雲或是自建機房(IDC)可以在即短的時間內佈署。同時具備易用與易於擴充的特性。而切分微服務的概念,除了在系統佈署的配置上可以更彈性的運用,且未來更有機會隨時提升到Serverless的層次,透過雲端全託管的服務,可以有效降低維運成本。

 

看了[為你自己學Git]的常用Git指令筆記

看了[為你自己學Git]的常用Git指令筆記

看了為你自己學Git,五倍紅寶石,高見龍大師著作(這本真的釐清很多我對git一知半解的觀念,大推)

 

以下整理筆記,提供日後個人查詢使用~

常用指令

git add (陷阱觀念:p38),add完再edit file,新的變更不會進入暫存區,必須再執行一次add
註:若針對os指令做檔案操作,變更都要透過git add,加入暫存區,若是使用git mv、git rm的話,是git在背後幫我們做掉2段式動作
git status 
git commit -m "變更訊息"
git log (純粹訊息時間)
git log -p (多列出版本之間差異)
git log -g (reflog)
git log --oneline 
git log --oneline --since="" --until=""
git log --oneline --grep="查詢log message字眼"
git log -S "檔案內容指定字眼"

git rm <file> --cached(僅從git移除管控,但會保留檔案,變成untracked file)

git commit --amend -m "修改最後一次commit的訊息"
git commit --amend --no-edit    (不修改訊息,併入變更到最後一次修改-變更須先add到暫存區)

git blame -L 1,5 <file> (顯示第1到第5行的變更簽入者, 抓戰犯XD)

.gitignore可管理不要被git,忽略這個規則,強迫加入的話
git add -f <filename>

git clean -fX(一口氣刪除被忽略的檔案)


不小心本地刪掉檔案,可以用checkout救回,git checkout <file> or . (取回檔案)

git reset --hard(回到head變更狀態)

git reset <commit>^ 拆掉指定commit,回到前
1次,^^代表上2次,也可以使用~,~1代表上一次,~5代表前5次

git reset mode有 --mixed, --soft --hard
            工作目錄    暫存區
mixed     不變          丟掉
soft         不變          不變 (僅移動head)
hard        丟掉          丟掉

如果不小心使用hard模式 reset了某個commit,救的回來嗎?
git reset <之前的commit> --hard(--hard可以強迫放棄reset之後修改的檔案)
若忘了git 的commit,這時就使用git reflog或是git log -g可以查到之前的commit

待續
在docker安裝pyodbc以連線到MSSQL的步驟

在docker安裝pyodbc以連線到MSSQL的步驟

在windows上,python要連線到mssql,只需要透過pyodbc,幾乎不用什麼設定,就可以輕鬆連線上mssql

但是在linux上,遇到的坑與血淚,相信前人遇到的已經太多了!

以下記錄一下步驟與眉角:

首先我們先假設已經有一個存在的docker container在運作了,裡面有基本python 3.6的環境(或其他版本,這邊以3.x為主,自行上docker hub找吧…)

連進去container後,有3大工程要施作…

1.安裝freetds

wget  http://ibiblio.org/pub/Linux/ALPHA/freetds/stable/freetds-stable.tgz

tar zxvf freetds-stable.tgz

cd freetds-0.91/

./configure --with-tdsver=7.1 --prefix=/usr/local/freetds0.91 --build=x86_64-pc-linux-gnu --host=x86_64-pc-linux-gnu --mandir=/usr/share/man --infodir=/usr/share/info --datadir=/usr/share --sysconfdir=/etc --localstatedir=/var/lib --libdir=/usr/lib64 --without-ldap --without-tcl --enable-pkinit --enable-thread-support --without-hesiod --enable-shared --with-system-et --with-system-ss --enable-dns-for-realm --enable-kdc-lookaside-cache --with-system-verto --disable-rpath --with-pkinit-crypto-impl=openssl --with-openssl

make

make install

 cat >> /usr/local/freetds0.91/etc/freetds.conf
加入
[TestDB]
host = mesotest.database.windows.net
port = 1433
tds version = 7.0

註:freetds.conf 的dump file = /tmp/freetds.log反註解,global的tds版本也要改成7.0一致的版本,有dump log的話,後續連線失敗的話,可以看的到錯誤原因,事半功倍

例: severity:9, dberr:20002[Adaptive Server connection failed], oserr:0[Success] –>tds版本問題,要調整,若8.0不行,就7.2->7.1->7.0往回裝

2.測試freetds連線

/usr/local/freetds0.91/bin/tsql -S TestDB -U app_user@mesotest.database.windows.net -P {password} -D test1

若freetds可以連線,也可以查詢的話,應該會像這樣:

可以下sql指令,也回傳的了資料集

2.設定ODBCInit

apt-get install unixodbc-dev
apt-get install python-pip

pip install pyodbc
#yum install gcc-c++

#關鍵中的關鍵
find /usr -name "*\.so" |egrep "libtdsodbc|libtdsS"
 #/usr/lib/libtdsS.so 
 #/usr/local/freetds0.91/lib/libtdsodbc.so

# cp /etc/odbcinst.ini /etc/odbcinst.ini.20160102

# cat >> /etc/odbcinst.ini

[SQL Server]
Description = FreeTDS ODBC driver for MSSQL
Driver = /usr/local/freetds0.91/lib/libtdsodbc.so
Setup = /usr/lib/libtdsS.so
FileUsage = 1

# 檢查一下驅動
# odbcinst -d -q
[SQL Server]

cat >> /etc/odbc.ini
[TESTDSN]
Driver          = SQL Server
Server          = xxx.xxx.xxx.xxx
User            = xxxx
TDS_Version     = 7.0
Port            = 1433

3.執行簡單的python連mssql程式

import pyodbc

conn =  pyodbc.connect("driver={SQL Server};server=mesotest.database.windows.net;PORT=1433 database=test1;UID=app_user@mesotest.database.windows.net;PWD=%s;TDS_Version=7.0;" % "{yourpassword}" )
cursor = conn.cursor()

query = "select getdate()"

print(query)
cursor.execute(query)
row = cursor.fetchone()
while row:
    print(str(row[0]))
    row = cursor.fetchone()

執行成功,我要哭了…凌晨3點了!!

根據網友們的分享,這裡還有一個很大的坑就是連線字串要包含TDS_Version的資訊,版本要跟freetds內配置的版本一樣…

否則就會陷入無限的…08001輪迴,而不知其所以然…

Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
pyodbc.Error: (‘08001’, ‘[08001] [unixODBC][FreeTDS][SQL Server]Unable to connect to data source (0) (SQLDriverConnect)’)

 

關鍵2篇REF

https://blog.csdn.net/samed/article/details/50449808

http://www.voidcn.com/article/p-vaxmczdi-dc.html

locust + nginx + tornado web server 壓測客戶端與服務端 自動化腳本與實測結果

locust + nginx + tornado web server 壓測客戶端與服務端 自動化腳本與實測結果

壓測是考驗整體架構吞吐量與穩定性的最直接方式

繼上一篇 找到了一個壓測新朋友Locust 傳送門

這一篇進一步透過自動化shell,可以加速建置你的壓測Client端集群與Server端集群

首先我們要租2台主機,讓其物理cpu、ram網卡獨立,然後可以預設在相同的DataCenter,以降低跨DataCenter的網路影響因素

整體壓測概念如下:

 

Initail #number of web server Shell Script

#!/bin/bash

read -p "Enter your webserver number to remove: " p_clear_count

for (( i=1 ; ((i < ($p_clear_count+1))) ; i=(($i+1)) ))
do
  sudo docker rm -f webtest$(printf 0%02d $i)
done;
sudo docker rm -f web-test-nginx

read -p "Enter your webserver number: " p_count

for (( i=1 ; ((i < ($p_count+1))) ; i=(($i+1)) ))
do
  sudo docker run -d --name webtest$(printf 0%02d $i) --network="webtest" -p $(printf 100%02d $i):6969 tornado-web-test
done;

sudo docker run --name web-test-nginx --network="webtest" -p 10000:10000 -v /mnt/nginx/conf.d/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx nginx-debug -g 'daemon off;'

Initail #number of locust client Shell Script

#!/bin/bash

Test_Url="http://{{hostname}}:10000"

read -p "Enter your locust client number to remove: " p_clear_count

for (( i=1 ; ((i < ($p_clear_count+1))) ; i=(($i+1)) ))
do
  sudo docker rm -f locust-slave$i
done;
sudo docker rm -f locust-master

read -p "Enter your locust client number: " p_count

sudo docker run -d --name locust-master --hostname locust-master \
 --network="webtest" \
 -p 8089:8089 -p 5557:5557 -p 5558:5558 \
 -v /mnt/webtest/locust-master:/locust \
 -e LOCUST_MODE=master \
 -e ATTACKED_HOST="$Test_Url" \
 grubykarol/locust

for (( i=1 ; ((i < ($p_count+1))) ; i=(($i+1)) ))
do
  sudo docker run -d --name locust-slave$i \
	 --network="webtest" \
	 --env NO_PROXY=locust-master \
	 -e ATTACKED_HOST="$Test_Url" \
	 -v /mnt/webtest/locust-slave:/locust \
	 -e LOCUST_MODE=slave \
	 -e LOCUST_MASTER=locust-master \
	 --rm grubykarol/locust
done;

 

實測下來,8g開10個container(共享)的情況下,估計10000 concurrent user應該已經是很極限了,其實我們會發現極限應該是會在client發出請求端

web server多少會因為os、網卡、容器網路的限制,導致同時connection數無法無限上崗,而web server的運作還不包含更複雜的運算情境(只考慮in ram處理與非同步mongodb log)

因此以此為基準來當作未來擴充的計算基礎參考,應該還ok,若要模擬更複雜的商業邏輯運作,那麼可以仿照此作法去刻

10k

20k (RPS反而降了,看來還是有其極限存在)

美中不足的就是怎麼動態改Nginx的Nginx.Conf,把ReversProxy動態換掉,這個以後再研究吧…

 

100k on AWS
Reference:
1.https://aws.amazon.com/tw/blogs/devops/using-locust-on-aws-elastic-beanstalk-for-distributed-load-generation-and-testing/
2.https://www.slideshare.net/AmazonWebServices/aws-reinvent-2016-how-to-launch-a-100kuser-corporate-back-office-with-microsoft-servers-and-aws-win303?from_action=save

壓測工具的新選擇!! Locust load test framework

壓測工具的新選擇!! Locust load test framework

繼上一篇,自建nginx分流後(傳送門),接著的問題就是要如何驗證分流效果與實際上這樣架構的負載吞吐量到底能到多少了。傳統想到壓測,我們就會想到jmeter,但是jmeter的參數配置繁多,而且若要做到可程式化的話其路可能相當曲折;再者,若在我們實體主機上測試的話,極有可能受限於其物理極限,包含頻寬與網卡的能力,因此我搜尋了一下python有沒有load test framework,又可以同時支援分散式的load test

 

結果發現了這套名叫:locust的套件!!其安裝方式相當的簡單:

首先,整份框架都是python寫的,透過pip就可以安裝,因此先來試著打包成映像檔,變成隨時可以使用的容器吧:

測試Script寫起來相當簡潔,我們直接存成locustfile.py,實作HttpLocust、TaskSet後,屆時會在locust的壓測ui介面,讀取到接口

from locust import HttpLocust, TaskSet, task


class WebTest(TaskSet):

    @task
    def get_uuid(self):
        self.client.get("/webtest?action=get_uuid")


class WebsiteTest(HttpLocust):
    task_set = WebTest

Dockerfile的內容:

FROM python:3.6-alpine

COPY docker-entrypoint.sh /

RUN    apk --no-cache add --virtual=.build-dep build-base \
    && apk --no-cache add libzmq \
    && pip install --no-cache-dir locustio==0.8.1 \
    && apk del .build-dep \
    && chmod +x /docker-entrypoint.sh

RUN  mkdir /locust
WORKDIR /locust
EXPOSE 8089 5557 5558

ENTRYPOINT ["/docker-entrypoint.sh"]

docker-entrypoint.sh的內容:

#!/bin/sh
set -e
LOCUST_MODE=${LOCUST_MODE:-standalone}
LOCUST_MASTER_BIND_PORT=${LOCUST_MASTER_BIND_PORT:-5557}
LOCUST_FILE=${LOCUST_FILE:-locustfile.py}

if [ -z ${ATTACKED_HOST+x} ] ; then
    echo "You need to set the URL of the host to be tested (ATTACKED_HOST)."
    exit 1
fi

LOCUST_OPTS="-f ${LOCUST_FILE} --host=${ATTACKED_HOST} --no-reset-stats $LOCUST_OPTS"

case `echo ${LOCUST_MODE} | tr 'a-z' 'A-Z'` in
"MASTER")
    LOCUST_OPTS="--master --master-bind-port=${LOCUST_MASTER_BIND_PORT} $LOCUST_OPTS"
    ;;

"SLAVE")
    LOCUST_OPTS="--slave --master-host=${LOCUST_MASTER} --master-port=${LOCUST_MASTER_BIND_PORT} $LOCUST_OPTS"
    if [ -z ${LOCUST_MASTER+x} ] ; then
        echo "You need to set LOCUST_MASTER."
        exit 1
    fi
    ;;
esac

cd /locust
locust ${LOCUST_OPTS}

單一台load test測試端 啟動指令 (驗證ok):

sudo docker run --name locust-master --hostname locust-master \
--network="webtest" \
-p 8089:8089 -p 5557:5557 -p 5558:5558 \
-v /home/paul/webtest/locust:/locust \
-e ATTACKED_HOST='http://web-test-nginx:10000' \
grubykarol/locust

分散式的測試端配置(待驗證):

啟動指令-master測試端:

docker run --name master --hostname master `
 -p 8089:8089 -p 5557:5557 -p 5558:5558 `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST='http://master:8089' `
 -e LOCUST_MODE=master `
 --rm -d grubykarol/locust

啟動指令-slave測試端:

docker run --name slave0 `
 --link master --env NO_PROXY=master `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST=http://master:8089 `
 -e LOCUST_MODE=slave `
 -e LOCUST_MASTER=master `
 --rm -d grubykarol/locust
docker run --name slave1 `
 --link master --env NO_PROXY=master `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST=http://master:8089 `
 -e LOCUST_MODE=slave `
 -e LOCUST_MASTER=master `
 --rm -d grubykarol/locust

 

容器執行後,打入http://xxx.xxx.xxx.xxx:8090,就可以連到對應的監控網址(這個是最棒的),一開始就會問你模擬的u數,以及你希望多久時間內要衝到該u數

摘要總表:

即時圖表:

錯誤分析:

 

同時,我去驗證了我們mongodb的log,是符合他load test的請求數量

 

以上是這次接觸到新的壓測工具實作的小小記錄,也推薦給大伙

 

參考:

https://locust.io/

https://docs.locust.io/en/stable/

https://medium.com/locust-io-experiments/locust-io-experiments-running-in-docker-cae3c7f9386e

 

 

 

WP Facebook Auto Publish Powered By : XYZScripts.com