.NET Core微服务之基于MassTransit实现数据最终一致性(Part 1)
Tip: 此篇已加入.NET Core微服务基础系列文章索引
一、预备知识:数据一致性
关于数据一致性的文章,园子里已经有很多了,如果你还不了解,那么可以通过以下的几篇文章去快速地了解了解,有个感性认识即可。
(1)左正,《保证分布式系统数据一致性的6种方案》
(2)成金之路,《分布式系统的数据一致性解决方案》
(3)E_Star,《分布式环境下数据一致性的设计总结》
(4)Itegel,《分布式事务?No,最终一致性》
必须要了解的点:ACID、CAP、BASE、强一致性、弱一致性、最终一致性。
CAP理论由加州大学伯克利分校的计算机教授Eric Brewer在2000年提出,其核心思想是任何基于网络的数据共享系统最多只能满足数据一致性(Consistency)、可用性(Availability)和网络分区容忍(Partition Tolerance)三个特性中的两个(由此我们知道在分布式系统中,同时满足CAP三个特性是不可能的),三个特性的定义如下:
C:数据一致性(Consistency):如果系统对一个写操作返回成功,那么之后的读请求都必须读到这个新数据;如果返回失败,那么所有读操作都不能读到这个数据,对调用者而言数据具有强一致性(Strong Consistency)(又叫原子性Atomic或线性一致性Linerizable Consistency)
A:服务可用性(Availability):所有读写请求在一定时间内得到响应,可终止、不会一直等待
P:分区容错性(Partition-Tolerance):在网络分区的情况下,被分隔的节点仍能正常对外服务
- 强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。=> 在传统单体式应用中,大部分都是强一致性的应用,想想我们写过多少工作单元模式的Code?
- 弱一致性:系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。
- 最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。
为保证可用性,互联网分布式架构中经常将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性。
在微服务架构中,各个微服务之间通常会使用事件驱动通信和发布订阅系统实现最终一致性。
更多背景知识,还是得看上面列出的参考文章,这里不再赘述。
二、MassTransit极简介绍
MassTransit 是一个自由、开源、轻量级的消息总线, 用于使用. NET 框架创建分布式应用程序。MassTransit 在现有消息传输上提供了一组广泛的功能, 从而使开发人员能够友好地使用基于消息的会话模式异步连接服务。基于消息的通信是实现面向服务的体系结构的可靠和可扩展的方式。
官网地址:http://masstransit-project.com/,GitHub地址:https://github.com/MassTransit/MassTransit (目前:1590Star,719Fork)
类似的国外开源组件还有NServiceBus,没有用过,据说MassTransit比NServiceBus更加轻量级,并且在开发之初就选用了RabbitMQ作为消息传输组件,当然MassTransit还支持Azure Service Bus。类似的国内开源组件则有园友savorboard(杨晓东)的CAP,这个我会在MassTransit学习结束后去使用使用,CAP在GitHub上已经有了超过1000个Star,是NCC的几个千星项目之一。另外,张善友大队长在他的NanoFabric项目中推荐我们使用Rebus和Ray,如下图所示:
由于时间和精力,以及文档资料的可见性,我在我的POC和这个系列博文的准备中,只会使用到MassTransit和CAP这两个开源项目。
三、MassTransit Quick Start
这里以MassTransit + RabbitMQ为例子,首先请确保安装了RabbitMQ,如果没有安装,可以阅读我的《基于EasyNetQ使用RabbitMQ消息队列》去把RabbitMQ先安装到你的电脑上。另外,RabbitMQ的背景知识也有一堆,有机会也还是要了解下Exchange,Channel、Queue等内容。
3.1 最简单的发送/接收实例
(1)准备两个控制台程序,一个为Sender(发送者),一个为Receiver(接收者),并分别通过NuGet安装MassTransit以及MassTransit.RabbitMQ
NuGet>Install-Package MassTransit
NuGet>Install-Package MassTransit.RabbitMQ
(2)编写Sender
public class Program
{
public static void Main(string[] args)
{
Console.Title = "MassTransit Client"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
});
}); var uri = new Uri("rabbitmq://192.168.80.71/EDCVHOST/Qka.MassTransitTest");
var message = Console.ReadLine(); while (message != null)
{
Task.Run(() => SendCommand(bus, uri, message)).Wait();
message = Console.ReadLine();
} Console.ReadKey();
} private static async void SendCommand(IBusControl bus, Uri sendToUri, string message)
{
var endPoint = await bus.GetSendEndpoint(sendToUri);
var command = new Client()
{
Id = ,
Name = "Edison Zhou",
Birthdate = DateTime.Now.AddYears(-),
Message = message
}; await endPoint.Send(command); Console.WriteLine($"You Sended : Id = {command.Id}, Name = {command.Name}, Message = {command.Message}");
}
}
这里首先连接到我的RabbitMQ服务,然后向指定的Queue发送消息(这里是一个Client类型的实例对象)。
(3)编写Receiver
public class Program
{
public static void Main(string[] args)
{
Console.Title = "MassTransit Server"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
}); cfg.ReceiveEndpoint(host, "Qka.MassTransitTest", e=>
{
e.Consumer<TestConsumerClient>();
e.Consumer<TestConsumerAgent>();
});
}); bus.Start();
Console.WriteLine("Press any key to exit.");
Console.ReadKey(); bus.Stop();
}
}
对于Receiver,要做的事就只有两件:一是连接到RabbitMQ,二是告诉RabbitMQ我要接收哪个消息队列的什么类型的消息。下面是TestConsumerClient和TestConsumerAgent的定义:
public class TestConsumerClient : IConsumer<Client>
{
public async Task Consume(ConsumeContext<Client> context)
{
Console.ForegroundColor = ConsoleColor.Red;
await Console.Out.WriteLineAsync($"Receive message: {context.Message.Id}, {context.Message.Name}, {context.Message.Birthdate.ToString()}");
Console.ResetColor();
}
} public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthdate { get; set; }
public string Message { get; set; }
} public class TestConsumerAgent : IConsumer<Agent>
{
public async Task Consume(ConsumeContext<Agent> context)
{
Console.ForegroundColor = ConsoleColor.Red;
await Console.Out.WriteLineAsync($"Receive message: {context.Message.AgentCode}, {context.Message.AgentName}, {context.Message.AgentRole}");
Console.ResetColor();
}
} public class Agent
{
public int AgentCode { get; set; }
public string AgentName { get; set; }
public string AgentRole { get; set; }
public string Message { get; set; }
}
(4)测试一下:
3.2 最简单的发布/订阅实例
除了简单的发送/接收模式外,我们用的更多的是发布/订阅这种模式。
(1)准备下图所示的类库和控制台项目,并对除Messages类库之外的其他项目安装MassTransit以及MassTransit.RabbitMQ。
(2)Messages类库:准备需要传输的Message
public class TestBaseMessage
{
public string Name { get; set; } public DateTime Time { get; set; } public string Message { get; set; }
} public class TestCustomMessage
{
public string Name { get; set; } public DateTime Time { get; set; } public int Age { get; set; } public string Message { get; set; }
}
(3)Publisher:接收我的消息吧骚年们
public class Program
{
public static void Main(string[] args)
{
Console.Title = "MassTransit Publisher"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST/"), hst =>
{
hst.Username("admin");
hst.Password("edison");
});
}); do
{
Console.WriteLine("Please enter your message, if want to exit please press q.");
string message = Console.ReadLine();
if (message.ToLower().Equals("q"))
{
break;
} bus.Publish(new TestBaseMessage()
{
Name = "Edison Zhou",
Time = DateTime.Now,
Message = message
}); bus.Publish(new TestCustomMessage()
{
Name = "Leo Dai",
Age = ,
Time = DateTime.Now,
Message = message
});
} while (true); bus.Stop();
}
这里向RabbitMQ发布了两个不同类型的消息(TestBaseMessage和TestCustomMessage)
(4)SubscriberA:我只接收TestBaseMessage类型的消息,其他的我不要
public class Program
{
public static void Main(string[] args)
{
Console.Title = "MassTransit SubscriberA"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
}); cfg.ReceiveEndpoint(host, "Qka.MassTransitTestv2.CA", e =>
{
e.Consumer<ConsumerA>();
});
}); bus.Start();
Console.ReadKey(); // press Enter to Stop
bus.Stop();
}
}
这里ConsumerA的定义如下:可以看出,它实现了一个泛型接口IConsumer,然后指定了TestBaseMessage为消费的消息类型。
public class ConsumerA : IConsumer<TestBaseMessage>
{
public async Task Consume(ConsumeContext<TestBaseMessage> context)
{
Console.ForegroundColor = ConsoleColor.Red;
await Console.Out.WriteLineAsync($"SubscriberA => ConsumerA received message : {context.Message.Name}, {context.Message.Time}, {context.Message.Message}, Type:{context.Message.GetType()}");
Console.ResetColor();
}
}
(5)SubscriberA:我只接收TestCustomMessage类型的消息,其他的我不要
public class Program
{
public static void Main(string[] args)
{
Console.Title = "MassTransit SubscriberB"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
}); cfg.ReceiveEndpoint(host, "Qka.MassTransitTestv2.CB", e =>
{
e.Consumer<ConsumerA>();
});
}); bus.Start();
Console.ReadKey(); // press Enter to Stop
bus.Stop();
}
}
这里的ConsumerA的定义如下;它实现的接口是IConsumer<TestCustomMessage>
public class ConsumerA : IConsumer<TestCustomMessage>
{
public async Task Consume(ConsumeContext<TestCustomMessage> context)
{
Console.ForegroundColor = ConsoleColor.Red;
await Console.Out.WriteLineAsync($"SubscriberB => ConsumerA received message : {context.Message.Name}, {context.Message.Time}, {context.Message.Message}, Age: {context.Message.Age}, Type:{context.Message.GetType()}");
Console.ResetColor();
}
}
(6)测试一下:由于Publisher发送了两个不同类型的消息,两个Subscriber均只接受其中的一个类型的消息。
3.3 带返回状态消息的示例
之前的例子都是发布之后,不管订阅者有没有收到以及收到后有没有处理成功(即有没有返回消息,类似于HTTP请求和响应),在MassTransit中提供了这样的一种模式,并且还可以结合GreenPipes的一些扩展方法实现重试、限流以及熔断机制。这一部分详见官方文档:http://masstransit-project.com/MassTransit/usage/request-response.html
(1)准备下图所示的三个项目:通过NuGet安装MassTransit以及MassTransit.RabbitMQ
(2)Messages:准备请求和响应的消息传输类型
public interface IRequestMessage
{
int MessageId { get; set; }
string Content { get; set; }
} public class RequestMessage : IRequestMessage
{
public int MessageId { get; set; }
public string Content { get; set; } public int RequestId { get; set; }
} public interface IResponseMessage
{
int MessageCode { get; set; }
string Content { get; set; }
} public class ResponseMessage : IResponseMessage
{
public int MessageCode { get; set; }
public string Content { get; set; } public int RequestId { get; set; }
}
(3)Sender 请求发送端
public class Program
{
public static void Main(string[] args)
{
Console.Title = "Masstransit Request Side"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
});
// Retry : 重试
cfg.UseRetry(ret =>
{
ret.Interval(, ); // 消费失败后重试3次,每次间隔10s
});
// RateLimit : 限流
cfg.UseRateLimit(, TimeSpan.FromMinutes()); // 1分钟以内最多1000次调用访问
// CircuitBreaker : 熔断
cfg.UseCircuitBreaker(cb =>
{
cb.TrackingPeriod = TimeSpan.FromMinutes();
cb.TripThreshold = ; // 当失败的比例至少达到15%才会启动熔断
cb.ActiveThreshold = ; // 当失败次数至少达到10次才会启动熔断
cb.ResetInterval = TimeSpan.FromMinutes();
}); // 当在1分钟内消费失败率达到15%或调用了10次还是失败时,暂停5分钟的服务访问
}); bus.Start(); SendMessage(bus); bus.Stop();
} private static void SendMessage(IBusControl bus)
{
var mqAddress = new Uri($"rabbitmq://192.168.80.71/EDCVHOST/Qka.MassTransitTestv3");
var client = bus.CreateRequestClient<IRequestMessage, IResponseMessage>(mqAddress,
TimeSpan.FromHours()); // 创建请求客户端,10s之内木有回馈则认为是超时(Timeout) do
{
Console.WriteLine("Press q to exit if you want.");
string value = Console.ReadLine();
if (value.ToLower().Equals("q"))
{
break;
} Task.Run(async () =>
{
var request = new RequestMessage()
{
MessageId = ,
Content = value,
RequestId =
}; var response = await client.Request(request); Console.WriteLine($"Request => MessageId={request.MessageId}, Content={request.Content}");
Console.WriteLine($"Response => MessageCode={response.MessageCode}, Content={response.Content}");
}).Wait();
} while (true);
}
}
这里不再解释,请看注释。
(4)Receiver 接收端
public class Program
{
public static void Main(string[] args)
{
Console.Title = "MassTransit Response Side"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
}); cfg.ReceiveEndpoint(host, "Qka.MassTransitTestv3", e =>
{
e.Consumer<RequestConsumer>();
});
}); bus.Start();
Console.WriteLine("Press any key to exit.");
Console.ReadKey(); bus.Stop();
}
}
其中,RequestConsumer的定义如下:
public class RequestConsumer : IConsumer<IRequestMessage>
{
public async Task Consume(ConsumeContext<IRequestMessage> context)
{
Console.ForegroundColor = ConsoleColor.Red;
await Console.Out.WriteLineAsync($"Received message: Id={context.Message.MessageId}, Content={context.Message.Content}");
Console.ResetColor(); var response = new ResponseMessage
{
MessageCode = ,
Content = $"Success",
RequestId = context.Message.MessageId
}; Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Response message: Code={response.MessageCode}, Content={response.Content}, RequestId={response.RequestId}");
Console.ResetColor();
await context.RespondAsync(response);
}
}
(5)测试一下:
可以看出,请求调用方收到了来自接收方返回的状态消息,我们可以借助返回值去check一些状态。这里不再演示发生异常从而启用重试、熔断等的示例,有兴趣的园友可以自行测试。
3.4 Observer模式的发布/订阅示例
在某些场景中,我们需要针对一个消息进行类似于AoP(面向切面编程)或者监控的操作,比如在发送消息之前和结束后记日志等操作,我们可以借助MassTransit中的Observer模式来实现。(在MassTransit的消息接收中,可以通过两种模式来实现:一种是基于实现IConsumer接口,另一种就是基于实现IObserver接口)关于这一部分,详见官方文档:http://masstransit-project.com/MassTransit/usage/observers.html
(1)准备以下图所示的项目:
(2)Messages:定义要使用的Consumer和Observer
Consumer:
public class TestMessageConsumer : IConsumer<TestMessage>
{
public async Task Consume(ConsumeContext<TestMessage> context)
{
Console.ForegroundColor = ConsoleColor.Red;
await Console.Out.WriteLineAsync($"TestMessageConsumer => Type:{context.Message.GetType()}, ID:{context.Message.MessageId}, Content:{context.Message.Content}");
Console.ResetColor();
}
}
Observer:
public class PublishObserver : IPublishObserver
{
public Task PrePublish<T>(PublishContext<T> context) where T : class
{
Console.WriteLine("------------------PrePublish--------------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("----------------------------------------------------"); return Task.CompletedTask;
} public Task PostPublish<T>(PublishContext<T> context) where T : class
{
Console.WriteLine("------------------PostPublish--------------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("----------------------------------------------------"); return Task.CompletedTask;
} public Task PublishFault<T>(PublishContext<T> context, Exception exception) where T : class
{
Console.WriteLine("------------------PublishFault--------------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("------------------------------------------------------"); return Task.CompletedTask;
}
} public class SendObserver : ISendObserver
{
public Task PreSend<T>(SendContext<T> context) where T : class
{
Console.WriteLine("------------------PreSend--------------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("-------------------------------------------------"); return Task.CompletedTask;
} public Task PostSend<T>(SendContext<T> context) where T : class
{
Console.WriteLine("------------------PostSend-------------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("-------------------------------------------------"); return Task.CompletedTask;
} public Task SendFault<T>(SendContext<T> context, Exception exception) where T : class
{
Console.WriteLine("------------------SendFault-----------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("-------------------------------------------------"); return Task.CompletedTask;
}
} public class ReceiveObserver : IReceiveObserver
{
public Task PreReceive(ReceiveContext context)
{
Console.WriteLine("------------------PreReceive--------------------");
Console.WriteLine(Encoding.Default.GetString(context.GetBody()));
Console.WriteLine("--------------------------------------"); return Task.CompletedTask;
} public Task PostReceive(ReceiveContext context)
{
Console.WriteLine("------------------PostReceive--------------------");
Console.WriteLine(Encoding.Default.GetString(context.GetBody()));
Console.WriteLine("------------------------------------------------------"); return Task.CompletedTask;
} public Task ReceiveFault(ReceiveContext context, Exception exception)
{
Console.WriteLine("------------------ReceiveFault--------------------");
Console.WriteLine(Encoding.Default.GetString(context.GetBody()));
Console.WriteLine("-------------------------------------------------------"); return Task.CompletedTask;
} public Task PostConsume<T>(ConsumeContext<T> context, TimeSpan duration, string consumerType) where T : class
{
Console.WriteLine("------------------PostConsume--------------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("--------------------------------------------------------"); return Task.CompletedTask;
} public Task ConsumeFault<T>(ConsumeContext<T> context, TimeSpan duration, string consumerType, Exception exception) where T : class
{
Console.WriteLine("------------------ConsumeFault-------------------");
var message = context.Message as TestMessage;
Console.WriteLine($"ID={message.MessageId}, Content={message.Content},Time={message.Time}");
Console.WriteLine("--------------------------------------------------------"); return Task.CompletedTask;
}
}
(3)ObserverPublish
public class Program
{
public static void Main(string[] args)
{
Console.Title = "Masstransit Observer Publisher"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
});
}); var observer1 = new SendObserver();
var handle1 = bus.ConnectSendObserver(observer1); var observer2 = new PublishObserver();
var handle2 = bus.ConnectPublishObserver(observer2); bus.Start(); do
{
Console.WriteLine("Presss q if you want to exit this program.");
string message = Console.ReadLine();
if (message.ToLower().Equals("q"))
{
break;
} bus.Publish(new TestMessage
{
MessageId = ,
Content = message,
Time = DateTime.Now
});
} while (true); bus.Stop();
}
}
可以看到,这里我们使用了两个用于发送消息的Observer:SendObserver和PublishObserver
(3)ObserverReceive
public class Program
{
public static void Main(string[] args)
{
Console.Title = "Masstransit Observer Receiver"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://192.168.80.71/EDCVHOST"), hst =>
{
hst.Username("admin");
hst.Password("edison");
}); cfg.ReceiveEndpoint(host, "Qka.MassTransitTestv4", e =>
{
e.Consumer<TestMessageConsumer>();
});
}); var observer = new ReceiveObserver();
var handle = bus.ConnectReceiveObserver(observer); bus.Start();
Console.ReadKey();
bus.Stop();
}
}
(4)测试一下:
由此看出,我们可以借助Observer这种方式来截取到消息的一个lifecycle,用以进行不同阶段的业务逻辑操作。
四、小结
本篇极简的介绍了一下数据一致性和MassTransit这个开源的组件,通过几个例子介绍了在.NET环境下如何使用MassTransit操作RabbitMQ实现消息的接收/发送以及发布/订阅。后续我会继续使用MassTransit结合Quartz.Net和Polly在ASP.NET Core环境下实现一个简单的基于补偿机制的数据一致性的小案例,希望到时也可以和各位园友分享。
示例代码
Click Here => 点我下载
参考资料
(1)桂素伟,《基于.NET Core的微服务》
(2)richieyangs(张阳),《如何优雅的使用RabbitMQ》,《使用Masstransit开发基于消息传递的分布式应用》
(3)青客宝团队,《MassTransit&Sagas分布式服务开发ppt分享》
(4)成天,《MassTransit实现应用程序间的交互》
(5)娃娃都会打酱油了,《MassTransit学习记录》
(6)MassTransit 官方文档,http://masstransit-project.com/MassTransit/
.NET Core微服务之基于MassTransit实现数据最终一致性(Part 1)的更多相关文章
- .NET Core微服务之基于MassTransit实现数据最终一致性(Part 2)
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.案例结构与说明 在上一篇中,我们了解了MassTransit这个开源组件的基本用法,这一篇我们结合一个小案例来了解在ASP.NET C ...
- .NET Core微服务之基于Ocelot实现API网关服务
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.啥是API网关? API 网关一般放到微服务的最前端,并且要让API 网关变成由应用所发起的每个请求的入口.这样就可以明显的简化客户端 ...
- .NET Core微服务之基于Ocelot实现API网关服务(续)
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.负载均衡与请求缓存 1.1 负载均衡 为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientServic ...
- .NET Core微服务之基于Consul实现服务治理
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.Consul基础介绍 Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发 ...
- .NET Core微服务之基于Consul实现服务治理(续)
Tip: 此篇已加入.NET Core微服务基础系列文章索引 上一篇发布之后,很多人点赞和评论,不胜惶恐,这一篇把上一篇没有弄到的东西补一下,也算是给各位前来询问的朋友的一些回复吧. 一.Consul ...
- .NET Core微服务之基于IdentityServer建立授权与验证服务
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.IdentityServer的预备知识 要学习IdentityServer,事先得了解一下基于Token的验证体系,这是一个庞大的主题 ...
- .NET Core微服务之基于IdentityServer建立授权与验证服务(续)
Tip: 此篇已加入.NET Core微服务基础系列文章索引 上一篇我们基于IdentityServer4建立了一个AuthorizationServer,并且继承了QuickStartUI,能够成功 ...
- .NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.案例结构总览 这里,假设我们有两个客户端(一个Web网站,一个移动App),他们要使用系统,需要通过API网关(这里API网关始终作为 ...
- .NET Core微服务之基于Exceptionless实现分布式日志记录
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.Exceptionless极简介绍 Exceptionless 是一个开源的实时的日志收集框架,它可以应用在基于 ASP.NET,AS ...
随机推荐
- python获取操作系统平台、版本及架构
platform模块提供了底层系统平台的相关信息 系统架构 32位还是64位 >>>import platform >>> platform.architectur ...
- 如何设置PPT中的演讲者模式
①1.首先将投影设备或其它幻灯片输出设备连接到笔记本或 PC 上,在 Windows 7 中按Win 键+P 并选择扩展模式将当前笔记本或 PC 的显示器与投影显示输出设备设置为扩展模式. ②我们 ...
- HTTP状态码的详细解释,供参考
HTTP状态码详解 常用对照表 状态码 含义 100 客户端应当继续发送请求.这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝.客户端应当继续发送请求的剩余部分,或者如果请求已经 ...
- public_handers.go
package],,) ],,) ]:],,);:],],,) ) ]],,) )) ,) )) if etagMatch { w.WriteHeader(ht ...
- python获取当前时间
import time time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()) print("当前时间:",time) ...
- 原型模式--java代码实现
原型模式 原型模式,属于对象创建型模式中的一种.通过复制原型对象的方法来创建一个对象实例,且该对象与原对象有相同的数据结构和值.类似我们在备份数据库信息的时候,将数据库中表的结构和数据的一同备份,生成 ...
- 系统的讲解 - SSO单点登录
目录 概念 好处 技术实现 小结 扩展 概念 SSO 英文全称 Single Sign On,单点登录. 在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统. 比如:淘宝网(www.t ...
- linux安装nvm node版本管理器 nvm常用命令 部署node服务器环境
1,nvm git地址点击打开链接,安装命令 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh ...
- SpringMVC 中 @ControllerAdvice 注解的三种使用场景!
@ControllerAdvice ,很多初学者可能都没有听说过这个注解,实际上,这是一个非常有用的注解,顾名思义,这是一个增强的 Controller.使用这个 Controller ,可以实现三个 ...
- 『发呆』.Net 2.0 ~ .Net 4.0 所实现了那些底层
随着时间的推移,程序越写越大,代码越写越少. 今天突然发呆,就想比较全面的汇总一下 .Net 2.0 和 .Net 4.0 都实现的功能. .Net 2.0 的大部分常见程序集 (已经过滤掉了一部分和 ...