1. 程式人生 > >【G】開源的分散式部署解決方案(一)

【G】開源的分散式部署解決方案(一)

做這個開源專案的意義是什麼?(口水自問自答,不喜可略過)

從功能上來說,請參考 預告篇,因自知當時預告篇沒有任何含金量,所以並沒有主動推送到首頁,而是私下的給一些人發的。

從個人角度上來說,我希望.net的環境會越來越好,就我自己的成長曲線是從mxdn開始自學、cxdn嘗試解答問題、部落格園讀別人部落格再到自己寫部落格、最後到工作中經常使用到的stackxxxxflow、gxxgle。這其中我當然是走了很多彎路,踩過無數的坑,也埋了無數的坑。現在自己有了一點小小的能力,想盡自己所能,通過一個專案整理出來給大家參考、學習的同時也能給自己一些意見。對新人來說也算是一種回饋,對老人來說也算是互相切磋互相提升。

什麼是分散式部署解決方案?

部署也有很多人叫釋出。那正常的流程是什麼樣的呢?

相信這張圖大家都不會太陌生。一般少數幾臺伺服器的時候直接這樣釋出是最省時省力的。

但是,如果有一天,你的伺服器變成幾十臺,然後又面臨著經常釋出的情況該怎麼辦呢?

@echo off
echo Start to Publish ApplicationName All Site
set local="C:\inetpub\ApplicationName"
Set RemotePath=C$\inetpub\ApplicationName

