应用服 务器向Web服务器发送整个清单时,UsernameInUseMessage类的多个实例会被包含在单独一条物理消息中。而Web服务器上的bus对象则 每次只会向如上的消息处理器发出一条逻辑消息。
这样, 实际验证一个用户时,Web页(如果你使用MVC,也可叫做控制器)将调用:
public class UserAuthenticationServiceAgent
{
public bool Authenticate(string username, string password)
{
byte[]existingHashedPassword = WebCache[username];
if (existingHashedPassword != null)
return existingHashedPassword == this.Hash(password);
return false;
}
}
注册新 用户时,Web服务器当然会首先检查缓存,然后发出一条包含了用户名和散列密码的消息RegisterUserMessage:
[Serializable]
[StartsWorkflow]
public class RegisterUserMessage : IMessage
{
private string username;
public string Username
{
get { return username; }
set { username = value; }
}
private string email;
public string Email
{
get { return email; }
set { email = value; }
}
private byte[] hashedPassword;
public byte[] HashedPassword
{
get { return hashedPassword; }
set { hashedPassword = value; }
}
}
消息 RegisterUserMessage到达应用服务器时,如下流程的新实例负责处理:
public class RegisterUserWorkflow :
BaseWorkflow,IMessageHandler
{
public void Handle(RegisterUserMessage message)
{
//通过message.Email发出包含了this.Id(一个guid号,是URL的组成部分)的确认请求
}
///
/// 用户点击email中的确认链接后,Web服务器发出包含了流程Id的消息UserualidatedMessage
///
public void Handle(UserValidatedMessage message)
{
// 将用户存入数据库
this.Bus.Publish(new UsernameInUseMessage(
message.Username, message.HashedPassword));
}
}
消息 UsernameInUseMessage最终将到达所有订阅了它的Web服务器。
性能/安全性的权衡
更深入考察整个流程,我们发现实际可实现为两个独立的消息处理器,并可用email地址代替流程Id。不过 ,在这个改进的替代方案中必须考虑安全问题。删除对流程Id的依赖,即表示我们可在未收到消息 RegisterUserMessage前收到UserValidatedMessage。
因为 UserValidatedMessage的处理过程消耗的资源相对较多——写入数据库,并向所有 Web服务器发布消息,恶意用户用不多的消息就可以发起拒绝服务攻击(DOS),同时也能躲开多数探测系统的眼睛。而要想依靠GUID欺骗则困难得多。不过,因为注册流程的处理实例可以缓存于内存中,这将大大降低相关数据搜索带来的资源消耗——甚至可以小到在探测系统察觉前,DOS攻击不能发生作用。
对带宽和服务器资源的要求降低
这个解决方案,使我们可以通过扩展Web层,规避对数据层的巨大压力和扩展需求。同时,它也能大大节省带宽消耗。对于用户名和密码而言,这看起来不是大问题,但在其他一些情况下,需处理的数据量可能大很多。当然,在此方案中处理用户信息所需的时间也会大大缩短,因为我们不需要在Web服务器(位于DMZ)、应用服务器和数据库服务器间来回奔走。
在这个 解决方案中,我们应该谨记的部分是消息发布/订阅。nServiceBus提供的在消息发布/订阅基础上设计系统的API十分简单。消息发布,是实现系统扩展性的核心部分。随着用户的增长,你只需要增添Web服务器,而不是数据库服务器。在整个解决方案,每请求所消耗的平均要小很多 ,因为所有的工作都是在接收请求的服务器本地完成。
锦上添花:ETags
为方便高级用户,我们还将此解决方案封装成了ETags。Web服务器停止运行时,缓存会被清空,我们能做的就是将缓存内容记录到磁盘上去(可用后台线程),并用服务器随UsernameInUseMessage消息一起传给我们的某种数据作为标记。这样,Web服务器重新启动后,它请求GetAllUsernamesMessage时可同时发出 ETag,应用服务器就只需要发送有变动的数据。使用“If-Modified-Since” 头HTTP GET的REST(译者注:可参看 深入浅出 RESThttp://www.javaeye.com/post/260746),也能很好解决这个问题。所有这些措施,都可以依靠Web服务器上磁盘空间的较小消耗,大大降低对网络带宽的需求。
结束语
即便你只有一台机子,同时充当Web和数据库服务器,在这个解决方案基础上构建的系统的运行效率也会很高。如果服务器更多, 性能自然会更好。不仅如此,此方案还极具可扩展性——即使你得到了8百万 Facebook用户,也不会因为遭受重大冲击而必须修改整个系统架构。
更多信息
http://www.nServiceBus.com
nServiceBus是一个用于构建企业级 .NET系统的开源通讯框架。它在消息发布/订阅支持、工作流集成和高度可扩展性等方面表现优异,因此是很多分布式系统基础平台的理想选择。
Podcast on Autonomous Servers and Publish/Subscribe
我们在这里主要研究服务自治、消息发布/订阅、异常、数据复制、系统复用和监管等领域的问题。