一 简介                                                     


  微服务将需多的功能拆分为许多的轻量级的子应用,这些子应用相互调度。好处就是轻量级,完全符合了敏捷开发的精神。我们知道ut(单元测试),不仅仅提高我们的程序的健壮性,而且可以强制将类和方法的设计尽量的单一化。那么微服务也是这样,敏捷对于软件工程的意义就是快速开发,验证市场需求,然后快速改进,从而适应市场节奏。什么东西要快速,就必须轻量级。大家知道一个应用的复杂程度,完全是和这个项目的功能和代码数量挂钩的,这是软件自诞生就存在的问题,一个设计不好的软件,最后会让这个软件更新和改进变的非常复杂,直至没有人敢去碰这个应用,我一直认为这个代码量和复杂程度挂钩,困难程度是以指数的量增加的,而不仅仅是线性增加。这就需要一个好的设计去解决问题,一个微服务尽量的单一,与其他子应用几乎没有代码上的关联,所以可以快速出原型,快速开发,验证市场,也可以快速砍掉一个项目,而不影响其他的应用。


  当然,因为微服务有他的局限性,所以也有它的坏处,比如一致性会打折扣,对于一致性的问题,我前几篇文章已经做过探讨。还有就是虽然软件开发部署工作解脱了,但是您想,原来一个应用,现在三四个应用进行协调和通讯,对于这个分布式架构的要求就会高,如何把他们打散又要弱关联在一起,那么eureka诞生了。asp.net
core支持一个底层的库,Microsoft.Extensions.Http.Polly,这个库是表达策略,例如以流畅且线程安全的方式处理重试、断路器、超时、Bulkhead
隔离和回退,就是防止雪崩,熔断降级等特性。感兴趣的可以看asp.net core 官方文档:
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2#use-polly-based-handlers
。多说一句,asp.net core也有后台服务job的底层api,有兴趣的可以试着做出一套asp.net
core的job框架。甚至服务发现,配合zookeeper,也可以开发出一套属于asp.net core的框架。


  言归正传,这里还需要注意的是一点是,一个微服务是可以和以前的比如面向传统服务类型的应用共存的,如果公司内部可以提出一个应用作为微服务来设计,那么我恭喜你,从泥潭中跨出了一步,如果你们公司全部用微服务设计了,那么真的恭喜,完全跳出了泥潭。当然一些重量级的应用,因为必须使用分布式事务等等的特殊要求,还是可以存在,微服务还是需要根据实际情况来实施的。

二 eureka集成                                                           

  说到eureka前,我们需要明白一个术语:backing
service。我把他翻译为协助服务,或者帮助服务。主要意思就是对资源具有管理,丢失处理,连接和配置等功能的一个程序。那微服务资源自然就是我们各种各样的服务,如图所示:



当然除过eureka,还有etcd,Consul,Marathon,ZooKeeper可以供大家挑选。

使用docker运行eureka服务器端:
docker run -p 8081:8080 -d --name eureka \ -d netflixoss/eureka:1.3.1
运行成功后,如下图所示:




让我们用我们的Walt.Framework.OrderService项目定义一个api接口和将配置eureka客户端,然后至今运行,就会直接将实例添加进eureka服务端:

设计api,我们直接看生成的api描述,使用swagger生成的api描述:



再安装eureka客户端,首先查找都有那些包:
nuget.exe list eureka
显示结果,红框中的包就是文档中要求针对netcore的包:



 

