作为SOA基础设施,企业服务总线(ESB)是一个具有高分布性、事件驱动服务的SOA架构,是当前企业集成的主流框架。
网站: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,可以使用在任何环境中。
MassTransit在消息队列(MQ)之上构建了消息总线机制,封装了对消息队列的操作,以及其它的组件,比如序列化、日志、Saga、持久化等。
下图为传入消息处理管道模型:
MassTransit的目标是作为消息机制的抽象框架,因此,它本身并不具体实现MQ,而是通过集成其它MQ产品来作为其通信层。目前官方已集成的MQ产品有MSMQ、RabbitMQ。其它非官方补充了ActiveMQ。
MassTransit在MQ之上添加了Sagas、多线程、异常处理、事务、序列化、消息头(Header)、消息使用者生命周期管理、路由、Rx(Reactive Extension 反应式扩展)集成、NHibernate集成、调试、跟踪、日志输出、加密、定时服务等。
MassTransit本身使用了许多优秀的设计,比如对MSMQ、RabbitMQ的使用,通过在Bus构造配置中调用UseMSMQ()或UseRabbitMQ()来声明式的决定。
通过分析源码,可以发现UseXXX函数是通过定义在MassTransit.Transports.MSMQ.dll和MassTransit.Transports.RabbitMq.dll中的扩展方法实现的,这样当用户引用相应的DLL时,方法调用才会开放给用户。即用户选择具体使用哪种Transport时,是通过引用相应的DLL来决定的。DLL引用入项目后,具体MQ产品特定的配置,函数等方法也附加到了基类对象中。这样可以避免在基类中定义大量子类特定接口
MassTransit所有扩展组件均是采用这种方法,比如日志(Logging)组件。
首先需要定义消息:
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函数中完成初始化和发布,可以在随后的输出中看到消息文本。
参考: 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消息。通过这种方式,所有的更新将传播到应用中所有其它节点中。
参考: 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方法完成处理。
参考: http://docs.masstransit-project.com/en/latest/overview/saga.html
一个Saga是指由协调器(coordinator)管理的一个长事务。Sagas通常由一个事件(event)启动,Sagas对事件进行编排,并维护所有事务的状态。
在MT中有两种方法定义Saga:一种是用接口和类直接定义初始化、协调交互的各种消息,或可以被Saga示例观察到的各种消息;另一种是通过在类里面定义事件、状态、行为来定义一个状态机来实现。
要通过状态机实现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; }
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(); }); }); } }