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开发基于消息传递的分布式应用的更多相关文章

  1. Masstransit开发基于消息传递的分布式应用

    使用Masstransit开发基于消息传递的分布式应用 Masstransit作为.Net平台下的一款优秀的开源产品却没有得到应有的关注,这段时间有机会阅读了Masstransit的源码,我觉得我有必 ...

  2. [Intel Edison开发板] 05、Edison开发基于MRAA实现IO控制,特别是UART通信

    一.前言 下面是本系列文章的前几篇: [Intel Edison开发板] 01.Edison开发板性能简述 [Intel Edison开发板] 02.Edison开发板入门 [Intel Edison ...

  3. {VS2010C#}{WinForm}{ActiveX}VS2010C#开发基于WinForm的ActiveX控件

    在VS2010中使用C#开发基于WinForm的ActiveX控件 常见的一些ActiveX大部分是使用VB.Delphi.C++开发,使用C#开发ActiveX要解决下面三个问题: 使.NET组件可 ...

  4. Form_Form Builder开发基于视图页面和自动代码生成包(案例)

     2014-01-06 Created By BaoXinjian

  5. 转】Mahout分步式程序开发 基于物品的协同过滤ItemCF

    原博文出自于: http://blog.fens.me/hadoop-mahout-mapreduce-itemcf/ 感谢! Posted: Oct 14, 2013 Tags: Hadoopite ...

  6. 用c++开发基于tcp协议的文件上传功能

    用c++开发基于tcp协议的文件上传功能 2005我正在一家游戏公司做程序员,当时一直在看<Windows网络编程> 这本书,把里面提到的每种IO模型都试了一次,强烈推荐学习网络编程的同学 ...

  7. 如何开发基于Dubbo RPC的分布式服务?

    什么是Dubbo? Dubbo能做什么? 在Crystal框架下,如何开发基于Dubbo RPC的服务? 在Crystal框架下,如何调用Dubbo RPC服务? 相关的文章 什么是Dubbo? Du ...

  8. 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)

    目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构   前言 学习Python也有一个半月时间了,学到现在感觉 ...

  9. Loadrunner脚本开发-基于HTTP协议的流媒体视频在线播放服务器性能测试

    脚本开发-基于HTTP协议的流媒体视频在线播放服务器性能测试 by:授客 QQ:1033553122   目的 实现基于http协议的流媒体在线视频播放,服务器性能测试脚本,模拟用户浏览器方式在线播放 ...

随机推荐

  1. .NET基础拾遗(5)多线程开发基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  2. setAttribute()

    ●节点分为不同的类型:元素节点.属性节点和文本节点等.   ●getElementById()方法将返回一个对象,该对象对应着文档里的一个特定的元素节点.   ●getElementsByTagNam ...

  3. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  4. jQuery学习之路(1)-选择器

    ▓▓▓▓▓▓ 大致介绍 终于开始了我的jQuery学习之路!感觉不能再拖了,要边学习原生JavaScript边学习jQuery jQuery是什么? jQuery是一个快速.简洁的JavaScript ...

  5. 从netty-example分析Netty组件

    分析netty从源码开始 准备工作: 1.下载源代码:https://github.com/netty/netty.git 我下载的版本为4.1 2. eclipse导入maven工程. netty提 ...

  6. DataTable 转换成 Json的3种方法

    在web开发中,我们可能会有这样的需求,为了便于前台的JS的处理,我们需要将查询出的数据源格式比如:List<T>.DataTable转换为Json格式.特别在使用Extjs框架的时候,A ...

  7. 在Asp.Net中操作PDF – iTextSharp - 使用表格

    使用Asp.Net生成PDF最常用的元素应该是表格,表格可以帮助比如订单或者发票类型的文档更加格式化和美观.本篇文章并不会深入探讨表格,仅仅是提供一个使用iTextSharp生成表格的方法介绍 使用i ...

  8. [原] KVM 虚拟化原理探究(2)— QEMU启动过程

    KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...

  9. the Zen of Python---转载版

    摘自译文学习区 http://article.yeeyan.org/view/legendsland/154430 The Zen of Python Python 之禅 Beautiful is b ...

  10. php cryptr 加密函数

    class CryptHelper { /** * 加密 * @param unknown $password * @param unknown $salt * @return string */ p ...