- 环境
- 需求
- 实现方法
- RabbiteMQ操作类
- 主要操作类 QueueOperator
- 异常类 QueueCanNotDoException
- 生产者(桌面程序 不记录)
- 消费者
- Program 代码
- Worker 代码
- Dockerfile
- 制作镜像
- docker build 命令
- 运行镜像
- 遇见的问题
- 问题1 docker run -v 挂在路径无效问题(在window环境中)
- 问题2 visual studio 容器工具首次加载太慢 vsdbgvs2017u5 exists, deleting 太慢或者卡在这里失败
- 问题3 The framework 'Microsoft.AspNetCore.App', version '5.0.0' was not found. (运行docker内的程序提示框架库找不到)
- .net 5
- Docker Desktop Version 3.3.3 (64133) - May 6 2021
- RabbitMQ 3.8.16 (部署在docker中 部署文档)
一个生产者将生产出来的数据发布到mq中,又多个消费者消费数据,并将消费结果反馈给mq,另外一个消费者消费结果数据。
实现方法一个Topic的mq,生产者将数据发给交换机,消费者绑定交换机,然后将结果上报给一个简单的mq,另外一个消费者消费结果。
using System;
using System.Text;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace Common.MQ
{
public class QueueOperator : IDisposable
{
///
/// 消息到达事件
///
public event Action MessageArrived;
///
/// 消息被拒绝事件
///
public event Action MessageDenied;
///
/// 当不能执行触发异常时的回调
///
public event Action QueueCanNotDoneed;
private ConnectionFactory factory;
private IConnection connection;
private bool isFirst = true;
public QueueOperator(string username, string pwd, string db, string hostname, int port)
{
factory = new ConnectionFactory()
{
UserName = username,
Password = pwd,
VirtualHost = db,
HostName = hostname,
Port = port
};
}
///
/// 异步发送消息
///
///
///
///
///
public void SendTopicAsnyc(string exchangeName, string routingkey, TData data) where TData : class, new()
{
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
CheckIsFirst(exchangeName, channel);
channel.BasicAcks += Channel_BasicAcks;
channel.BasicNacks += Channel_BasicNacks;
channel.ConfirmSelect();
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
channel.BasicPublish(exchange: exchangeName,
routingKey: routingkey,
basicProperties: null,
body: body);
}
}
public void SendSampleAsnyc(string queueName, TData data)
{
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
CreateQueue(queueName, channel);
channel.BasicAcks += Channel_BasicAcks;
channel.BasicNacks += Channel_BasicNacks;
channel.ConfirmSelect();
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
channel.BasicPublish(exchange: "",
routingKey: queueName,
basicProperties: null,
body: body);
}
}
private void CheckIsFirst(string exchangeName, IModel channel)
{
if (isFirst)
{
channel.BasicQos(0, 1, true);
CreateExchage(exchangeName, "topic", channel);
// CreateQueue(routingkey, channel);
// channel.QueueBind(routingkey, exchangeName, routingkey, null);
isFirst = false;
}
}
///
/// 绑定队列
///
///
///
///
/// 回调事件,收到数据要做的事情
public void BindQueue(string exchangeName, string routingkey, Action callback) where TData : class, new()
{
connection = factory.CreateConnection();
var channel = connection.CreateModel();
CheckIsFirst(exchangeName, channel);
var queueName = channel.QueueDeclare().QueueName;
channel.QueueBind(queueName, exchangeName, routingkey);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
callback?.Invoke(JsonConvert.DeserializeObject(message));
//反馈处理完成信号
channel.BasicAck(ea.DeliveryTag, false);
};
channel.BasicConsume(queue: queueName,
autoAck: false,
consumer: consumer);
}
///
/// 绑定队列
///
///
///
/// 回调事件,收到数据要做的事情
public void BindQueue(string queueName, Action callback) where TData : class, new()
{
if (connection == null)
connection = factory.CreateConnection();
var channel = connection.CreateModel();
CreateQueue(queueName, channel);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
callback?.Invoke(JsonConvert.DeserializeObject(message));
//反馈处理完成信号
channel.BasicAck(ea.DeliveryTag, false);
};
channel.BasicConsume(queue: queueName,
autoAck: false,
consumer: consumer);
}
private void CreateExchage(string name, string type, IModel channel)
{
try
{
channel.ExchangeDeclare(exchange: name,
type: type);
}
catch (Exception ex)
{
QueueCanNotDoneed?.Invoke(new QueueCanNotDoException(ex) { CanNotType = "交换机创建异常" });
}
}
private void CreateQueue(string name, IModel channel)
{
try
{
channel.QueueDeclare(name, false, false, false, null);
}
catch (Exception ex)
{
QueueCanNotDoneed?.Invoke(new QueueCanNotDoException(ex) { CanNotType = $"Queue[{name}]创建异常" });
}
}
///
/// 拒绝消息
///
///
///
private void Channel_BasicNacks(object sender, RabbitMQ.Client.Events.BasicNackEventArgs e)
{
MessageDenied?.Invoke(e.DeliveryTag, e.Multiple);
}
///
/// 接收消息
///
///
///
private void Channel_BasicAcks(object sender, RabbitMQ.Client.Events.BasicAckEventArgs e)
{
MessageArrived?.Invoke(e.DeliveryTag, e.Multiple);
}
public void Dispose()
{
if (connection.IsOpen)
connection.Close();
}
}
}
异常类 QueueCanNotDoException
using System;
using System.Collections.Generic;
using System.Text;
namespace Common.MQ
{
public class QueueCanNotDoException : Exception
{
public QueueCanNotDoException(Exception ex)
{
new Exception(ex?.Message??"", ex);
}
public string CanNotType { get; set; }
public override string ToString()
{
return $"{CanNotType} 不能被执行,详细信息:rn{Message}";
}
}
}
生产者(桌面程序 不记录)
生产者这里没有好记录的,正常操作mq就行了
消费者.net 5 的 work service
Program 代码 public static IHostBuilder CreateHostBuilder(string[] args)
{
var ihostbuilder = Host.CreateDefaultBuilder(args);
//window和linux的路径表示方式不一样,虽然/在win10中也能识别,但是尽量保持系统原有风格
var configFile = AppDomain.CurrentDomain.BaseDirectory;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
ihostbuilder.UseWindowsService();
configFile += "conf\appsettings.json";
}
else
{
ihostbuilder.UseSystemd();
configFile += "conf/appsettings.json";
}
ihostbuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
config.Sources.Clear();
//更改配置文件的路径,方便docker进行外部映射 Volume
config.AddJsonFile(configFile);
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog();
return ihostbuilder;
}
Worker 代码
public class Worker : BackgroundService
{
private readonly ILogger _logger;
private static QueueOperator queueOperator;
private MQSetting setting;
public Worker(ILogger logger, IConfiguration configuration)
{
_logger = logger;
setting = configuration.GetSection("MQSetting").Get();
}
public override Task StartAsync(CancellationToken cancellationToken)
{
try
{
queueOperator = new QueueOperator(setting.UserName, setting.Password, setting.DBName, setting.HostName, setting.Port);
queueOperator.BindQueue>(setting.ExchangeName, setting.RoutingKey, data =>
{
Console.WriteLine(data.Step);
queueOperator.SendSampleAsnyc>(setting.ResultRoutingKey, new CarlingResultData() { Step = data.Step, Key = data.Key });
});
Console.WriteLine("初始化mq完成");
}
catch (Exception ex)
{
Console.WriteLine("初始化mq异常:" + ex.Message);
}
return base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine($"Worker running at: { DateTimeOffset.Now}");
// _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
Dockerfile
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. # 这里要注意,vs生成的默认dockerfile中这里的runtime:5.0的版本,这个版本会导致做成镜像无法运行,因为缺少.net 5 库。 FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base WORKDIR /app FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build WORKDIR /src RUN dotnet restore COPY . . #FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build #WORKDIR /src #COPY ["service/Shimada.CarlingService/Shimada.CarlingService.csproj", "service/Shimada.CarlingService/"] #RUN dotnet restore "service/Shimada.CarlingService/Shimada.CarlingService.csproj" #COPY . . #WORKDIR "/src/service/Shimada.CarlingService" #RUN dotnet build "Shimada.CarlingService.csproj" -c Release -o /app/build # #FROM build AS publish #RUN dotnet publish "Shimada.CarlingService.csproj" -c Release -o /app/publish #FROM base AS final #WORKDIR /app #COPY --from=publish /app/publish . #ENTRYPOINT ["dotnet", "Shimada.CarlingService.dll"] FROM base AS debuge WORKDIR /app COPY "bin/Debug/net5.0/" "/app" EXPOSE 8080/tcp EXPOSE 8080/udp # 这里将conf配置文件目录和Logs日志文件目录映射出来,方便在docker外面修改和查看数据。 VOLUME ["/app/conf","/app/Logs"] ENTRYPOINT ["dotnet", "Shimada.CarlingService.dll"]制作镜像 docker build 命令
docker build -t carlingservice:0.0.5.5 C:shimadaSY-VIS-2101srcSY_VIS_2101serviceShimada.CarlingService
命令语法
Usage: docker build [OPTIONS] PATH | URL | -
Build an image from a Dockerfile
Options:
--add-host list Add a custom host-to-IP mapping (host:ip)
--build-arg list Set build-time variables
--cache-from strings Images to consider as cache sources
--disable-content-trust Skip image verification (default true)
-f, --file string Name of the Dockerfile (Default is
'PATH/Dockerfile')
--iidfile string Write the image ID to the file
--isolation string Container isolation technology
--label list Set metadata for an image
--network string Set the networking mode for the RUN
instructions during build (default "default")
--no-cache Do not use cache when building the image
-o, --output stringArray Output destination (format:
type=local,dest=path)
--platform string Set platform if server is multi-platform
capable
--progress string Set type of progress output (auto, plain,
tty). Use plain to show container output
(default "auto")
--pull Always attempt to pull a newer version of
the image
-q, --quiet Suppress the build output and print image
ID on success
--secret stringArray Secret file to expose to the build (only
if BuildKit enabled):
id=mysecret,src=/local/secret
--ssh stringArray SSH agent socket or keys to expose to the
build (only if BuildKit enabled) (format:
default|[=|[,]])
-t, --tag list Name and optionally a tag in the
'name:tag' format
--target string Set the target build stage to build.
运行镜像
docker run --name debug2 -d carlingservice:0.0.5.9 -v /C/shimada/SY-VIS-2101/src/SY_VIS_2101/service/Shimada.CarlingService/bin/Logs:/app/Logs遇见的问题 问题1 docker run -v 挂在路径无效问题(在window环境中)
https://blog.csdn.net/ap10062kai/article/details/79232582
互联网解决办法,在我这里没有解决
我的解决办法:
使用docker desktop工具运行的镜像,指定的挂在路径就可以了,有谁知道用命令怎么写的可以告诉我
2021年5月13日 补充
使用命令可以实现挂载
docker run -v C:shimadaSY-VIS-2101srcSY_VIS_2101serviceShimada.CarlingServicebinconf:/app/conf -v C:shimadaSY-VIS-2101srcSY_VIS_2101serviceShimada.CarlingServicebinLogs:/app/Logs --name debug3 -d carlingservice:0.0.5.5
重点: -v参数一定要紧跟着run 如果在run和-v中间添加了其他参数会导致执行失败。
desktop版本的docker在添加挂载的时候会有确认提示框,提示是否分享目录。(也可以提前在Setting中配置好File Sharing)
可以按照 https://www.cnblogs.com/microestc/p/10784877.html 的办法解决
个人觉得上面这个办法太麻烦了,直接开了个代理,因为国内的墙无法下载一些地址的文件导致的。
在dockerfile中进行修改 (看上面的dockerfile源码)
# 这里要注意,vs生成的默认dockerfile中这里的runtime:5.0的版本,这个版本会导致做成镜像无法运行,因为缺少.net 5 库。 FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
https://docs.docker.com/engine/reference/builder/#usage (dockerfile 文档)
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#file-configuration-provider (appsettings.json 文档)
https://www.cnblogs.com/microestc/p/10784877.html (使用vs生成镜像失败或者非常慢的解决办法)
https://blog.csdn.net/shanghaibao123/article/details/108278639
https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html



