使用Masstransit开发基于消息传递的分布式应用
Masstransit作为.Net平台下的一款优秀的开源产品却没有得到应有的关注,这段时间有机会阅读了Masstransit的源码,我觉得我有必要普及一下这个框架的使用。
值得一提的是Masstransit的源码写的非常优秀,值得每个想提高自己编程能力的.Net选手阅读,整个代码看起来赏心悦目。反之,每次打开自己公司项目的时候心情都异常沉重。所以不是.Net不行,还是咱们水平不行。
学会了Masstransit你再也不用羡慕别人有Dubbo、Mule、Akka什么的了,当然在某些方面他们的使用场景还是有一些区别。另外插播一条广告:本人目前在西安求职中,如果那位同学有好的工作机会希望能够帮忙推荐。
阅读本篇文章的前提是你需要对消息队列有一些了解,特别是RabbitMq,Masstransit作为一款轻量级的ESB默认支持RabbitMq和MSMQ。本文的例子都使用RabbitMq来介绍,所以你最好能读一下我之前写的《如何优雅的使用RabbitMq》。
简单来说,Masstransit提供了使用消息队列场景的一种抽象,也就是说,如果你有使用消息队列的需求,都可以通过Masstransit来完成,当然如果仅仅是拿消息队列来发个短信、邮件之类的并不能体现出Masstransit的优越性。当整个业务系统都通过Masstransit过来构建和交互的时候,才能真正体现ESB的价值所在。
我写了5不同场景个Demo,方便大家学习和参考。我会重点讲解Real World的案例,也就是如何在真实场景使用Masstransit。如果仅仅是把一些组件融入到了项目中并且能够运行,并不能算是一个合格的架构师,一个合格的架构师一定是可以将某个组件以最佳实践的方式融入到了自己的项目中,并且能够为开发者提供清晰且合理的抽象,然后针对这一方案制定一些约定和规则,随着项目的推进,整个项目的代码都能够有章可循,始终在架构师的掌控之中。
一、发送命令模型(Send Command Pattern)
这种模型最常见的就是CQRS中C,用来向DomainHandler发送一个Command。另外系统的发送邮件服务、发送短信服务也可以通过这种模式来实现。这种模型跟邮递员向邮箱投递邮件有点相似。这一模型的特点是你需要知道对方终结点的地址,意味着你要明确要向哪个地址发送消息。从Masstransit提供的api就可以看出来:
- var endPoint =await bus.GetSendEndpoint(sendToUri);
- var command = new GreetingCommandA()
- {
- Id = Guid.NewGuid(),
- DateTime = DateTime.Now
- };
- await endPoint.Send(command);
这个Demo主要由2个工程组成,Client发送消息到Server,Server来响应这一消息。
二、发布/订阅模型(publish/subscribe pattern)
之所以有基于消息传递的分布式应用这种架构模式,很大程度上就是依靠这种模式来完成。一个典型的例子是子系统A发布了一条消息,子系统B和子系统C都可以订阅这一消息并异步处理该消息。而这一过程对子系统A来说是不关心的。从而减少不同的子系统之间的耦合,提高系统的可扩展性。
三、消息的继承层次
用过RabbitMQ的同学应该知道,RabbitMQ提供了3中类型的Exchange,分别为direct、fanout和topic。所有这一切都是为了提供一种路由消息的机制。而这一切是通过匹配一种字符串类型的routingKey来实现的,当然有了Masstransit你就不用这么费劲了。C#作为一种强类型的语言,我们可以通过设计消息的继承层次来实现消息的路由机制。比如我们可以设计下面的消息继承体系:
- public interface IMessage
- {
- Guid Id { get; set; }
- }
- public class Message : IMessage
- {
- public Guid Id { get; set; }
- public string Type { get; set; }
- }
- public class UserUpdatedMessage : Message
- {
- public Guid Id { get; set; }
- }
有了这样的继承体系,我们可以定义下面的Consumer类型:
- public class BaseInterfaceMessageConsumer:IConsumer<IMessage>
- {
- public async Task Consume(ConsumeContext<IMessage> context)
- {
- await Console.Out.WriteLineAsync($"consumer is BaseInterfaceMessageConsumer,message type is {context.Message.GetType()}");
- }
- }
还可以定义下面的Consumer类型:
- public class UserUpdatedMessageConsumer: IConsumer<UserUpdatedMessage>
- {
- public async Task Consume(ConsumeContext<UserUpdatedMessage> context)
- {
- await Console.Out.WriteLineAsync($"consumer is UserUpdatedMessageConsumer,message type is {context.Message.GetType()}");
- }
- }
这样就可以路由不同的消息到相应的Consumer中了。
四、使用Topshelf来构建windows服务
我们最终要将consumer程序集打成windows服务来安装在产品环境下,Topshelf为我们提供了一组DSL描述的api来创建window服务:
- HostFactory.Run(x =>
- {
- x.Service<GreetingServer>(s =>
- {
- s.ConstructUsing(name => new GreetingServer());
- s.WhenStarted(tc => tc.Start());
- s.WhenStopped(tc => tc.Stop());
- });
- x.StartAutomatically();
- x.RunAsLocalSystem();
- x.SetDescription("A greeting service");
- x.SetDisplayName("Greeting Service");
- x.SetServiceName("GreetingService");
- });
五、RPC调用(request/response pattern)
我们还可以通过Masstransit实现RPC调用:
- var response = await client.Request(new SimpleRequest() {CustomerId = customerId});
- Console.WriteLine("Customer Name: {0}", response.CusomerName);
这有点像是一个webservice调用,不过在ESB的设计中我们应该尽量避免这种设计,特别是在异构系统之间,应该尽量采用send command pattern和publish/subscriber pattern。
六、正式场景该如何使用Masstransit
在使用Masstranit的正式场景中,我们主要考虑以下几个方面:
1、配置方式
定义一个抽象类,用来统一配置方式:
- public abstract class BusConfiguration
- {
- public abstract string RabbitMqAddress { get; }
- public abstract string QueueName { get; }
- public abstract string RabbitMqUserName { get; }
- public abstract string RabbitMqPassword { get; }
- public abstract Action<IRabbitMqBusFactoryConfigurator,IRabbitMqHost> Configuration { get; }
- public virtual IBus CreateBus()
- {
- var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
- {
- var host = cfg.Host(new Uri(RabbitMqAddress), hst =>
- {
- hst.Username(RabbitMqUserName);
- hst.Password(RabbitMqPassword);
- });
- Configuration?.Invoke(cfg, host);
- });
- return bus;
- }
- }
具体的项目会继承该配置类做对应的配置:如UserManagementBusConfiguration、UserManagementServiceBusConfiguration等
2、能够跟DI容器结合,本例以Castle Windsor Container为例:
在web项目中添加ServiceBusInstaller:
- public class ServiceBusInstaller:IWindsorInstaller
- {
- public void Install(IWindsorContainer container, IConfigurationStore store)
- {
- container.Register(
- Component.For<IBus, IBusControl>()
- .Instance(UserManagementBusConfiguration.BusInstance)
- .LifestyleSingleton());
- }
- }
然后我们就可以在controller中注入IBus了:
- private readonly IUserProvider _userProvider;
- private readonly IBus _bus;
- public ValuesController(IUserProvider userProvider,IBus bus)
- {
- _userProvider = userProvider;
- _bus = bus;
- }
- [HttpGet]
- [Route("api/values/createuser")]
- public string CreateUser()
- {
- //save user in local db
- _bus.Publish(new UserCreatedEvent() {UserName = "Tom", Email = "tom@google.com"});
- return "create user named Tom";
- }
同样的道理,在consumer项目中也可以做同样的配置,添加ConsumersInstaller:
- public class ConsumersInstaller:IWindsorInstaller
- {
- public void Install(IWindsorContainer container, IConfigurationStore store)
- {
- container.Register(
- Classes.FromThisAssembly().BasedOn(typeof (IConsumer)).WithServiceBase().WithServiceSelf().LifestyleTransient());
- }
- }
在Consumer中注入一个组件试试:
- public class UserCreatedEventConsumer : IConsumer<UserCreatedEvent>
- {
- private readonly GreetingWriter _greetingWriter;
- public UserCreatedEventConsumer(GreetingWriter greetingWriter)
- {
- _greetingWriter = greetingWriter;
- }
- public async Task Consume(ConsumeContext<UserCreatedEvent> context)
- {
- _greetingWriter.SayHello();
- await Console.Out.WriteLineAsync($"user name is {context.Message.UserName}");
- await Console.Out.WriteLineAsync($"user email is {context.Message.Email}");
- }
- }
把web项目和consumer服务都跑起来看看:
3、重试配置
- cfg.UseRetry(Retry.Interval(3, TimeSpan.FromMinutes(1)));
消息消费失败后重试3次,每次间隔1分钟
4、限速器
- cfg.UseRateLimit(1000, TimeSpan.FromSeconds(1));
每分钟消息消费数限定在1000之内
5、熔断器
- cfg.UseCircuitBreaker(cb =>
- {
- cb.TrackingPeriod = TimeSpan.FromMinutes(1);
- cb.TripThreshold = 15;
- cb.ActiveThreshold = 10;
- });
参照Martin Folwer对熔断器模式的描述:CircuitBreaker
6、异常处理
- public class UserUpdatedEventComsumer
- :IConsumer<UserUpdatedEvent>
- ,IConsumer<Fault<UserUpdatedEvent>>
- {
- public Task Consume(ConsumeContext<UserUpdatedEvent> context)
- {
- throw new System.NotImplementedException();
- }
- public async Task Consume(ConsumeContext<Fault<UserUpdatedEvent>> context)
- {
- await Console.Out.WriteLineAsync($"catch exception: {context.Message.Message}");
- }
- }
只要继承于对应的Fault<TMessage>即可为对应的消息编写异常处理。
7、单元测试(待续)
8、消息定时发送(待续)
9、自定义中间件(待续)
10、自定义观察者(待续)
11、长生命周期的消费者:Turnout(待续)
12、长生命周期的状态机:saga(待续)
13、Routing slip pattern的实现:Courier(待续)
整个Demo代码提供下载:http://git.oschina.net/richieyangs/RabbitMQ.Practice
使用Masstransit开发基于消息传递的分布式应用的更多相关文章
- Masstransit开发基于消息传递的分布式应用
使用Masstransit开发基于消息传递的分布式应用 Masstransit作为.Net平台下的一款优秀的开源产品却没有得到应有的关注,这段时间有机会阅读了Masstransit的源码,我觉得我有必 ...
- [Intel Edison开发板] 05、Edison开发基于MRAA实现IO控制,特别是UART通信
一.前言 下面是本系列文章的前几篇: [Intel Edison开发板] 01.Edison开发板性能简述 [Intel Edison开发板] 02.Edison开发板入门 [Intel Edison ...
- {VS2010C#}{WinForm}{ActiveX}VS2010C#开发基于WinForm的ActiveX控件
在VS2010中使用C#开发基于WinForm的ActiveX控件 常见的一些ActiveX大部分是使用VB.Delphi.C++开发,使用C#开发ActiveX要解决下面三个问题: 使.NET组件可 ...
- Form_Form Builder开发基于视图页面和自动代码生成包(案例)
2014-01-06 Created By BaoXinjian
- 转】Mahout分步式程序开发 基于物品的协同过滤ItemCF
原博文出自于: http://blog.fens.me/hadoop-mahout-mapreduce-itemcf/ 感谢! Posted: Oct 14, 2013 Tags: Hadoopite ...
- 用c++开发基于tcp协议的文件上传功能
用c++开发基于tcp协议的文件上传功能 2005我正在一家游戏公司做程序员,当时一直在看<Windows网络编程> 这本书,把里面提到的每种IO模型都试了一次,强烈推荐学习网络编程的同学 ...
- 如何开发基于Dubbo RPC的分布式服务?
什么是Dubbo? Dubbo能做什么? 在Crystal框架下,如何开发基于Dubbo RPC的服务? 在Crystal框架下,如何调用Dubbo RPC服务? 相关的文章 什么是Dubbo? Du ...
- 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)
目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构 前言 学习Python也有一个半月时间了,学到现在感觉 ...
- Loadrunner脚本开发-基于HTTP协议的流媒体视频在线播放服务器性能测试
脚本开发-基于HTTP协议的流媒体视频在线播放服务器性能测试 by:授客 QQ:1033553122 目的 实现基于http协议的流媒体在线视频播放,服务器性能测试脚本,模拟用户浏览器方式在线播放 ...
随机推荐
- IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法
直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...
- Scrapy框架爬虫初探——中关村在线手机参数数据爬取
关于Scrapy如何安装部署的文章已经相当多了,但是网上实战的例子还不是很多,近来正好在学习该爬虫框架,就简单写了个Spider Demo来实践.作为硬件数码控,我选择了经常光顾的中关村在线的手机页面 ...
- CYQ.Data、ASP.NET Aries 百家企业使用名单
如果您或您所在的公司正在使用此框架,请联系左侧的扣扣,告知我信息,我将为您添加链接: 以下内容为已反馈的用户,(收集始于:2016-08-08),仅展示99家: 序号 企业名称 企业网址 备注 1 山 ...
- 传播正能量——做一个快乐的程序员
引子 今天在博客园看到施瓦小辛格的文章我们搞开发的为什么会感觉到累,顿时有感而发.自己本来不擅长写文章,更不擅长写这种非技术性的文章,但是在思绪喷薄之际,还是止不住有很多话要说.针对从客观上说&quo ...
- [APUE]标准IO库(上)
一.流和FILE对象 系统IO都是针对文件描述符,当打开一个文件时,即返回一个文件描述符,然后用该文件描述符来进行下面的操作,而对于标准IO库,它们的操作则是围绕流(stream)进行的. 当打开一个 ...
- Python标准模块--Unicode
1 模块简介 Python 3中最大的变化之一就是删除了Unicode类型.在Python 2中,有str类型和unicode类型,例如, Python 2.7.6 (default, Oct 26 ...
- css元素水平居中和垂直居中的方式
关于居中的问题,一直处于疑惑不解的状态,知道的几种方法好像也不是每一次都会起到作用,所以更加迷惑.主要是不清楚该 在什么情况下采用哪种解决方法,所以,整理了一些方法,梳理一下思路,做一个总结. 1. ...
- await and async
Most people have already heard about the new “async” and “await” functionality coming in Visual Stud ...
- C#反序列化XML异常:在 XML文档(0, 0)中有一个错误“缺少根元素”
Q: 在反序列化 Xml 字符串为 Xml 对象时,抛出如下异常. 即在 XML文档(0, 0)中有一个错误:缺少根元素. A: 首先看下代码: StringBuilder sb = new Stri ...
- css3制作旋转动画
现在的css3真是强大,之前很多动画都是用jq来实现,但是css3制作的动画要比jq实现起来简单很多,今天呢,我自己也写了一个css旋转动画和大家分享.效果如下面的图片 思路:1.制作之前呢,我们先来 ...