[.Net]使用Soa库+Abp搭建微服务项目框架(五):服务发现和健康监测

本文介绍了如何使用Hangfire作为服务发现和健康监测工具,包括配置Consul服务发现、轮询本地服务列表、设置定时任务以及健康检查功能。通过示例展示了如何在不同服务配置中启用这些特性,并演示了服务列表获取和健康状态监控的API接口。

上篇文章说过,服务发现和健康监测是面向服务体系架构重要的模块,Soa库可以配置使用Consul作为服务发现服务,或者轮询已配置的服务列表作为本机服务发现。

将用Hangfire来作为服务发现与健康监测的定时执行库

具体配置信息请参考Hangfire – Background jobs and workers for .NET and .NET Core

服务发现

配置各个服务的地址列表,或者在服务启动时注册的key-value地址集合,轮询地址服务描述服务(GetRoutesDescAsync),确定服务中方法的路由,方法签名,使用方式,描述(功能,作者)等内容。

本机服务发现(InServer)

本机服务发现将轮询Address配置中的地址所对应的RoutesGetters,执行获取路由信息,并更新SoaServiceRoute中的Address集合

每个微服务都有一个Id 为 "Soa.ServiceDiscovery.InServer.GetRoutesDescAsync"的方法

获取该服务的地址,服务描述。

在服务模块初始化中已经将这个服务路由对象添加到RoutesGetters

var service = new SoaServiceRoute
{
    Address = new List<SoaAddress>
    {
        addr
    },
    ServiceDescriptor = new SoaServiceDesc { Id = "Soa.ServiceDiscovery.InServer.GetRoutesDescAsync" }
};

在主服务(Soa.Sample.Web)中的appsettings.json中,配置如下:

    "Discovery": "InServer"

配置服务发现,当前设置的轮询间隔为每分钟执行一次

    "InServiceDiscovery": {
      "Enable": true,
      "JobCron": "0 * * * * ? "
    }

各个服务Host的appsettings.json中,只需要配置Discovery即可:

    "Discovery": "InServer",

Consul

可以使用UseConsulForDiscovery方法配置基于consul的服务发现功能

consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案

首先去consul官网下载二进制文件

Downloads | Consul by HashiCorp

安装直接下载zip包,解压后只有一个可执行的文件consul,将consul添加到系统的环境变量里面。

打开命令行并输入consul.exe  agent -dev -bind {你本机的ip地址} 来运行consul服务

在主服务(Soa.Sample.Web)和各个服务Host的appsettings.json中,配置如下:

配置文件中设置Discovery

    "Discovery": "Consul",

设置Consul

    "ConsulServiceDiscovery": {
      "Ip": "127.0.0.1",
      "Port": "8500"
    }

  注意8500为consul所在ip地址

 服务运行时将自动注册ip和服务名称到consul的kv中,待客户端调用时,只需要传入key值即可

当服务运行之后,在浏览器地址栏输入127.0.0.1:8500查看已在consul注册的服务

服务监控

在​ServicesManagerController是获取服务列表和服务详情的Api接口控制器

    [Route("api/[controller]/[action]")]
    public class ServicesManagerController:AbpController
    {
        private readonly IClientServiceDiscovery clientServiceDiscovery;

        public ServicesManagerController(IClientServiceDiscovery clientServiceDiscovery)
        {
            this.clientServiceDiscovery = clientServiceDiscovery;
        }
      
        [HttpGet]
        public async Task<List<SoaAddress>> GetAddresses()
        {
            var addresses = await clientServiceDiscovery.GetAddressAsync();
            return addresses;

        }


        [HttpGet]
        public async Task<List<SoaServiceDesc>> GetServices(string server)
        {
            var routes = await clientServiceDiscovery.GetRoutesAsync();
            if (routes != null && routes.Any() && !string.IsNullOrEmpty(server))
            {
                return (from route in routes
                        where route.Address.Any(x => x.Code == server)
                        select route.ServiceDescriptor).ToList();
            }
            return (from route in routes select route.ServiceDescriptor).ToList();

        }

    }

 运行项目,在swagger中调试请求GetSoaService接口,可以获取所有服务信息

健康监测

健康监测用于确定各服务的健康程度,比如是否运行缓慢,是否链接顺畅等。

用一个简单的监测链接是否超时方式,确定这个服务是否健康。

using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = timeout })
{
    try
    {
        await socket.ConnectAsync(server.CreateEndPoint());
        server.IsHealth = true;
    }
    catch
    {
        server.IsHealth = false;
    }
}

在主服务(Soa.Sample.Web)和各个服务Host的appsettings.json中,配置如下:

"HealthCheck": {
      "Enable": true,
      "JobCron": "0/5 * * * * ? ",
      "Timeout": 3000
    },

 这里健康监测服务的轮询时间是5s,超时时间为3000ms

运行后关闭一个微服务,若关闭Service2,请求/api/ServicesManager/GetAddresses

可以看到IsHealth变为False

项目地址:

jevonsflash/Soa: (github.com)