客户端需要引入包:
dotnet add Walt.TestMcroServices.Webapi.csproj package
Steeltoe.Discovery.ClientCore
然后再注册服务,这个大家都应该熟悉的不能再熟悉了,因为我们开发自定义的服务已经有三个了:
public class Startup { ... public IConfiguration Configuration { get; private
set; } public Startup(...) { ... } public void
ConfigureServices(IServiceCollection services) {// Add Steeltoe Discovery
Client service services.AddDiscoveryClient(Configuration); //添加服务 // Add
framework services. services.AddMvc(); ... } public void
Configure(IApplicationBuilder app, ...) { ... app.UseStaticFiles();
app.UseMvc();// Use the Steeltoe Discovery Client service
app.UseDiscoveryClient();
    //这里是将服务应用到http通道,我们知道这里处理的都是asp.net core
相关请求的http通道的一些事情,类似与asp.net的那几个application事件。 }
这里多说一句关于owin的事情,上面代码的中间件技术和OWIN完全不同,OWIN:开放 Web
接口,它定义了在管道中使用中间件来处理请求和相关响应的标准方法。我们看看asp.net core 的管道都干些什么?如图所示:

 

官方说辞:




所以无论各个环节都是处理http上下文中的内容,web驱动监听到http请求和发送请求的内容,就是request,和response,在这两个之间就是我们的通道,不管是处理生成http内容时,还是身份验证,session处理,都是这个通道之间的某个环节。

那owin作用是什么尼?就是定义这些中间环节的一些标准,所以任何web框架,只要实现了owin接口标准,两个不同web框架,就都可以相互兼容。

言归正准,继续eureka的客户配置, 配置文件:
{ "Logging": { "LogLevel": { "Default": "trace", "System": "trace", "Microsoft"
:"trace", "Steeltoe": "Debug" }, "KafkaLog":{ "Prix":"这是我的自定义日志提供程序", "
LogStoreTopic":"mylog-orderservice" } }, "KafkaService":{ "Properties":{ "
bootstrap.servers":"192.168.249.106:9092", "group.id":"group1" } }, "
zookeeperService":{ "Connectstring":"192.168.249.106:2181", "SessionTimeout":
12000000 }, "spring": { "application": { "name": "orderservice" } }, "eureka":
{ "client": { "serviceUrl": "http://192.168.249.105:8080/eureka/v2/",
"shouldFetchRegistry": false }, "instance": { "ipAddress":"192.168.249.102",
"preferIpAddress":true, "port": 802, "instanceId":"orderserviceinstance" } } }
 

红颜色的就是eureka需要使用的配直节。


所以在startup中的配置就需要把整个configuration传进去,让他自己查找,而不用精确找到configuration配直节后,再传入服务构建,这个非常简单,总共就两行代码。

  
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using
Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.Configuration; using
Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;
using Steeltoe.Discovery.Client; using Swashbuckle.AspNetCore.Swagger; namespace
Walt.TestMicroservices.OrderService {public class Startup { public
Startup(IConfiguration configuration ,IHostingEnvironment hostingEn ,
ILoggerFactory loggerFac ) { Configuration= configuration; HostingEn =
hostingEn; LoggerFac= loggerFac; } public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEn { get; } public ILoggerFactory LoggerFac {
get; set; } // This method gets called by the runtime. Use this method to add
services to the container. public void ConfigureServices(IServiceCollection
services) { services.AddMvc(mvcOptions=>{
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);//
services.AddAuthorization();// services.AddAuthentication("Bearer") //
.AddIdentityServerAuthentication(options =>// { // options.Authority = "
http://localhost:64433"; // options.RequireHttpsMetadata = false; //
options.ApiName = "api1";// }); // Add Steeltoe Discovery Client service
services.AddDiscoveryClient(Configuration);// Register the Swagger generator,
defining 1 or more Swagger documents services.AddSwaggerGen(c => { c.SwaggerDoc(
"v1", new Info { Title = "My API", Version = "v1" }); }); var log =
LoggerFac.CreateLogger<Startup>(); log.LogDebug("服务配置完成"); } // This method
gets called by the runtime. Use this method to configure the HTTP request
pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment
env) {var log = LoggerFac.CreateLogger<Startup>(); log.LogInformation("
infomation"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else
{ app.UseHsts(); }app.UseDiscoveryClient(); app.UseSwagger();
app.UseSwaggerUI(c=> { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"
); c.RoutePrefix= string.Empty; }); app.UseMvc(routes => { routes.MapRoute(
name:"default", template: "api/{controller=Home}/{action=Index}/{id?}"); });
app.UseAuthentication(); log.LogDebug("通道配置完毕"); } } }
 

 

 

 运行:

 

 

我们看到注册成功了,那看调用方:Walt.TestMicroServices.Webapi

startup类中注册:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using
Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.Configuration; using
Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;
using Walt.Framework.Service; using Walt.Framework.Configuration; using
Walt.Framework.Service.Kafka;using Steeltoe.Discovery.Client; using System;
using Steeltoe.Common.Http.Discovery; namespace Walt.TestMicroServoces.Webapi {
public class Startup { public Startup(IConfiguration configuration
,IHostingEnvironment hostingEn , ILoggerFactory loggerFac ) { Configuration=
configuration; HostingEn= hostingEn; LoggerFac = loggerFac; } public
IConfiguration Configuration {get; } public IHostingEnvironment HostingEn { get
; }public ILoggerFactory LoggerFac { get; set; } // This method gets called by
the runtime. Use this method to add services to the container. public void
ConfigureServices(IServiceCollection services) { services.AddSingleton
<IKafkaService, KafkaService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthorization(); services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options=> { options.Authority = "
http://localhost:64433"; options.RequireHttpsMetadata = false; options.ApiName =
"api1"; }); services.AddKafka(KafkaBuilder => { var kafkaConfig =
Configuration.GetSection("KafkaService");
KafkaBuilder.AddConfiguration(kafkaConfig); });//
services.AddSingleton<IOrderService, OrderService>();// Add Steeltoe Discovery
Client service services.AddDiscoveryClient(Configuration); // Add Steeltoe
handler to container services.AddTransient<DiscoveryHttpMessageHandler>(); //
Configure a HttpClient services.AddHttpClient<OrderService>(c => {
c.BaseAddress = new Uri("http://orderservice"); })
.AddHttpMessageHandler<DiscoveryHttpMessageHandler>()
//这个就是拦截http请求,然后让发现服务处理这个http请求,而不适用默认的http请求处理程序
.AddTypedClient<IOrderService, OrderService>();
//将上面DiscoveryHttpMessageHandler这个处理程序生成httpclient然后注入给这个服务类,然后再将这个服务类加入DI
var log = LoggerFac.CreateLogger<Startup>(); log.LogDebug("服务配置完成"); } // This
method gets called by the runtime. Use this method to configure the HTTP
request pipeline. public void Configure(IApplicationBuilder app,
IHostingEnvironment env) {var log= LoggerFac.CreateLogger<Startup>();
log.LogInformation("infomation"); if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage(); }else { app.UseHsts(); } app.UseMvc();
app.UseDiscoveryClient(); app.UseAuthentication(); log.LogDebug("通道配置完毕"); } } }
 

 

 

这里看配置文件:
{ "Logging": { "LogLevel": { "Default": "Debug", "System": "Debug", "Microsoft"
:"Debug" }, "KafkaLog":{ "Prix":"这是我的自定义日志提供程序", "LogStoreTopic":"mylog-webapi"
} },"KafkaService":{ "Properties":{ "bootstrap.servers":"192.168.249.106:9092",
"group.id":"group1" } }, "zookeeperService":{ "Connectstring":"
192.168.249.106:2181", "SessionTimeout":12000000 }, "spring": { "application": {
"name": "webapi" } }, "eureka": { "client": { "serviceUrl": "
http://192.168.249.105:8080/eureka/v2/", "shouldRegisterWithEureka": true }, "
instance": { "ipAddress":"192.168.249.102", "preferIpAddress":true, "port": 801,
"instanceId":"webapiinstance" } } }
 

