本文源碼地址
https://github.com/wswind/Steeltoe-Sample
Steeltoe是什么
Steeltoe是幫助.NET開發的服務接入Spring Cloud技術棧的官方支持工具。也就是說,微服務的系統框架,還是由Spring Cloud來實現,而業務服務,通過.NET Core來實現。后面我們將基於Steeltoe來嘗試實現微服務系統框架。
steeltoe主要包含以下功能:斷路器,配置中心,服務連接器(MSSql、MySql、Oauth、Mongodb、Redis等),服務發現,網絡文件共享(windows),動態日志,雲管理(服務監控),雲安全(Jwt認證),開發工具(Steeltoe CLI)。steeltoe源碼結構如上圖。
steeltoe項目地址:https://github.com/SteeltoeOSS/steeltoe
steeltoe樣例地址:https://github.com/SteeltoeOSS/Samples
steeltoe文檔:https://steeltoe.io/docs/
steeltoe運行於Cloud Foundry,關於Cloud Foundry的介紹,請查看:https://blog.csdn.net/qq_30154571/article/details/84955097
Consul服務發現
參考: https://steeltoe.io/service-discovery/get-started/consul
我沒有使用docker for windows,而是在virtualbox 中創建了centos 7虛擬機運行docker,所以需要對官方教程的網絡配置有調整。如果你的環境為docker for windows,則可以直接使用localhost
我的虛擬機網絡環境為virtualbox的host only network + nat雙網卡配置。
網關ip為192.168.56.1對應開發物理機ip,虛擬機ip為192.168.56.104對應consul ip。
virtualbox的網絡環境配置,可參考我的另一篇博文https://www.cnblogs.com/wswind/p/10832740.html
在centos中通過命令運行consul:
docker pull consul
docker run -d -p 8500:8500 consul #-d意味着后台運行
通過https://start.steeltoe.io/創建模板,選擇NetCore3.1,組件選擇Discovery。
此時我們可以看到,這個模板生成器,其實創建的就是一個Asp.NET Core WebAPI的空項目,唯一的不同,就是在項目文件中添加了包依賴:Steeltoe.Discovery.ClientCore。並在ConfigureServices時,調用了
services.AddDiscoveryClient(Configuration);
通過在appsettings.json中添加配置,寫明本機地址以及consul地址,運行項目,即可完成服務注冊
"spring": {
"application": {
"name": "Consul-Register-Example"
}
},
"consul": {
"host": "192.168.56.104",
"port": 8500,
"discovery": {
"enabled": true,
"register": true,
"port": "8080",
"ipAddress": "192.168.56.1",
"preferIpAddress": true
}
}
修改Properties\launchSettings.json
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8080",
"sslPort": 0
}
}
為了便於通過192.168.56.1訪問服務,我添加了UseUrls:
var builder = WebHost.CreateDefaultBuilder(args)
.UseUrls("http://0.0.0.0:8080")
如果不修改,對於健康檢查貌似沒什么影響,只是你注冊上去的ip,就是無法訪問的了。
關於asp.net core如何修改綁定ip,可參考:
https://www.cnblogs.com/Hai--D/p/5842842.html
運行項目后,打開consul地址,我們可以看到服務注冊已經完成了。
我們可以看到服務注冊過程非常簡單,steeltoe的服務注冊工具的使用,僅需添加幾行json配置即可完成。大大減少了我們的代碼開發量。
上面是服務注冊,接下面我們來講解服務發現。由於已經了解steeltoe的模板生成器只是在空模板上加了nuget包的引用,我們也無需再通過它生成項目了。
通過.net core cli創建空項目,並添加nuget包即可,命令行如下:
dotnet new webapi -n ConsulDiscovery
cd ConsulDiscovery
dotnet add package Steeltoe.Discovery.ClientCore
在ConfigureSerivces中,設置DI services.AddDiscoveryClient(Configuration);
然后修改appsettings.json
{
...
"spring": {
"application": {
"name": "Consul-Discover-Example"
}
},
"consul": {
"host": "192.168.56.104",
"port": 8500,
"discovery": {
"enabled": true,
"register": false
}
}
...
}
修改WeatherForecastController,通過依賴注入IDiscoveryClient,創建DiscoveryHttpClientHandler,然后通過consul注冊的服務地址來訪問之前創建的服務。
DiscoveryHttpClientHandler _handler;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IDiscoveryClient client)
{
_logger = logger;
_handler = new DiscoveryHttpClientHandler(client);
}
[HttpGet]
public async Task<string> Get()
{
var client = new HttpClient(_handler, false);
return await client.GetStringAsync("http://Consul-Register-Example/api/values");
}
啟動服務后,可以看到返回值:
上述過程的UML序列圖如下,服務注冊客戶端首先進行服務注冊,服務發現通過讀取Consul中的服務地址,來進行訪問。
Service Connectors with Microsoft SQL
首先通過生成器https://start.steeltoe.io/創建項目,選netcore 3.1 + SQL Server
創建項目默認安裝了Nuget包 Microsoft.EntityFrameworkCore.SqlServer並添加了依賴注入
services.AddSqlServerConnection(Configuration);
不過.NET Core 3.1模板目前有問題缺少了一些包引用,導致無法編譯通過,需要手動安裝
System.Data.SqlClient
Steeltoe.CloudFoundry.ConnectorCore
另外,生成的模板項目中的nuget包Microsoft.EntityFrameworkCore.SqlServer不是必須的,如果不使用EfCore其實無需引入這個包。
在appsettings.json中添加數據庫的連接配置
{
...
"sqlserver": {
"credentials": {
"server": "127.0.0.1",
"port": "1433",
"username": "sa",
"password": "sa"
}
}
...
}
sql server需要啟用tcp/ip以允許外部訪問,開啟后需要重啟服務
模板中,向我們展示了這個包的用法
...
public class ValuesController : ControllerBase
{
private readonly SqlConnection _dbConnection;
public ValuesController([FromServices] SqlConnection dbConnection)
{
_dbConnection = dbConnection;
}
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
List<string> tables = new List<string>();
_dbConnection.Open();
DataTable dt = _dbConnection.GetSchema("Tables");
_dbConnection.Close();
foreach (DataRow row in dt.Rows)
{
string tablename = (string)row[2];
tables.Add(tablename);
}
return tables;
}
...
運行效果如下:
通過Dapper來配合使用 Steeltoe.CloudFoundry.ConnectorCore 應該會很方便。
不過也有一個問題,依賴注入是直接注入了SqlConnection,不清楚如果同時連接多數據庫能否拓展。不過對於微服務開發而言,這種情況很少見。
Service Connectors with Redis Cache
首先運行redis
docker run -d -p 6379:6379 redis
通過 Steeltoe Initializr創建項目選擇.NET Core3.1 + Redis
項目中添加了包:
Microsoft.Extensions.Caching.StackExchangeRedis
Steeltoe.CloudFoundry.ConnectorCore
在依賴注入時,添加了:
services.AddDistributedRedisCache(Configuration);
添加配置文件:
{
...
"redis": {
"client": {
"host": "192.168.56.104",
"port": "6379",
}
}
...
}
默認的控制器代碼如下:
public class ValuesController : ControllerBase
{
private readonly IDistributedCache _cache;
public ValuesController(IDistributedCache cache)
{
_cache = cache;
}
// GET api/values
[HttpGet]
public async Task<IEnumerable<string>> Get()
{
await _cache.SetStringAsync("MyValue1", "123");
await _cache.SetStringAsync("MyValue2", "456");
string myval1 = await _cache.GetStringAsync("MyValue1");
string myval2 = await _cache.GetStringAsync("MyValue2");
return new string[]{ myval1, myval2};
}
運行結果如下:
Using Service Connectors with RabbitMQ
運行rabbitmq
docker run -d -p 5672:5672 rabbitmq
Steeltoe Initializr 創建.netcore 3.1 + rabbitmq
#引用包
Steeltoe.CloudFoundry.ConnectorCore
RabbitMQ.Client
#依賴注入
services.AddRabbitMQConnection(Configuration);
添加連接配置
"rabbitmq": {
"client": {
"server": "192.168.56.104",
"port": "5672",
"username": "guest",
"password": "guest",
}
}
注意此處官方教程中的配置有誤,此處為ip配置為server而非host,見Issue:https://github.com/SteeltoeOSS/steeltoe/issues/263
controller的樣例代碼如下,自己發送了五條消息並自己接收打印。
public ValuesController(ILogger<ValuesController> logger, [FromServices] ConnectionFactory factory)
{
_logger = logger;
_factory = factory;
}
// GET api/values
[HttpGet]
public ActionResult<string> Get()
{
using (var connection = _factory.CreateConnection())
using (var channel = connection.CreateModel())
{
//the queue
channel.QueueDeclare(queue: queueName,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
// consumer
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
string msg = Encoding.UTF8.GetString(ea.Body);
_logger.LogInformation("Received message: " + msg);
};
channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
// publisher
int i = 0;
while (i<5) { //write a message every second, for 5 seconds
var body = Encoding.UTF8.GetBytes($"Message {++i}");
channel.BasicPublish(exchange: "",
routingKey: queueName,
basicProperties: null,
body: body);
Thread.Sleep(1000);
}
}
return "Wrote 5 message to the info log. Have a look!";
}