使用 **.NET 9 + ABP Framework (ASP.NET Boilerplate 或 Abp vNext) + SQL Server** 的后端架构中,保存文件或图片时通常**不推荐直接将文件存储在数据中(如使用 `varbinary(max)`)**,而是建议将文件**保存到磁盘、网络路径或对象存储(如 Azure Blob、AWS S3)中**,然后在数据中仅保存文件的**元信息(如路径、名称、大小、MIME 类型等)**。 但根据你的需求,我会详细介绍两种方式: --- ### ✅ 推荐方案:文件存磁盘/云存储,数据只存元数据 #### 1. 创建文件实体(Entity) ```csharp using System; using Abp.Domain.Entities.Auditing; public class AppFile : FullAuditedEntity<long> { public string FileName { get; set; } // 原始文件名 public string StoredFileName { get; set; } // 存储时的唯一文件名(如 GUID) public string FilePath { get; set; } // 相对路径或 URL public long FileSize { get; set; } // 文件大小(字节) public string MimeType { get; set; } // MIME 类型,如 image/jpeg public string FileType { get; set; } // 自定义分类(如 Avatar, Document 等) } ``` #### 2. 在 `.DbContext` 中注册实体 ```csharp public class YourAppDbContext : AbpZeroCoreDbContext<YourAppDbContext> { public DbSet<AppFile> AppFiles { get; set; } public YourAppDbContext(DbContextOptions<YourAppDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<AppFile>(e => { e.ToTable("AppFiles"); e.Property(x => x.FileName).HasMaxLength(255); e.Property(x => x.StoredFileName).HasMaxLength(255); e.Property(x => x.FilePath).HasMaxLength(1000); e.Property(x => x.MimeType).HasMaxLength(100); e.Property(x => x.FileType).HasMaxLength(50); }); } } ``` #### 3. 文件上传服务(IFileAppService) ```csharp using Microsoft.AspNetCore.Http; using System.IO; using System.Threading.Tasks; using Abp.Application.Services; using Abp.Authorization; using Abp.Domain.Repositories; [AbpAuthorize] public class FileAppService : ApplicationService, IFileAppService { private readonly IRepository<AppFile, long> _fileRepository; private readonly IWebHostEnvironment _env; public FileAppService(IRepository<AppFile, long> fileRepository, IWebHostEnvironment env) { _fileRepository = fileRepository; _env = env; } public async Task<long> UploadFileAsync(IFormFile file, string fileType = "") { if (file == null || file.Length == 0) throw new UserFriendlyException("文件为空"); var extension = Path.GetExtension(file.FileName); var storedFileName = $"{Guid.NewGuid()}{extension}"; // 避免重名 var uploadFolder = Path.Combine(_env.WebRootPath, "uploads"); // wwwroot/uploads var filePath = Path.Combine(uploadFolder, storedFileName); // 确保目录存在 if (!Directory.Exists(uploadFolder)) Directory.CreateDirectory(uploadFolder); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } var appFile = new AppFile { FileName = file.FileName, StoredFileName = storedFileName, FilePath = $"/uploads/{storedFileName}", // 可通过 HTTP 访问 FileSize = file.Length, MimeType = file.ContentType, FileType = fileType }; await _fileRepository.InsertAsync(appFile); await CurrentUnitOfWork.SaveChangesAsync(); return appFile.Id; } } ``` #### 4. 配置静态文件访问(`Program.cs`) ```csharp var builder = WebApplication.CreateBuilder(args); // 其他配置... var app = builder.Build(); // 启用静态文件中间件(用于访问 /uploads 下的图片) app.UseStaticFiles(); // 允许访问 wwwroot 下的静态文件 app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(builder.Environment.WebRootPath, "uploads")), RequestPath = "/uploads" }); app.MapControllers(); app.Run(); ``` --- ### 🔺 可选方案:将文件以二进制形式存入 SQL Server > ⚠️ 不推荐大文件使用此方式,会影响数据性能备份。 #### 修改实体: ```csharp public class AppFile : FullAuditedEntity<long> { public string FileName { get; set; } public string MimeType { get; set; } public byte[] Content { get; set; } // 文件内容 public long FileSize { get; set; } } ``` #### 修改上传逻辑(小文件适用): ```csharp public async Task<long> UploadFileToDatabaseAsync(IFormFile file) { if (file == null || file.Length == 0) throw new UserFriendlyException("文件为空"); byte[] content; using (var memoryStream = new MemoryStream()) { await file.CopyToAsync(memoryStream); content = memoryStream.ToArray(); } var appFile = new AppFile { FileName = file.FileName, MimeType = file.ContentType, Content = content, FileSize = file.Length }; await _fileRepository.InsertAsync(appFile); await CurrentUnitOfWork.SaveChangesAsync(); return appFile.Id; } ``` > 查询时直接返回 `byte[]` 即可用于下载。 --- ### 📌 如何提供文件下载? ```csharp [HttpGet("download/{id}")] public async Task<FileResult> DownloadFile(long id) { var file = await _fileRepository.FirstOrDefaultAsync(id); if (file == null || file.Content == null) throw new UserFriendlyException("文件不存在"); return File(file.Content, file.MimeType, file.FileName); } ``` --- ### ✅ 最佳实践总结 | 方式 | 优点 | 缺点 | 推荐场景 | |------|------|------|----------| | 存磁盘/CDN/Blob | 性能好、易扩展、节省 DB 资源 | 需管理路径一致性、备份分离 | ✅ 所有场景优先选择 | | 存数据(varbinary) | 事务一致、易于备份 | 性能差、膨胀数据 | ❌ 仅小文件、强一致性要求 | --- ### 💡 补充建议 - 使用 **Azure Blob Storage** 或 **MinIO/AWS S3** 更适合生产环境。 - 可结合 ABP 的模块化设计,封装成独立的 `FileManagementModule`。 - 添加权限控制:谁可以上传/下载? - 添加文件清理任务(定期删除过期文件)。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林晓lx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值