看调用类,很简单,因为asp.net core和eueka的集成已经在上一步骤中的startup中处理,这里直接使用:
using System.Net.Http; using System.Threading.Tasks; using
Microsoft.Extensions.Configuration;using Steeltoe.Common.Discovery; namespace
Walt.TestMicroServoces.Webapi {public class OrderService:IOrderService { private
IConfiguration _config;private HttpClient _httpClient; public
OrderService(IConfiguration config ,HttpClient httpClient) { _config=config;
_httpClient= httpClient; } public Task<string> GetOrder() { var
task=_httpClient.GetStringAsync("Order"); return task; } } }
调用:

首先看日志:



 

只要获取到这个实例,就没什么问题了。

页面结果:



 注意:我在用nuget包的时候,总是获取不到实例,不知道什么,后来没办法就用源码:


直接就可以了,就算不可以,调式源码,很快也就解决了,看来坑都怕源码,尤其是这种还没大规模使用和讨论的小项目,所以我这次的代码就把这个引用保留,大家下载后需要自己下载
Descover源码,然后引用这个工程就ok了。

Discovery github地址:https://github.com/SteeltoeOSS/Discovery

 三 集成polly                                               

  polly 提供了微服务之间调用的容错功能,无论是慢超市,还是失败,对于微服务之间提供了很多策略,如下的策略:

