最近一直在研究微服務體系架構。微服務概念一直很火,但是作為一個初學者往往迷失在高深理論與紛繁多樣的技術,而失去了方向,慢慢的,_,還沒開始就已經放棄了。是以還是不得不誇一誇微軟一切以開發者為中心的價值觀:好文檔,好工具。
1.微服務學習線路
1.1 開卷有益
首先我們從微軟的微服務架構的白皮書(中文版,英文版)入手,開卷有益,這是一本無關平台,學習微服務,了解微服務的好書,雖然技術是.NET,但是書中更多的内容是介紹的微服務思想,理論,最佳實踐,其他平台同樣适用,适用于我們整體把控微服務架構體系中的核心問題:
- 微服務之間的通信
- 網關
- 身份認證與授權
- 資料庫服務
- DDD
- CQRS
雖然不一定能夠全部了解書中的所有理論概念,但是總能給到一些啟發,開拓思維。
1.2 實際項目
然後就是微軟架構師利用.net core技術,基于docker容器技術,實作的适用于容器化 .NET 應用程式的體系結構微服務架構demo項目-eShopOnContainer,這個項目在上面的白皮書中也有介紹。
下面大概介紹一下這個項目的架構,雖然是一個demo,其中有部分具有一定的局限性且并不适合生産環境,但是這并不妨礙我們去了解微服務體系架構。
eShopOnContainer
是一個在用戶端、服務端同時可以跨平台的項目。這都得益于 .NET Core能夠跑在不同系統的容器上,windows或者linux。項目還有
Xamarin
移動APP,
ASP.NET Core Web MVC
和一個
SPA
。
eShopOnContainer
的架構,是一種面向微服務體系架構的實作。這些微服務都是可以自我治理的:
- 每個微服務有屬于自己的資料庫。
- 每個微服務都有簡單的CRUD方法、和精細的DDD/CQRS模式方法。
- 用戶端和微服務通過HTTP協定進行資料交換
- 微服務之間通過異步消息進行通信
- 消息隊列可以通過RabbitMQ或者AzureAzure Service Bus去傳遞內建事件。
事件總線
項目中有一個簡化的事件抽象總線,來處理內建事件。這個抽象事件總線在項目中有兩個實作:
- RabbitMQ
- Azure Service Bus
這裡對于生産級别的解決方案,微軟建議使用更加健壯的元件。
API 網關
整體架構中還包括了API網關和BFF模式的實作:
- 釋出簡化的API
- 在外部消費者和内部微服務之間增加安全措施,以此對外隐藏并保護内部微服務
這些API網關是通過Envoy實作的,我順帶翻閱了下官網,使用Envoy的公司還比較多,基本都是耳熟能詳,
Uber
,
ebay
,
airbnb
amazon
Google
IBM
Microsoft
,還有騰訊等等。在架構中,Envoy實作的網關,隻執行向内部微服務和自定義聚合器的請求轉發,進而為用戶端提供單一基本的URL.其實還可以通過Envoy實作:
- 在gRPC于HTTP/REST之間的自動轉換
- 身份驗證
- 授權管理
- 緩存支援
項目中,除了API網關之外,還提供了一組“自定義聚合器”。這些聚合器為某些操作的用戶端提供了一個簡單的API。
- 移動購物:購物操作的聚合,供XamarinAPP調用
- PC購物:購物操作的聚合,供Web用戶端調用,(mvc與spa)
之前eShop使用的是Ocelot實作網關的。對于Ocelot,官方給的說法是欲抑先揚:Ocelot很好,很優秀,也是.net core 優秀的開源項目,也支援許多特性,它可以作為.net core項目網關實作候選元件。但是,Ocelot缺乏對gRPC的支援,是以在最新的項目(這個eShopOnContainer項目一直在疊代更新與維護,從衆多分支就可以看出)中就換為Envoy提供網關服務。
自定義聚合器
這個主要用于公開一個具有涉及内部各個微服務之間的複雜方法的HTTP/JSON API,每個自定義聚合器的方法都能調用1個或者多個内部微服務,根據邏輯聚合多個結果并提供給用戶端。從聚合器到微服務的調用的都是使用gRPC
gRPC
在衆多微服務之間,大多數微服務都是通過事件總線和釋出者/觀察者模式進行異步通信。但是,自定義的聚合器和内部微服務之間的同步通信是用gRPC實作的。gRPC是一種基于RPC的協定,具有良好的性能,帶寬占比也低,是内部微服務通信協定中的最佳候選協定。項目中使用了4個網關實作BFF,目前它們是通過
Envoy
來實作的。每個BFF為其用戶端提供一個唯一的端點,然後将調用轉發到特定的微服務或自定義聚合器。
- 1.用戶端通過Envoy代理暴露的URL調用BFF.
- 2.通過請求資料,Envoy轉發請求至内部的微服務(簡單的增删改查),或者複雜的聚合器(複雜邏輯),這對用戶端都是透明的。
當調用直接從Envoy轉發到内部微服務時,它是使用HTTP/JSON執行的。也就是說,現在内部微服務公開了一組混合的方法:
- 一些走gRPC(由聚集器調用)
- 一些走HTTP/JSON中(由Envoy調用)。
這裡微軟官方進行了展望"這可能會在未來發生變化”,即所有的微服務方法都可以使用gRPC,如果需要,Envoy可以在gRPC和HTTP/JSON之間自動轉換。
微服務内部架構模式
不同類型的微服務可能采用不同的内部架構模式和方法,這取決于微服務的用途。
- 4個
,部署在同一個容器内SQL Server
主要是降低記憶體的需求,生産部署不建議這樣做,應該使用High-availability的解決方案。
- 1個
執行個體,單獨一個容器Redis
-
MongoDb
Redis
和
MongoDb
都是單獨的容器,作為兩個廣泛使用的NO-SQL資料庫的示例。
其他
項目中除了,上面的架構内容,還有DDD領域驅動開發(Domain Drive Design),CQRS指令與查詢職責分離(Command and Query Responsibility Segregation)的實踐,日志,健康檢查等内容。是以涵蓋的範圍蠻廣,個人覺得非常值得研習。
2.容器化 .NET 應用程式的開發調試
鋪墊了這麼多,終于要進入本篇文章的主題,對于我們的微服務化的應用,我們可以說,我們的應用都是跑在容器上的,或者說我們所有的微服務都跑再容器上(當然容器指的就是docker,docker容器幾乎成為了行業标準)。我們如何進行開發呢,這裡再誇一下微軟,在白皮書中有
Docker 應用開發工作流
- 編碼:建立應用
- 為應用建立Dockerfile
- 建立自定義docker鏡像
- 定義docker-compose.yaml
- 建構并運作docker應用
- 測試docker應用(微服務)
- 推送代碼送出或者繼續開發
下面就将開始把我的一個應用以容器的方式跑起來,根據上面的工作流進行實踐與書寫
項目概述
這本身是一個公司推送集中平台,接受公司多個産品線的推送請求,然後通過阿裡進行移動推送,然後每一次推送都有背景記錄,進行存儲。由于我接下來實踐的Docker應用開發的工作流,是以實際隻有一個webapi項目,也并不打算去拆分,這對我們實踐意義也不太大。
我們的目标
開發環境拆分為多個docker容器,且調試時能夠正常運作。
2.1 安裝docker-desktop
docker引擎需要運作在linux上,那麼win10就需要裝裝虛拟機:
hyper-v
,實際上docker是跑在這個虛拟機上,windows上的docker适用于測試和開發。生産環境還是linux哈。
即便是win10也請注意下版本:
Docker Desktop requires Windows 10 Pro/Enterprise (15063+) or Windows 10 Home (19018+).
- 下載下傳:https://www.docker.com/get-started
- 安裝:傻瓜式點下一步
- 設定
Resources ADVANCED
選擇虛拟機cpu顆數,記憶體大小

