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

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

    Masstransit作为.Net平台下的一款优秀的开源产品却没有得到应有的关注,这段时间有机会阅读了Masstransit的源码,我觉得我有必要普及一下这个框架的使用. 值得一提的是Masstran ...

  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. 基于cygwin构建u-boot(一)环境搭建

    从本文开始,更系统的描述基于cygwin构建u-boot. 之前<痛苦的版本对齐>系列,对于cygwin环境下Sourcery CodeBench,u-boot-1.1.6的构建有侧面的说 ...

  2. 伪静态(URL重写)

    伪静态在可以使用数据库提供更强大的功能的同时,将很长很复杂的链接变成简短的静态链接形式,迎合搜索引擎方便搜索引擎蜘蛛(Spider)来抓取网页上的相关内容,提高页面被搜索引擎索引收录的比率,为用户提供 ...

  3. Uubntu scrot 的简单使用

    scrot 是一个使用 imlib2 库截取屏幕和保存图像的的工具. 1:安装 #apt-get install scrot 2:查看帮助 #scrot -help -v, --version显示版本 ...

  4. C# dynamic类型

    dynamic类型是C#4.0中引入的新类型,它允许其操作掠过编译器类型检查,而在运行时处理.dynami类型在绝大多数情况下和object类型相似,不同之处在于编译器对于包含了dynamic的表达式 ...

  5. 2014第3周六升级win8.1

    今天上班主要是沟通了一个需求,然后思考下实现方案并记录下要点,晚上没有加班就回来,把操作系统升级到了win8.1,升级的重要原因是,原来的win7时常会卡顿,并且开关机慢,还有上面装了很多无用的影响效 ...

  6. 简单了解下Dubbo

    1. Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需 ...

  7. man/ls/clock/date/echo笔记

    login:    用户名:用户ID    认证机制:Authentication授权:Authorization审计:Audition (日志) prompt,命令提示符:命令:magic numb ...

  8. poj 2395 Out of Hay(最小生成树,水)

    Description The cows have run <= N <= ,) farms (numbered ..N); Bessie starts at Farm . She'll ...

  9. Eclipse选中变量名,相同变量都变色显示

    Eclipse选中变量名,相同变量都变色显示 java文件的设置"Window"-"preferences"-"Java"-"Ed ...

  10. 用数据说话,外贸B2C产品选择(上篇)-热门搜索法

    当选择了外贸这条路,那就是选择了跟外国人做生意.那面对全球这么大的市场究竟选什么样的产品才干脱颖而出?什么样的产品才是全球卖家喜欢的呢?什么样的产品才干让自己財源滚滚?我想这都是全部刚開始外贸创业的人 ...