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/

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

.NET Core微服务之基于MassTransit实现数据最终一致性(Part 1)的更多相关文章

  1. .NET Core微服务之基于MassTransit实现数据最终一致性(Part 2)

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.案例结构与说明 在上一篇中,我们了解了MassTransit这个开源组件的基本用法,这一篇我们结合一个小案例来了解在ASP.NET C ...

  2. .NET Core微服务之基于Ocelot实现API网关服务

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.啥是API网关? API 网关一般放到微服务的最前端,并且要让API 网关变成由应用所发起的每个请求的入口.这样就可以明显的简化客户端 ...

  3. .NET Core微服务之基于Ocelot实现API网关服务(续)

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.负载均衡与请求缓存 1.1 负载均衡 为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientServic ...

  4. .NET Core微服务之基于Consul实现服务治理

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.Consul基础介绍 Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发 ...

  5. .NET Core微服务之基于Consul实现服务治理(续)

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 上一篇发布之后,很多人点赞和评论,不胜惶恐,这一篇把上一篇没有弄到的东西补一下,也算是给各位前来询问的朋友的一些回复吧. 一.Consul ...

  6. .NET Core微服务之基于IdentityServer建立授权与验证服务

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.IdentityServer的预备知识 要学习IdentityServer,事先得了解一下基于Token的验证体系,这是一个庞大的主题 ...

  7. .NET Core微服务之基于IdentityServer建立授权与验证服务(续)

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 上一篇我们基于IdentityServer4建立了一个AuthorizationServer,并且继承了QuickStartUI,能够成功 ...

  8. .NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.案例结构总览 这里,假设我们有两个客户端(一个Web网站,一个移动App),他们要使用系统,需要通过API网关(这里API网关始终作为 ...

  9. .NET Core微服务之基于Exceptionless实现分布式日志记录

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.Exceptionless极简介绍 Exceptionless 是一个开源的实时的日志收集框架,它可以应用在基于 ASP.NET,AS ...

随机推荐

  1. [python] File path and system path

    1. get files in the current directory with the assum that the directory is like this: a .py |----dat ...

  2. 利用sphinx为python项目生成API文档

    sphinx可以根据python的注释生成可以查找的api文档,简单记录了下步骤 1:安装 pip install -U Sphinx 2:在需要生成文档的.py文件目录下执行sphinx-apido ...

  3. 《SQL Server企业级平台管理实践》读书笔记——SQL Server数据库文件分配方式

    1.文件分配方式以及文件空间检查方法 最常用的检查数据文件和表大小的命令就是:sp_spaceused 此命令有三个缺陷:1.无法直观的看出每个数据文件和日志文件的使用情况.2.这个存储过程依赖SQL ...

  4. jdk RSA算法类使用

    package com.security.rsa; import java.security.Key;import java.security.KeyFactory;import java.secur ...

  5. C#虚方法和覆写方法

  6. iOS webview加载html自定义选项框选词

    项目要求:webview加载html网址,内容为英文文本,需要获取文本上的单词 这个是最终效果图: 思路是先实现自定义的选项框(不带系统选项)再获取到滑选的单词: 实现的步骤: 首先是替换掉系统长按出 ...

  7. hdu 4607 树的直径

    思路:利用dfs遍历整棵树,找出最长子树与次长子树,两者的和最大就是直径. 若k值小于直径就输出k-1,否则输出(k-d-1)*2+d; #include<iostream> #inclu ...

  8. flask-login使用笔记

    看外国文献的中文翻译  翻译的程度有的让人会疯,翻译最好的状态是异译 直译会显的很生硬 看起来确实难过:所以在看的时候,建议都看外国文献吧,或者自己用谷歌翻译,感觉比一些翻译的博客准多了: 在使用fl ...

  9. 我的Spring Boot学习记录(一):自动配置的大致调用过程

    1. 背景 Spring Boot通过包管理工具引入starter包就可以轻松使用,省去了配置的繁琐工作,这里简要的通过个人的理解说下Spring Boot启动过程中如何去自动加载配置. 本文中使用的 ...

  10. WPF中的逻辑树和可视化树

    WPF中的逻辑树是指XAML元素级别的嵌套关系,逻辑树中的节点对应着XAML中的元素. 为了方便地自定义控件模板,WPF在逻辑树的基础上进一步细化,形成了一个“可视化树(Visual Tree)”,树 ...