Masstransit开发基于消息传递的分布式应用
使用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就可以看出来:
1
2
3
4
5
6
7
8
|
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#作为一种强类型的语言,我们可以通过设计消息的继承层次来实现消息的路由机制。比如我们可以设计下面的消息继承体系:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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类型:
1
2
3
4
5
6
7
|
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类型:
1
2
3
4
5
6
7
|
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服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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调用:
1
2
3
|
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、配置方式
定义一个抽象类,用来统一配置方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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:
1
2
3
4
5
6
7
8
9
10
|
public class ServiceBusInstaller:IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register( Component.For<IBus, IBusControl>() .Instance(UserManagementBusConfiguration.BusInstance) .LifestyleSingleton()); } } |
然后我们就可以在controller中注入IBus了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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:
1
2
3
4
5
6
7
8
|
public class ConsumersInstaller:IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register( Classes.FromThisAssembly().BasedOn( typeof (IConsumer)).WithServiceBase().WithServiceSelf().LifestyleTransient()); } } |
在Consumer中注入一个组件试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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、重试配置
1
|
cfg.UseRetry(Retry.Interval(3, TimeSpan.FromMinutes(1))); |
消息消费失败后重试3次,每次间隔1分钟
4、熔断机制
1
|
cfg.UseRateLimit(1000, TimeSpan.FromSeconds(1)); |
每分钟消息消费数限定在1000之内
5、异常处理(待续)
6、单元测试(待续)
7、消息定时发送(待续)
8、自定义中间件(待续)
9、自定义观察者(待续)
10、长生命周期的消费者:Turnout(待续)
11、长生命周期的状态机:saga(待续)
12、Routing slip pattern的实现:Courier(待续)
整个Demo代码提供下载:http://git.oschina.net/richieyangs/RabbitMQ.Practice
Masstransit开发基于消息传递的分布式应用的更多相关文章
- 使用Masstransit开发基于消息传递的分布式应用
Masstransit作为.Net平台下的一款优秀的开源产品却没有得到应有的关注,这段时间有机会阅读了Masstransit的源码,我觉得我有必要普及一下这个框架的使用. 值得一提的是Masstran ...
- [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协议的流媒体在线视频播放,服务器性能测试脚本,模拟用户浏览器方式在线播放 ...
随机推荐
- Zend Studio 文件头和方法注释设置
在zend studio中选择窗口->首选项->PHP–>编辑器 –>模板 –>新建 然后添加 funinfo或fileinfo 模板代码根据下边定义的COPY过去就可以 ...
- MySQL--连接属性
The capability flags are used by the client and server to indicate which features they support and w ...
- 禁用物料不允许BOM
应用 Oracle Bill Of Materiel 层 Level Function 函数名 Funcgtion Name BOM_BOMFDBOM 表单名 Form Name BOMFDBOM ...
- TEA加密算法的C/C++实现
TEA(Tiny Encryption Algorithm) 是一种简单高效的加密算法,以加密解密速度快,实现简单著称.算法真的很简单,TEA算法每一次可以操作64-bit(8-byte),采用128 ...
- java设计模式--行为型模式--备忘录模式
备忘录模式,我们平常所做的备忘录么.还得深深研究哦. 备忘录模式: 备忘录模式 概述 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状 ...
- 安装GeoIP数据库
1.安装GeoIP数据库 cd /usr/local/logstash/etc curl -O "http://geolite.maxmind.com/download/geoip/data ...
- 安装开源项目 MultiType (基于 RecyclerView)出现的各种问题 -- 自己的第一篇博客
一.引入开源项目的方式 使用开源项目 MultiType 的两种方式: 1.maven引入:在主Module 的 build.gradle 中加入 dependencies { ...... comp ...
- UESTC_温泉旅店 CDOJ 878
天空飘下一朵一朵的雪花,这是一片纯白的世界. 在天空之下的温泉旅店里,雪菜已醉倒在一旁,冬马与春希看了看说着梦话的雪菜,决定找一点玩的来度过这愉快的晚上. 这家旅店提供一种特色游戏,游戏有n张牌,各写 ...
- 【转】android 电池(二):android关机充电流程、充电画面显示
关键词:android 电池关机充电 androidboot.mode charger关机充电 充电画面显示 平台信息:内核:linux2.6/linux3.0系统:android/android4. ...
- MySQL Workbench导出数据库
步骤: 1. 打开mysql workbench,进入需要导出的数据库,点击左侧栏的[Management]tab键. 2. 点选要输出的数据库 点击[Data Export] 选在要输出的数据库 选 ...