Skip to content

.NET Core & Spring Cloud 互相调用微服务

Spring Cloud 学习 中实现了 Java 版的微服务。在此基础上调查了如何在 .NET Core 上注册和发现服务。

.NET Core 端使用 Pivotal.Discovery.Client 包来注册及发现 Spring Cloud 服务。
在网上找到了好多教程文章,但最新最全的还是官方网站 steeltoe.io 上的。上面有最新的文档 Steeltoe Introduction 和完整的示例代码 Steeltoe Sample Applications

Java 访问 .NET Core 微服务

.NET Core 提供者

通过 NuGet 安装 Pivotal.Discovery.Client

Install-Package Pivotal.Discovery.ClientCore

Program.cs
通过 UseUrls 方法指定端口(Visual Stuido 中设置 工程属性=>调试=>Web 服务设置=>应用 URL 中的端口也是一样的)

cs
public static void Main(string[] args)
{
    BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseUrls("http://*:9102")
        .UseStartup<Startup>()
        .Build();

Startup.cs
启用 DiscoveryClient

cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddDiscoveryClient(Configuration);
    services.AddMvc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseMvc();
    app.UseDiscoveryClient();
}

appsettings.json
配置 Eureka

json
"spring": {
    "application": {
        "name": "eureka-client-dotnet-provider"
    }
},
"eureka": {
    "client": {
        "serviceUrl": "http://localhost:9871/eureka/",
        "shouldFetchRegistry": true,
        "validateCertificates": false
    },
    "instance": {
        "port": 9102,
        "hostName": "localhost",
        "instanceId": "eureka-client-dotnet-provider:9102"
    }
}

需要注意的是通过 Visual Stuido 的 IIS Express 启动时,必须设置 eureka.instance.hostNamelocalhost,否则访问时会显示 400 错误。

Bad Request - Invalid Hostname
HTTP Error 400. The request hostname is invalid.

这是因为通过 IIS Express 默认是禁止通过 IP 地址访问的。
本以为 eureka.instance.preferIpAddress 配置项设置为 false 也可以解决这个问题,但可惜不行。
其它的配置项可以参考 Eureka Client Settings
另外要注意的是 eureka:instance:instanceId 中需要带端口号,否则当启动多个实例(端口号不一样)时,只有最后启动的服务会被发现。(不知道是不是因为 ID 相同而被顶替掉了)

课题:如何自动使用当前监听的端口号注册到 Eureka Server?

Java 中的 Eureka Client 默认就是使用当前 Application 启动的端口号来注册到 Eureka Server,.NET Core 中还需要在 appsettings.json 中手动配置。
首先是 .NET Core API 的端口号不能像 Spring Boot Application 那样通过命令行参数 (--server.port=8080) 来指定。这个调查了下,可以通过 dotnet run 命令启动时指定 --launch-profile 参数的值来实现。该参数使用在 launchSettings.json 对应的配置项来启动程序。
其次是 eureka:instance:porteureka:instance:instanceId 都需要在 appsettings.json 中设置。这个可能需要在代码中配置这几个参数才能实现。暂时还未调查。

Java 消费者

和访问其它的 Java 服务提供者 是一样的写法。需要注意的是 .NET Core API 的默认路由前面是带 /api 的,两边要保持一致。

java
@FeignClient(name = "eureka-client-dotnet-provider", configuration = HystrixClientConfigration.class)
public interface HystrixClientRemoteInterface {
    @RequestMapping(value = "/api/TeamInfo/{teamName}", method = RequestMethod.GET)
    public String getTeamInfo(@PathVariable("teamName") String teamName);
}

.NET Core 消费者调用 Java 提供者

由于使用了 HttpClientFactory , 需要 ASP.NET Core 2.1 。具体使用方法可以参考 3 ways to use HTTPClientFactory in ASP.NET Core 2.1

Java 提供者

TeamProviderController

java
@RestController
public class TeamProviderController {
    @Value("${server.port}")
    private int port;

    @RequestMapping(value = "/getTeamInfo", method = RequestMethod.GET)
    @ResponseBody
    public String getTeamInfo(@RequestParam("teamName") String teamName) {
        // 返回团队信息。
        return "provider:" + port + " 查询" + teamName + "团队信息";
    }
}

application.yml

java
spring:
  application:
    name: eureka-client-provider
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9871/eureka
server:
  port: 9091