PolicyPremiseAkaHow does the policy mitigate?
Retry 
(policy family)
(quickstart <https://github.com/App-vNext/Polly#retry> ; deep
<https://github.com/App-vNext/Polly/wiki/Retry>)
Many faults are transient and may self-correct after a short delay.

重试
"Maybe it's just a blip" Allows configuring automatic retries.
Circuit-breaker
(policy family)
(quickstart <https://github.com/App-vNext/Polly#circuit-breaker> ; deep
<https://github.com/App-vNext/Polly/wiki/Circuit-Breaker>)
When a system is seriously struggling, failing fast is better than making
users/callers wait. 

失败而不是调用等待

Protecting a faulting system from overload can help it recover.
"Stop doing it if it hurts" 

"Give that system a break" Breaks the circuit (blocks executions) for a
period, when faults exceed some pre-configured threshold.
Timeout
(quickstart <https://github.com/App-vNext/Polly#timeout> ; deep
<https://github.com/App-vNext/Polly/wiki/Timeout>)
Beyond a certain wait, a success result is unlikely.

超时
"Don't wait forever" Guarantees the caller won't have to wait beyond the
timeout.
Bulkhead Isolation
(quickstart <https://github.com/App-vNext/Polly#bulkhead> ; deep
<https://github.com/App-vNext/Polly/wiki/Bulkhead>)
When a process faults, multiple failing calls backing up can easily swamp
resource (eg threads/CPU) in a host.

A faulting downstream system can also cause 'backed-up' failing calls upstream.

Both risk a faulting process bringing down a wider system.

一个进程失败或多个失败的调用可以很容易的淹没主机的资源。

所以需要隔离墙。
"One fault shouldn't sink the whole ship" Constrains the governed actions to a
fixed-size resource pool, isolating their potential to affect others.
Cache
(quickstart <https://github.com/App-vNext/Polly#cache> ; deep
<https://github.com/App-vNext/Polly/wiki/Cache>) Some proportion of requests
may be similar. "You've asked that one before" Provides a response from cache
if known. 

Stores responses automatically in cache, when first retrieved.
Fallback
(quickstart <https://github.com/App-vNext/Polly#fallback> ; deep
<https://github.com/App-vNext/Polly/wiki/Fallback>)
Things will still fail - plan what you will do when that happens.

某些将仍然失败,在它发生的时候,你要有计划准备做一些事。
"Degrade gracefully" Defines an alternative value to be returned (or action to
be executed) on failure.
PolicyWrap
(quickstart <https://github.com/App-vNext/Polly#policywrap> ; deep
<https://github.com/App-vNext/Polly/wiki/PolicyWrap>)
Different faults require different strategies; resilience means using a
combination.

不同的失败请求有不同的情况,所以可以对上面的策略有不同的结合处理。
"Defence in depth" Allows any of the above policies to be combined flexibly.
  每个策略大家有兴趣试着做例子。

  代码中开发非常简单,提供了三种配置策略的方法,下面是其中之一:

 
var timeout = Policy.TimeoutAsync<HttpResponseMessage>( TimeSpan.FromSeconds(10
));var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30)); services.AddHttpClient("conditionalpolicy") // Run
some code to select a policy based on the request .AddPolicyHandler(request =>
request.Method== HttpMethod.Get ? timeout : longTimeout);
 


这个在官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2#use-polly-based-handlers

讲解的非常详细。

还有就是polly的github地址:https://github.com/App-vNext/Polly

都有非常详细的介绍。