最近一直在研究微服务体系架构。微服务概念一直很火,但是作为一个初学者往往由于更多由于面对众说纷纭的高深理论和纷繁多样的技术,而失去了方向,不知道从何处开始,也就是传说中的从入门到劝退。_,还没开始就已经放弃了。所以还是不得不夸一夸微软一切以开发者为中心的价值观:好文档,好工具。
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
实例,单独一个容器 - 1个
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
三个阶段的多阶段构建.
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
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
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.选择解决方案中选择
docker-compose
项目,Solution Explorer > Set as a Startup Project
-
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/