.NET Core 消费者

Startup.cs

cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add Steeltoe Discovery Client service
    services.AddDiscoveryClient(Configuration);

    // Add Steeltoe handler to container
    services.AddTransient<DiscoveryHttpMessageHandler>();

    // Configure a HttpClient
    services.AddHttpClient("client-api-team", c =>
    {
        c.BaseAddress = new Uri(Configuration["Services:Team-Service:Url"]);
    })
        .AddHttpMessageHandler<DiscoveryHttpMessageHandler>()
        .AddTypedClient<ITeamService, TeamService>();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseMvc();

    // Add Steeltoe Discovery Client service
    app.UseDiscoveryClient();
}

ITeamService

cs
public interface ITeamService
{
    Task<string> GetTeamInfo(String teamName);
}

TeamService

cs
public class TeamService : ITeamService
{
    private ILogger<TeamService> _logger;
    private readonly HttpClient _httpClient;

    public TeamService(HttpClient httpClient, ILoggerFactory logFactory = null)
    {
        _httpClient = httpClient;
        _logger = logFactory?.CreateLogger<TeamService>();
    }

    public async Task<string> GetTeamInfo(String teamName)
    {
        var result = await _httpClient.GetStringAsync($"/getTeamInfo?teamName={teamName.ToString()}");
        _logger?.LogInformation($"GetTeamInfo - TeamName:{teamName} - Result:{result}");
        return result;
    }
}

错误

请求的地址格式写错了会导致 404 或 400 错误。例子中正确地址应该是 $"/getTeamInfo?teamName={teamName.ToString()}"

写成 $"/getTeamInfo" 会报 400 错误。

An unhandled exception occurred while processing the request.
HttpRequestException: Response status code does not indicate success: 400 ().
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()

写成 $"/getTeamInfo/{teamName.ToString()}" 会报 404 错误。

An unhandled exception occurred while processing the request.
HttpRequestException: Response status code does not indicate success: 404 ().
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()

.NET Core 消费者 调用 .NET Core 提供者

同调用 Java 提供者一样。

.NET Framework 调用服务

.NET 4.5.0 消费者调用 .NET Core 提供者

4.6.1 以前的版本(不含 4.6.1)无法使用 Pivotal.Discovery.EurekaBas 包,所以也就不能使用这种方式实现了。

Install-Package Pivotal.Discovery.EurekaBase
Install-Package Pivotal.Discovery.ClientAutofac

安装报错:

无法安装程序包“Pivotal.Discovery.EurekaBase 2.1.0”。你正在尝试将此程序包安装到目标为“.NETFramework,Version=v4.5”的项目中,但该程序包不包含任何与该框架兼容的程序集引用或内容文件。有关详细信息,请联系程序包作者。
无法安装程序包“Pivotal.Discovery.ClientAutofac 2.1.0”。你正在尝试将此程序包安装到目标为“.NETFramework,Version=v4.5”的项目中,但该程序包不包含任何与该框架兼容的程序集引用或内容文件。有关详细信息,请联系程序包作者。

可以参考 .NET Standard Versions ,由于 Pivotal.Discovery.EurekaBase 最低需要 .NET Standard 2.0,所以必须使用 ASP.NET 4.6.1 或 以上的版本。

.NET 4.6.1 消费者调用 .NET Core 提供者

Install-Package Pivotal.Discovery.EurekaBase

4.6.1 及以后的版本应该是可以的。官方的 Sample 中好像有。

参考

  1. spring cloud+.net core 搭建微服务架构:服务注册(一)
  2. spring cloud+dotnet core 搭建微服务架构:服务发现(二)
  3. spring cloud+.net core 搭建微服务架构:Api 网关(三)
  4. spring cloud+dotnet core 搭建微服务架构:配置中心(四)
  5. .NET Core 微服务之基于 Steeltoe 使用 Eureka 实现服务注册与发现
  6. Steeltoe - Service Discovery - 1.0 Netflix Eureka
  7. 3 ways to use HTTPClientFactory in ASP.NET Core 2.1
  8. 微服务~Eureka 实现的服务注册与发现及服务之间的调用
  9. VS 2015 调试状态下,用 IP 访问提示:Bad Request - Invalid Hostname
  10. steeltoe.io
  11. SteeltoeOSS/Samples - GitHub
  12. .NET Core 2.0 及.NET Standard 2.0
  13. .NET Standard Versions