使用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协议的流媒体在线视频播放,服务器性能测试脚本,模拟用户浏览器方式在线播放 ...
随机推荐
- 防御XSS攻击-encode用户输入内容的重要性
一.开场先科普下XSS 跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS.恶 ...
- wepack+sass+vue 入门教程(一)
一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...
- 从源码看Azkaban作业流下发过程
上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...
- 【前端性能】高性能滚动 scroll 及页面渲染优化
最近在研究页面渲染及web动画的性能问题,以及拜读<CSS SECRET>(CSS揭秘)这本大作. 本文主要想谈谈页面优化之滚动优化. 主要内容包括了为何需要优化滚动事件,滚动与页面渲染的 ...
- SQL数据库之DQL
初来乍到,我是一个Java行业的小学生,刚学半年. 今天老师讲了数据库的操作语句,在这里与大家分享一下我学到的知识吧,要是有不足的地方麻烦大家指出来,共同进步,共同提高! 1.数据库中的各种符号 %: ...
- 客户端的验证插件validator
简单,智能,令人愉悦的表单验证~~~ 官方文档:http://www.niceue.com/validator/ <!DOCTYPE html> <html> <head ...
- 【Java每日一题】20170103
20161230问题解析请点击今日问题下方的"[Java每日一题]20170103"查看(问题解析在公众号首发,公众号ID:weknow619) package Jan2017; ...
- JavaScript将字符串中的每一个单词的第一个字母变为大写其余均为小写
要求: 确保字符串的每个单词首字母都大写,其余部分小写. 这里我自己写了两种方法,或者说是一种方法,另一个是该方法的变种. 第一种: function titleCase(str) { var new ...
- CSS3新特性应用之结构与布局
一.自适应内部元素 利用width的新特性min-content实现 width新特性值介绍: fill-available,自动填充盒子模型中剩余的宽度,包含margin.padding.borde ...
- ionic第一坑——ion-slide-box坑(ion-slide分两页的坑)
ionic.views.Slider = ionic.views.View.inherit({ initialize: function (options) { . . . function setu ...