For %%I in (192.168.0.2 192.168.0.3 ... 192.168.0.n) do
( echo "Publish ApplicationName %%I" net use \\%%I\ipc$ Password /User:UserName For %%P in (default) do ( echo "Publish \\%%I\%RemotePath%\"dd xcopy /e /y %local% "\\%%I\%RemotePath%\" ) net use \\%%I\ipc$ /del ) :End pause;

上面這段程式碼,相信也有一部分人用過吧。使用流程也比較簡單,先用vs釋出到本地,然後再使用批處理檔案,通過釋出命令來進行多臺網站的部署。

突然有一天,你是上司跟你說,你的這段批處理有很多問題,比如不利於維護、沒有部署記錄、釋出後首次訪問很慢、沒有版本管理、部署過程一旦發生異常也沒有容錯機制等等。

是的,這些問題都還是基礎的。如果你的伺服器數量達到一定級別以後,你的部署會變得異常艱難。某臺伺服器上部署了多少個服務、某幾臺伺服器是一組,這些會讓你抓狂,甚至想說髒話,就如同下圖。

由於後面還會有一系列文章要出,此處就不再過多的解釋,當你看到完整的專案之後,相信你會對它有一個新的認識。

第一個正式版包含哪些功能?

請允許我偷個懶,從預告篇把尚不成熟的功能列表copy一份過來:

1 基礎的許可權控制(可能只支援一個角色,甚至是寫死)

2 部署專案管理(以Jenkins為例,做自動化構建方案)

3 部署伺服器管理(以Aliyun伺服器為例)

4 部署組管理(包括部署組、部署組項)

5 建立部署任務(包括觸發自動化構建、部署檔案關聯等)

6 部署伺服器常駐服務(以Windows Service的形式駐留,主要完成中控的指令協助部署)

7 一鍵部署(支援單臺或部署組)

  7.1 支援手動繫結歷史部署檔案版本(理論上支援所有的,如Jenkins這種構建方案還會涉及到儲存構建副本數量和天數等,另本功能只支援單臺部署,部署組的修改版本通過建立部署任務時統一指定)

  7.2 自動檢測部署檔案包狀態(以Jenkins為例,會自動等待構建完畢,並會自行檢測構建狀態判斷是否繼續向下執行)

  7.3 下載部署檔案包並解壓

  7.4 控制負載均衡降權

  7.5 部署(包括等待伺服器正在執行的請求完成的檢測)

  7.6 觸發快取(也可以理解為觸發首次訪問,提升使用者體驗)

  7.7 控制負載均衡升權

PS:其中為了支援單臺伺服器部署,所以一些擴充套件性功能會採用外掛(暫定)的形式允許自行實現,同時也會包含一套以阿里雲、jenkisn為基礎的示例實現。

後續也會考慮把一些功能提取出來做成API,允許把功能嵌入到自己的系統裡去。當然,這個要看有多少人需要了。

核心技術與開發工具

後端:.net 4.6.1,EF6,mvc5(不上.net core的原因是1.1.0目前並不穩定,還是有不少bug,我真的親身踩到了,實在是不想無限踩過去,請原諒我沒有勇於踩坑的心,確確實實是精力有限)

前端:AdminLTE 2 支援PC、Mobile雙端

資料庫:SQL Server LocalDB

IDE:Visual Studio 2017 RC

原始碼託管:oschina git

現在做到哪一步了?

先上一張解決方案資源管理器的截圖,簡單的看下專案結構。

可以看到,目前只有一個專案,而且比較亂,Business、Data、Models 各種亂入。

這麼低階的一個專案怎麼好意思開源的?

是的,沒錯,目前專案狀態就是這樣。

因為我要做的不是釋出出來一個成品,寫個介紹就完了。

我會從0程式碼開始在git上保留整個過程。且根據進度不定期更新博文來講解編碼過程中的各種問題以及解決思路。

而下一篇的名字我也已經想好了。

【G】開源的分散式部署解決方案(二) -  好專案是從爛專案基礎上重構出來的

在我的這個開源系列部落格中,你可以看到一個開源專案的完整成長曆程。我不知道這個專案會走多遠,但我堅信這個專案一定會幫到一部分人,這就足夠了。

下面放幾張截圖,也是下一篇內容的主角。

(簡單的登入頁)

(後臺UI的基礎框架)

(已完成的一個功能,部署伺服器管理)

(支援手機端的table)

登入的實現

為了避免太水的嫌疑,上一點點乾貨。

新建專案時,點選Change Authentication會看到4個選項。至於如何選擇我就不多說了,我先說說我選了 Identity(Individual User Accounts)踩過的坑。

因為我之前主要是做架構方向的,可以理解為這些都是所謂的“小弟”在做的功能,而我一個不小心就掉進了陷阱裡去。說是陷阱吧,其實主要是因為自己不熟悉,畢竟mvc還是比較龐大的,即便你順著它的思路去做,也未必能做得對。

預設情況下生成好的專案是已經使用推薦的EF6實現了整個登入流程。而我又希望自己可以掌控關鍵的驗證邏輯,所以一路摸下去把 ApplicationUser、AppplicationUserManager、UserManager、UserStore都看了一遍,經過各種嘗試,發現驗證邏輯封裝在Microsoft.AspNet.Identity.EntityFramework(這個是盲打的,已經回不去了,如果錯了歡迎指正)。

找到了目標當然第一想法是功課他,隨著不斷的踩坑發現工作量異常的大,遂放棄。想從另外一個角度入手,因為UserManager才是核心,那我順著這條線是否可以把他已經實現的EF那套開給剔除掉。

於是就有了下面的程式碼:

首先要改造Model,於是自己把 ApplicationUser、IdentityUser、UserStore都重寫了。前面兩個比較簡單,重點是UserStore,要實現IUserStore<TUser>這個介面,這裡是自行編寫驗證邏輯的轉折點。

其次,Controller也進行的小改造,去掉了SignInManager等,只保留了UserManager,同時Startup.Auth裡面的DbContext也換成了自己的。

最後把Name也一同存入了Cookie,方便layout裡面要顯示使用者名稱。

核心程式碼如下:

1.IdentityUser 其實這個類可以跟 ApplicationUser合併的,我也是跟官方生成專案時的寫法保留下來的。可能故意分開在ApplicationUser裡面寫的是邏輯,而IdentityUser是對屬性的定義,這樣會顯得更清晰一點吧,雖然我也會這麼做,也很熱衷於這麼做,但區別是我不會讓ApplicationUser繼承IdentityUser,而是寫一個Business來實現方法做一個徹底的拆分。

    public class IdentityUser : IUser<string>
    {
        public string Id { get; set; }

        public string Name { get; set; }

        public string Password { get; set; }

        public string Role { get; set; }

        public string UserName { get; set; }
    }

2.UserStore其實並沒有多少借鑑的意義,因為這是很小學生的寫法了,主要是為了突出UserStore的重要性。

    public class UserStore : IUserStore<ApplicationUser>
    {
        public async Task CreateAsync(ApplicationUser user)
        {
            using (var dbContext = GDbContext.Create())
            {
                dbContext.User.Add(new User()
                {
                    UserName = user.UserName,
                    Password = user.Password,
                    Name = user.Name,
                    Role = user.Role,
                });

                await dbContext.SaveChangesAsync();
            }
        }

        public Task DeleteAsync(ApplicationUser user)
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            //nothing to do
        }

        public Task<ApplicationUser> FindByIdAsync(string userId)
        {
            int id = 0;
            if (!Int32.TryParse(userId, out id))
            {
                throw new ArgumentException(nameof(userId));
            }

            using (var dbContext = GDbContext.Create())
            {
                var user = dbContext.User.SingleOrDefault(u => u.ID == id && !u.IsDelete);

                return Task.FromResult(new ApplicationUser()
                {
                    Id = user.ID.ToString(),
                    Name = user.Name,
                    Password = user.Password,
                    Role = user.Role,
                    UserName = user.UserName
                });
            }
        }

        public Task<ApplicationUser> FindByNameAsync(string userName)
        {
            using (var dbContext = GDbContext.Create())
            {
                var user = dbContext.User.SingleOrDefault(u => u.UserName == userName && !u.IsDelete);

                return Task.FromResult(new ApplicationUser()
                {
                    Id = user.ID.ToString(),
                    Name = user.Name,
                    Password = user.Password,
                    Role = user.Role,
                    UserName = user.UserName
                });
            }
        }

        public Task UpdateAsync(ApplicationUser user)
        {
            throw new NotImplementedException();
        }
    }

 3.Startup.Auth ConfigureAuth方法進行了如下的改動,主要是修改 DbContext 的獲取,以及SecurityStampValidator.OnvalidateIdentity這個方法的引數調整。

           // 配置資料庫上下文、使用者管理器和登入管理器,以便為每個請求使用單個例項
            app.CreatePerOwinContext(GDbContext.Create);
            app.CreatePerOwinContext(() => new UserManager<ApplicationUser>(new UserStore()));

            // 使應用程式可以使用 Cookie 來儲存已登入使用者的資訊
            // 並使用 Cookie 來臨時儲存有關使用第三方登入提供程式登入的使用者的資訊
            // 配置登入 Cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // 當用戶登入時使應用程式可以驗證安全戳。
                    // 這是一項安全功能,當你更改密碼或者向帳戶新增外部登入名時,將使用此功能。
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager<ApplicationUser>, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });            

說起來簡單,但真的做起來還是踩過了大大小小的坑,即便是你覺得很不起眼的一個功能,都有可能折騰的你要死要活。

我會告訴你我新建專案、刪除專案、再新建、再刪除,這個過程已經重複好多遍了嗎?

當你看到一段好的程式碼在你面前時,又有幾個人能體會寫的人死了多少腦細胞?

我不會拒絕伸手黨,但我希望伸手的同時能再點個贊,你的支援是無數同行堅持在這條路上的動力。

宣告

1.在出第一個正式版之前不會使用任何開源協議

2.永遠免費,不會出什麼專業版、旗艦版之類的進行收費

3.第一個版本完結時間目前還不知道,我這個人懶散慣了,但我會盡量多抽出一些時間儘快寫出來。

最後,本專案暫定名為 G,唯一群號:7424099

Git地址:http://git.oschina.net/doddgu/G   (希望大家可以順手點個star,如果有喜歡的朋友捐贈下就更好了,感謝各位的支援)