使用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. How Many Tables(POJ 1213 求连通分量)

    How Many Tables Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)T ...

  2. Java 学习 第二篇;面向对象 定义类的简单语法:

    1:基本知识 [public / protected / private] class 类名 { 零个到多个构造器定义; 零个到多个属性; 零个到多个方法; } 其中类中各个成员之间的顺序没有关系,且 ...

  3. keil在WIN7下的破解

    win7好看的界面和不错的性能,被越来越多的人所接受并使用.对于学电子的人来说,往往要用到专业方面的软件如Keil.下面以Keil C51 V9.00 即最新版本uVision 4在win7下的破解为 ...

  4. SWFUpload批量上传插件

    SWFUpload是一个批量上传插件,在HTML4.1里面,估计也只有Flash+javascript配合才能够做到了.先复制个重要的网址,这个应该是官方的文档了,相当齐全. http://leeon ...

  5. WPF笔记(1.3 属性元素)——Hello,WPF!

    原文:WPF笔记(1.3 属性元素)--Hello,WPF! 这一节中“属性元素”的概念可以用匪夷所思形容.1.WPF用标签元素实现对象建模,有两种:Control和Container,都用来装载内容 ...

  6. jQuery中的选择器《思维导图》

    学习jQuery的课程中,我对jQuery中的选择器有了更深的认识,它的简洁写法,完美的兼容性,可靠的处理机制,都让我们省了很多事, 下面是我在学习过程中对jQuery选择器写的思维导图(全屏查看:& ...

  7. 【剑指offer】面试题25:二叉树中和为某一值的路径

    题目: 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径. 思路: dfs一下就可以了.一般dfs肯定递归写比 ...

  8. c语言中内存对齐问题

    在最近的项目中,我们涉及到了“内存对齐”技术.对于大部分程序员来说,“内存对齐”对他们来说都应该是“透明的”.“内存对齐”应该是编译器的“管辖范围”.编译器为程序中的每个“数据单元”安排在适当的位置上 ...

  9. Ajax下载文件(页面无刷新)

    说明:Ajax是无法实现文件传输的,本文只是模拟了Ajax不刷新页面就可以请求并返回数据的效果.实质上还是通过提交form表单来返回文件流的输出. 分步实现逻辑: ajax请求服务器,访问数据库,根据 ...

  10. Can you find it?(二分 二分+STL set map)

    Can you find it? Time Limit : 10000/3000ms (Java/Other)   Memory Limit : 32768/10000K (Java/Other) T ...