使用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. NodeJS和C++的性能比较(转)

    原文地址: http://www.web-tinker.com/article/20374.html 前段时间做了个实验,测试了1E9次的空循环在NodeJS和C++中的执行用时.于是我和小伙伴们瞬间 ...

  2. haproxy之配置文件解析

    功能--> 提供高可用/负载均衡/基于tcp和http应用的代理;支持虚拟主机,特别适用于负载特大的web站点. 配置文件解析--> #配置文件-->开启/proc/net/ipv4 ...

  3. jQuery.data的是jQuery的数据缓存系统

    jQuery.Data源码 jQuery.data的是jQuery的数据缓存系统 jQuery.data的是jQuery的数据缓存系统.它的主要作用就是为普通对象或者DOM元素添加数据. 1 内部存储 ...

  4. Unix 环境高级编程 (APUE) 之 网络 IPC:套接字

    一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字 . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级 ...

  5. mysql--help不可用

    1.问题描述: 执行help语句时没有得到相应的返回只是提示检查相关表是不是已经加载: mysql> help contents; Nothing found Please check if ' ...

  6. Discuz!X2.5论坛在IIS和Apache环境配置实现伪静态

    最近在研究自己的网站,然后把这文章分享出来,让不清楚怎么设置的童鞋参考,高手可以飘过~~~ URL 静态化是一个有利于搜索引擎的设置,通过 URL 静态化,达到原来是动态的 PHP 页面转换为静态化的 ...

  7. 类:初识类的事件(有点不明白,怎么普通Precedure可以赋值给TEvent)

    先勾画一下思路:1.建立一个类, 里面有年龄字段 FAge;2.通过 Age 属性读写 FAge;3.如果输入的年龄刚好是 100 岁, 将会激发一个事件, 这个事件我们给它命名为: OnHundre ...

  8. C# Assembly类_反射

    System.Reflection.Assembly类是一个比较常用到的类,在反射中就经常用到. 由于这个类实在有太多的字段.属性与方法.实在不想将一个个属性方法从MSDN复制出来然后逐个属性.方法敲 ...

  9. Android学习总结——SharedPreferences

    SharePreferences存储方式,只是轻量级数据存储,xml格式的数据显示方式.简单存储步骤如下:一:获取SharePreferences对象1.SharedPreferences pref ...

  10. jquery第一期:运行第一个jquery

    首先下载js文件,网址jquery.com去下载,可以下载1.10版的 首先打开editplus进行编辑,添加js文件: 编写代码: <!DOCTYPE html PUBLIC "-/ ...