Resources-FILE SHARING
docker容器能夠通過volume挂載主控端作業系統(linux)的檔案目錄或目錄,宿主作業系統在Windows的Docker Desktop中,就是指是 Hyper-v 裡的 Linux 系統。但是,如果隻能從hyper-v中的linux系統中進行挂載,顯然不足以達到我們的需求,最友善的方式肯定是直接從Hyper-v的宿主windows裡挂載檔案咯。(有點繞,多了解下,windows>hyper-v>docker) 最終效果:Docker 容器直接挂載主機系統的目錄,我們可以先将目錄挂載到虛拟 Linux 系統上,,再利用 Docker 挂載到容器之中。這個過程被內建在了 Docker Desktop 系列軟體中,我們不需要人工進行任何操作,整個過程已經實作了自動化。這就是FILE SHARING選項的意義。如果還不好了解,往下看。
Docker Engine
配置阿裡雲鏡像加速器,使用加速器可以提升擷取Docker官方鏡像的速度,親測還是有用,但是,2018 年五月之後,微軟将後續釋出的所有
docker image
都推送到了 自家的
MCR (Miscrosoft Container Registry)
,但在中國大陸,由于衆所周知的原因,它的速度實在是令人發指。後續有解決方案,文章會講到。
2.2 編碼-建立我們的應用
由于項目是現成的,那麼這一步我們可以省略,這個跟您開發一個webapi項目沒有任何差別,原來怎麼做的,現在還是怎麼做。我隻說一個關鍵點,那就是資料初始化,我們的推送資料需要存入資料庫中,你也可以等mysql容器啟動後,再去初始化容器中的mysql資料庫,但是我們能用代碼一步到位:
在
program.cs
:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AliMobilePush.Webapi
{
public class Program
{
public static void Main(string[] args)
{
//CreateHostBuilder(args).Build().Run();
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
//...CreateHostBuilder
}
}
SeedData.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace AliMobilePush.Infrastructure
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new PushContext(
serviceProvider.GetRequiredService<
DbContextOptions<PushContext>>()))
{
context.Database.EnsureCreated();
context.SaveChanges();
}
}
}
}
2.3 為應用建立Dockerfile
無論是通過
Visual Studio
自動部署,還是通過
Docker CLI
。都需要為應用建立
Dockerfile
。一般情況,Dockerfile是放到應用或者服務的根檔案夾下。這裡有三種方式建立dockerfile。
- 建立項目時,勾選Enable Docker Support項
- 已經建立好的webapi項目,右鍵 Solution Explorer 然後選擇 Add > Docker Support
- 手寫dockerfile,不是我們本文的重點,請參考另外一篇文章【One by one系列】一步步學習docker(三)——實戰部署dotnetcore
不管哪種方式,一定會或者要在項目根目錄下增加Dockerfile
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["Webapi/AliMobilePush.Webapi.csproj", "Webapi/"]
COPY ["Infrastructure/AliMobilePush.Infrastructure/AliMobilePush.Infrastructure.csproj", "Infrastructure/AliMobilePush.Infrastructure/"]
COPY ["Domain/AliMobilePush.Domain/AliMobilePush.Domain.csproj", "Domain/AliMobilePush.Domain/"]
COPY ["Application/AliMobilePush.Application/AliMobilePush.Application.csproj", "Application/AliMobilePush.Application/"]
RUN dotnet restore "Webapi/AliMobilePush.Webapi.csproj"
COPY . .
WORKDIR "/src/Webapi"
RUN dotnet build "AliMobilePush.Webapi.csproj" -o /app/build
FROM build AS publish
RUN dotnet publish "AliMobilePush.Webapi.csproj" -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AliMobilePush.Webapi.dll"]
上面dockerfile分為了
base
build
publish
三個階段的多階段建構.
1.base
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
Debian10的asp.net core 運作時image開頭,并建立公開端口80,443的中間image
base
2.build
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["Webapi/AliMobilePush.Webapi.csproj", "Webapi/"]
COPY ["Infrastructure/AliMobilePush.Infrastructure/AliMobilePush.Infrastructure.csproj", "Infrastructure/AliMobilePush.Infrastructure/"]
COPY ["Domain/AliMobilePush.Domain/AliMobilePush.Domain.csproj", "Domain/AliMobilePush.Domain/"]
COPY ["Application/AliMobilePush.Application/AliMobilePush.Application.csproj", "Application/AliMobilePush.Application/"]
RUN dotnet restore "Webapi/AliMobilePush.Webapi.csproj"
COPY . .
WORKDIR "/src/Webapi"
RUN dotnet build "AliMobilePush.Webapi.csproj" -o /app/build
build階段是從編譯工具—sdk鏡像開始,而不是
aspnet
,那是因為隻有
sdk
鏡像用後建構編譯工具,是以sdk鏡像也比aspnet鏡像大。先還原
restore
,再
publish
3.publish
FROM build AS publish
RUN dotnet publish "AliMobilePush.Webapi.csproj" -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AliMobilePush.Webapi.dll"]
最後階段再次從
base
開始,包括
COPY --from=publish /app/publish .
将釋出的輸出複制到最終鏡像中。由于無需包含
sdk
鏡像中的建構編譯工具,是以此過程可以使最終鏡像小得多。
官方最佳實踐,多階段建構鏡像,這樣生成過程更高效,并使容器更小。官方文檔,整個多階段建構 可以讓後一個階段建構可以使用前一個階段建構的産物,形成一條建構階段的chain;最終結果僅産生一個image,避免産生備援的多個臨時images或臨時容器對象,這正是我們所需要的:我們隻需要個結果。
2.4 建立自定義docker鏡像
一個服務對應一個鏡像,需要知道,在
Visual Studio
的強大功能下,docker鏡像是自動建立的。
作為開發者,隻要功能沒完成,或者代碼不送出到版本控制。都是需要在本地部署和測試的。那麼這就意味你需要在本地的docker主機上建立docker鏡像,部署docker容器,并在這些容器上去運作,測試,調試。使用
Visual Studio
建立具有 Docker 支援的項目時,不會顯示的建立映像。 而是在按下 F5(或 Ctrl-F5)運作docker 化的應用程式或服務時建立映像 。
Visual Studio
會自動執行這個操作,開發人員不會看到該過程,但務必要了解其原理。
2.5 定義docker-compose.yaml
定義服務,建立多容器應用,主要是可以在
docker-compose.yml
中定義一系列的服務。通過部署指令将其部署為組合應用程式。 它還配置其依賴項關系和運作時配置。在主解決方案檔案夾或根解決方案檔案夾中建立該
docker-compose.yml
檔案,
docker-compose.yml
是可以拆分成多個
docker-compose
檔案。然後根據不同的環境去覆寫值。添加docker-compose.yml檔案也有兩種方式
- 已經建立好的webapi項目,右鍵 Solution Explorer 然後選擇 Add>Container Orchestrator Support
- 手寫
,這個後續博文會詳細介紹,亦不是本篇的重點。是以下面重點介紹第一種方式:docker-compose.yml
第一次作Solution Explorer > Add>Container Orchestrator Support操作
- 會在api項目下增加
,如果原本沒有的話Dockerfile
- 會在解決方案目錄增加
-
docker-compose.dcproj
-
docker-compose.override.yml
-
docker-compose.yml
-
.dockerignore
-
docker-compose.yml
version: '3.4'
services:
webapi:
build:
context: .
dockerfile: Webapi/Dockerfile
networks:
- asp-net
depends_on:
- "cachedata"
- "sqldata"
cachedata:
image: redis
networks:
- asp-net
sqldata:
image: mysql
networks:
- asp-net
networks:
asp-net:
driver: bridge
docker-compose.override.yml
version: '3.4'
services:
webapi:
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "5000:5000"
- "5001:5001"
volumes:
- ./docker/log/alipush.log:/app/alipush.log
cachedata:
ports:
- "6379:6379"
volumes:
- ./docker/data/redis:/data
sqldata:
ports:
- "3307:3306"
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- ./docker/data/mysql:/var/lib/mysql
注意:修改資料庫連接配接配置
這裡需要注意,所有有用到鏡像間的通信的地方,我們都需要使用鏡像名進行指代,例如我們需要修改程式的資料庫通路字元串的伺服器位址
Mysql:
"ConnectionStrings": {
"PushContext": "Persist Security Info=False;database=pushcenter;server=sqldata;Connect Timeout=30;user id=pushcenter; pwd=123456"
},
Redis:
"Redis": {
"ConnectionString": "cachedata,defaultDatabase=1",
"Instance": "push_request_",
"Timeout": 1
},
2.6 建構并運作docker應用
如果是單容器應用,直接跑。
如果多服務容器應用,就有兩個選擇
-
docker-compose up
-
Visual Studio
2.6.1 單容器應用
使用docker指令,
docker run
即可
docker run -t -d -p 80:5000 cesardl/netcore-webapi-microservice-docker:first
2.6.2 運作多容器應用
使用
docker-compose
指令,
docker-compose up
docker-compose up
docker run
指令(或在
Visual Studio
中運作和調試容器)足以在開發環境中測試容器。 但不應該将這種方法用于生産部署,在生産部署中應該以業務流程協調程式為目标,,比如K8S,或者
docker swarm
在Visual Studio 中運作和調試容器
- 1.選擇解決方案中選擇
項目,Solution Explorer > Set as a Startup Projectdocker-compose
-
開始運作調試吧F5
可以在output-build視窗下觀察:
實際上,是visual studio幫我們直接執行了
docker-compose -f docker-compose.yml
的指令
然後緊接着,docker-compose就會
- 建立橋接網絡
- 建立并啟動redis,mysql容器:按照docker-compose.yml的依賴 depends_on項
- 建立并啟動webapi容器
建構的過程中,win10會一直提示,檔案是否共享,會一直不停的點share it.這時我們去觀察下:
docker-desktop>Resources>FILE SHARING
沒錯,我們把這些主機(win10)檔案夾挂載到hyper-v(虛拟機,docker主控端),hyper-v又挂載到容器,實作主機檔案夾與容器檔案夾的映射。
再看下結果:鏡像與容器
然後就可以打斷點調試容器應用了。
如果你發現建構的鏡像與容器有問題,想重新來過,vs大法提供了如下方法:
Solution>Clean Solution
再在output-build視窗下觀察:
- 先kill服務
- 然後在删掉容器
- 最後删掉應用的鏡像-不過實際沒有删掉
應用容器倒是停了并且删除了,但是mysql,redis這些容器資料服務,僅僅隻是停了。
注意:dockerfile裡面的
mcr.microsoft.com/dotnet/core/sdk:3.1-buster
鏡像,下載下傳巨慢,建構一次,一碗番茄煎蛋面都要做好了。
國内下載下傳微軟鏡像慢的解決方案
https://github.com/newbe36524/Newbe.McrMirror
使用docker-mcr下載下傳鏡像
dotnet tool install newbe.mcrmirror -g
docker-mcr -i mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim
docker-mcr -i mcr.microsoft.com/dotnet/core/sdk:3.1-buster
把建構過程需要下載下傳的鏡像,先提前下下來吧。
測試
測試用例1 webapi-swagger
測試用例2 mysql能否通路,且通過ef生成了資料庫
測試用例3 redis能否通路
測試用例4 檔案挂載是否正常(舉例一個即可)
2.7 推送代碼送出或者繼續開發
推送下班,避免996
或者繼續開發
3.Visual Studio大法好
實際上,使用
Visual Studio
進行開發的工作流比使用編輯器或CLI 方法的工作流簡單得多。
Visual Studio
隐藏或簡化了 Docker 需要執行的與 Dockerfile 和 docker-compose.yml 檔案相關的大部分步驟
- 自動生成Dockerfile,可編輯
- 自動生成docker-compose.yml,可編輯
- 自動執行docker-compose up,且可調試
- 可自動停止且并移除容器
微軟以開發者為中心的價值觀,為開發者省了不少事,Visual Studio不愧為宇宙第一的IDE。
參考連結
https://www.cnblogs.com/xianwang/p/12039922.html
https://zhuanlan.zhihu.com/p/147369525
http://www.imooc.com/article/259789
https://my.oschina.net/u/4285813/blog/3661653/print
https://github.com/dotnet-architecture/eShopOnContainers
https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/
作者:Garfield
同步更新至個人部落格:http://www.randyfield.cn/
本文版權歸作者所有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected]
微信公衆号
掃描下方二維碼關注個人微信公衆号,實時擷取更多幹貨
同步更新至:http://www.randyfield.cn/
出處:http://www.cnblogs.com/RandyField/
本文版權歸作者和部落格園共有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected].