MassTransit 探究初步

1.  背景

作为SOA基础设施,企业服务总线(ESB)是一个具有高分布性、事件驱动服务的SOA架构,是当前企业集成的主流框架。

2.  简介

网站:http://masstransit-project.com/

 

MassTransit (MT) is a framework forcreating distributed applications on the .Net platform. MT provides the abilityto subscribe to messages by type and then connect different processing nodesthough message subscriptions building a cohesive mesh of services.

 

主要特性:

l  Bus architecture

l  Sagas

l  Exception management

l  Transactions

l  Serialization

l  Headers

l  Consumer lifecycle

l  Built on top of Rabbit Mq

l  IOC support

 

授权:

基于Apache 2.0,可以使用在任何环境中。

3.  架构

MassTransit在消息队列(MQ)之上构建了消息总线机制,封装了对消息队列的操作,以及其它的组件,比如序列化、日志、Saga、持久化等。

 

下图为传入消息处理管道模型:

 

4.  整体分析

MassTransit的目标是作为消息机制的抽象框架,因此,它本身并不具体实现MQ,而是通过集成其它MQ产品来作为其通信层。目前官方已集成的MQ产品有MSMQ、RabbitMQ。其它非官方补充了ActiveMQ。

MassTransit在MQ之上添加了Sagas、多线程、异常处理、事务、序列化、消息头(Header)、消息使用者生命周期管理、路由、Rx(Reactive Extension 反应式扩展)集成、NHibernate集成、调试、跟踪、日志输出、加密、定时服务等。

5.  特性分析

5.1.  声明式配置

MassTransit本身使用了许多优秀的设计,比如对MSMQ、RabbitMQ的使用,通过在Bus构造配置中调用UseMSMQ()或UseRabbitMQ()来声明式的决定。

5.2.  通过扩展方法隐藏具体实现

通过分析源码,可以发现UseXXX函数是通过定义在MassTransit.Transports.MSMQ.dll和MassTransit.Transports.RabbitMq.dll中的扩展方法实现的,这样当用户引用相应的DLL时,方法调用才会开放给用户。即用户选择具体使用哪种Transport时,是通过引用相应的DLL来决定的。DLL引用入项目后,具体MQ产品特定的配置,函数等方法也附加到了基类对象中。这样可以避免在基类中定义大量子类特定接口

MassTransit所有扩展组件均是采用这种方法,比如日志(Logging)组件。

6.  入门

首先需要定义消息:

public class YourMessage { public string Text { get; set; } }

消息总线的创建可以通过Bus类静态方法Initialize进行,传入相应的配置方法,如下所示:

 

Bus.Initialize(sbc =>
{
    sbc.UseMsmq();
    sbc.VerifyMsmqConfiguration();
    sbc.UseMulticastSubscriptionClient();
    sbc.ReceiveFrom("msmq://localhost/test");
    sbc.Subscribe(subs =>
    {
        subs.Handler<YourMessage>(msg => Console.WriteLine("From bus 1: " + msg.Text));
    });
});

初始化好Bus之后,就可以调用Publish方法发布消息了:

Bus.Instance.Publish(new YourMessage { Text = "Hi" });

在初始化中,通过sbc.Subscribe函数注册了一个Handler,其作用是在控制台中输出消息文本。

创建一个控制台应用程序,在main函数中完成初始化和发布,可以在随后的输出中看到消息文本。

7.  高级功能使用

7.1.  Subscription Service

参考 http://docs.masstransit-project.com/en/latest/overview/subscriptions.html

 

如果使用的消息队列不提供订阅共享功能,比如MSMQ,可以使用MT的SubscriptionService来实现此功能。在这种情况下,订阅信息的协调功能由一个中心管理器来完成。这个中心管理器就是运行在网路中的SubscriptionService的一个实例。每个消息总线(Bus)实例通过SubscriptionClient与其进行通信并交换订阅信息。

 

示例

 

创建Subscription Service:

var subscriptionBus = ServiceBusFactory.New(sbc =>
{
    sbc.UseStomp();
    sbc.SetConcurrentConsumerLimit(1);

    sbc.ReceiveFrom("stomp://localhost/mt_subscriptions");
});
var subscriptionSagas = new InMemorySagaRepository
  
  
   
   ();
var subscriptionClientSagas = new InMemorySagaRepository
   
   
    
    ();
var subscriptionService = new SubscriptionService(subscriptionBus, subscriptionSagas, subscriptionClientSagas);

   
   
  
  

创建Time Out Service:

var timeoutBus = ServiceBusFactory.New(sbc =>
{
    sbc.UseStomp();
    sbc.UseControlBus();

    sbc.ReceiveFrom("stomp://localhost/mt_timeouts");
    sbc.UseSubscriptionService("stomp://localhost/mt_subscriptions");
});

var timeoutService = new TimeoutService(timeoutBus, new InMemorySagaRepository<TimeoutSaga>());
timeoutService.Start();

创建应用Bus:

var bus = ServiceBusFactory.New(sbc
{
    sbc.UseStomp();
    sbc.UseControlBus();

    sbc.ReceiveFrom("stomp://localhost/your_awesome_application");
    sbc.UseSubscriptionService("stomp://localhost/mt_subscriptions");
});

说明:

当调用UseSubscriptionService时,隐式附加了一个SubscriptionClient到bus中。首先,SubscriptionClient会发送一个AddSubscriptionClient消息给SubscriptionService队列,然后开始监听订阅变更,随后发送AddScription/RemoveSubscription消息。通过这种方式,所有的更新将传播到应用中所有其它节点中。

 

7.2.  Request/Response

参考 http://docs.masstransit-project.com/en/latest/overview/request.html

 

通常使用Message Bus是一个应用发送一个请求(Request),另一个应用接收到这个请求后做出一些回应(Response)。比如工资进程请求税务进程执行某种所得税,在计算完成后,返回结果。

 

示例

 

消息定义:

请求和响应通过CorrelationId来进行关联。

public class BasicRequest : CorrelatedBy<Guid>
{
    public Guid CorrelationId { get;set; }
    public string Text { get; set; }
}
public class BasicResponse : CorrelatedBy<Guid>
{
    public Guid CorrelationId { get; set; }
    public string Text { get; set; }
}

响应者:

简单的在请求信息上添加一个RESP前缀作为响应。注意BasicResponse.CorrelationId并没有设置,这个是由MT自动设置的。

public class Program
{
    public static void Main()
    {
        Bus.Initialize(sbc =>
        {
            sbc.UseMsmq();
            sbc.VerifyMsmqConfiguration();
            sbc.UseMulticastSubscriptionClient();
            sbc.ReceiveFrom("msmq://localhost/message_responder");
            sbc.Subscribe(subs=>
            {
                subs.Handler<BasicRequest>( (cxt, msg )=>
                {
                    cxt.Respond(new BasicResponse{Text = "RESP"+msg.Text});
                });
            });
        });
    }
}


请求者:

提交请求并处理与原始请求相关联(通过CorrelationId)的响应,处理后取消对响应的订阅并结束这次请求。

public class Program
{
    public static void Main()
    {
        Bus.Initialize(sbc =>
        {
            sbc.UseMsmq();
            sbc.VerifyMsmqConfiguration();
            sbc.UseMulticastSubscriptionClient();
            sbc.ReceiveFrom("msmq://localhost/message_requestor");
        });

        Bus.Instance.PublishRequest(new BasicRequest(), x =>
        {
            x.Handle<BasicResponse>(message => Console.WriteLine(message.Text));
            x.SetTimeout(30.Seconds());
        });
    }
}

上述代码会阻塞调用线程直到响应返回或超时。超时后会抛出RequestTimeoutException。如果响应处理发生异常,该异常会重新抛出给调用请求发出线程。

如果想使用异步处理,可以调用BeginPublishRequest,在处理结束后,调用EndRequest方法完成处理。

 

 

7.3.  Sagas

参考 http://docs.masstransit-project.com/en/latest/overview/saga.html

 

一个Saga是指由协调器(coordinator)管理的一个长事务。Sagas通常由一个事件(event)启动,Sagas对事件进行编排,并维护所有事务的状态。

 

7.3.1.  定义Sagas

在MT中有两种方法定义Saga:一种是用接口和类直接定义初始化、协调交互的各种消息,或可以被Saga示例观察到的各种消息;另一种是通过在类里面定义事件、状态、行为来定义一个状态机来实现。

 

7.3.2.  通过状态机实现Sagas

要通过状态机实现Sagas,必须从SagaStateMachine类继承:

public class AuctionSaga :
    SagaStateMachine<AuctionSaga>,
    ISaga
{
    static CombineSaga()
    {
        Define(() =>
        {
            // the state machine behavior is defined here
        });
    }
    public Guid CorrelationId { get; set; }
    public IServiceBus Bus { get; set; }
}

然后定义各种状态:

public static State Initial { get; set; }
public static State Completed { get; set; }
public static State Open { get; set; }
public static State Closed { get; set; }

定义与状态关联的事件:

public static Event<CreateAuction> Create { get; set; }
public static Event<PlaceBid> Bid { get; set; }

定义消息:

public interface CreateAuction :
    CorrelatedBy<Guid>
{
    string Title { get; }
    string OwnerEmail { get; }
    decimal OpeningBid { get; }
}


public interface PlaceBid
{
    Guid BidId { get; }
    Guid AuctionId { get; }
    decimal MaximumBid { get; }
    string BidderEmail { get; }
}

定义状态转换:

static AuctionSaga()
{
    Define(() =>
    {
        Initially(
            When(Create));
        During(Open,
            When(Bid));
    });
}

定义事件发生时的行为:

static AuctionSaga()
{
    Define(() =>
    {
        Initially(
            When(Create)
                .Then((saga,message) =>
                {
                    saga.OpeningBid = message.OpeningBid;
                    saga.OwnerEmail = message.OwnerEmail;
                    saga.Title = message.Title;
                })
                .TransitionTo(Open));
    });
}
//
public decimal OpeningBid { get; set; }
public string OwnerEmail { get; set; }
public string Title { get; set; }

完整的定义:

static SupervisorSaga()
{
    Define(() =>
    {
        Initially(
            When(Create)
                .Then((saga,message) =>
                {
                    saga.PostalCode = message.PostalCode;
                })
                .Publish((saga,message) => new RequestPostalCodeDetails(saga.PostalCode))
                .Publish((saga,message) => new RequestGeolocation(saga.PostalCode))
                .TransitionTo(Waiting));

        During(Waiting,
            When(PostalCodeDetailsReceived)
                .Then((saga,message) =>
                {
                    saga.City = message.City;
                    saga.State = message.State;
                }),
            When(GeolocationReceived)
                .Then((saga,message) =>
                {
                    saga.Latitude = message.Latitude;
                    saga.Longitude = message.Longitude;
                }));

        Combine(PostalCodeDetailsReceived, GeolocationReceived)
            .Into(ReadyToProceed, saga => saga.ReadyFlags);

        During(Waiting,
            When(ReadyToProceed)
                .Then((saga,message) =>
                {
                    saga.Bus.Publish(new PostalCodeDetails(...));
                })
                .Complete());
    });
}
//
public int ReadyFlags { get; set; }
public static Event<CreatePostalCodeDetailsRequest> Create { get; set; }
public static Event<PostalCodeDetailsResponse> PostalCodeDetailsReceived { get; set; }
public static Event<GeolocationResponse> GeolocationReceived { get; set; }
public static Event ReadyToProceed { get; set; }

当Sagas定义好后,就可以在Bus中进行订阅:

public class Program
{
    public static void Main()
    {
        Bus.Initialize(sbc =>
        {
            sbc.ReceiveFrom("loopback://localhost/my_saga_bus");
            sbc.Subscribe(subs =>
            {
                subs.Saga<AuctionSaga>(new InMemorySagaRepository<AuctionSaga>())
                    .Permanent();
            });
        });
    }
}


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值