作为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(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());
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
{
public Guid CorrelationId { get;set; }
public string Text { get; set; }
}
public class BasicResponse : CorrelatedBy
{
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( (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(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,
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 Create { get; set; }
public static Event Bid { get; set; }
定义消息:
public interface CreateAuction :
CorrelatedBy
{
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 Create { get; set; }
public static Event PostalCodeDetailsReceived { get; set; }
public static Event 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(new InMemorySagaRepository())
.Permanent();
});
});
}
}