您的位置:首页 > Web前端 > Node.js

enode框架step by step之框架要实现的目标的分析思路剖析2

2013-07-08 14:04 316 查看
enode框架系列step by step文章系列索引:
分享一个基于DDD以及事件驱动架构(EDA)的应用开发框架enode
enode框架step by step之事件驱动架构(EDA)思想的在框架中如何体现
enode框架step by step之saga的思想与实现
enode框架step by step之框架要实现的目标的分析思路剖析1
上一篇文章,介绍了enode框架的总体目标,以及如何实现高吞吐、低延迟、高可用、无单点问题的实现思路。本篇文章,我们再分析一下其他一些需要考虑的问题。我发现写文章挺累的,费时费脑经,但我会坚持下去。本文主要分析一下enode框架的物理部署以及command service的api的设计思路吧。

enode框架的物理部署思路:集群的web站点+分布式缓存和存储

集群的概念:多台机器做相同的业务,对外如一台机器在做事情一样,集群中任意一台机器挂了没有影响,因为其他机器还在工作;集群的机器要访问的数据的设计,我觉得一般有两种思路:
集群中每台机器都用自己的数据。数据一致性是通过每台机器之间的数据同步来实现,这样做的主要难题是数据同步的延迟带来的数据不一致的问题;但是好处是,因为没有任何共享数据,所以一台机器挂了完全对整个系统没有任何影响。因为这样的设计相当于是完全同时由很多独立的且没有共享任何数据的机器在同时工作,当然是最能容灾的了;
有一台机器存放数据的共享数据,集群中每台机器都访问这些共享数据。这种设计的好处是数据不用同步了,没有数据延迟带来数据不一致的问题;但是坏处是,有单点问题,万一共享数据的服务器挂了不是麻烦了。幸好,现在有很多开源的成熟的分布式缓存和分布式存储的产品,如memcached, redis这些都是分布式的缓存,可以有效的避免单点故障的问题,虽然挂了的单台memcached服务器会影响一部分数据的读取和写入,但是至少不会给整个系统带来挂掉的后果;同样分布式存储如mongodb,也能做到这样的效果。这两种产品,在分布式部署方面我还没有任何实际经验,平时工作中也不曾遇到过,所以今后还需要好好的学习和实践。
分布式的概念:一个业务在不同的物理点上做,比如web服务器(处理UI逻辑)、应用服务器(处理业务逻辑),这两个节点分开部署在不同的机器上,共同完成一个业务;分布式的特点是,每个节点都不能挂,否则这个业务就不能完成了;当然,我们可以给分布式中的每个节点都做集群处理,这样可以降低分布式系统的单节点故障; 但是因为分布式要完成一个业务,内部要夸网络通信如调用远程服务,所以性能肯定比没有调用远程服务的设计要低。一般我们不会采用分布式,用分布式都是被逼的,比如以下情况下,我们可能会采用分布式的设计:
一个系统,有几大块业务,相互比较独立,为了让每块业务都能独立设计和发展,我们会把这些不同的业务模块分开设计与实现;比如一个电子商务网站的交易中心和商品中心,可以独立分开设计;
数据量太大,没办法存放在一个点,所以只能分开存储;这种情况我们一般会把数据分区,不同分区的数据放在不同的点;如数据库的分库分表,还有一些分布式缓存如memcached、redis,还有如mongodb这样的支持分布式存储的文档型数据库;
一个系统,不同的层次使用完全不同的技术实现,比如由于历史原因,我们要对一个系统改造,但是这个系统的业务逻辑很复杂,而且都是用c++写的,我们不敢随便动;但是我们希望可以在UI上给这个系统重新设计以带来更好的用户体验,比如原来是用c++写的界面,现在希望通过WPF这种更高级更炫开发维护成本更炫的技术来实现。那么我们就会在同一个系统使用不同的语言和技术来实现。这种情况下,我们可能需要将c++实现的业务逻辑通过远程服务暴露出来,比如通过WCF暴露,WCF远程服务本身可以由c#编写,然后c#调用managed c++,然后managed c++调用unmanaged c++。从而实现业务逻辑的远程服务暴露;而在UI层,我们可以使用c#+WPF的方式来实现,然后UI层调用WCF远程服务。这种架构就是因为一个系统中不同层次因为使用了完全不同的技术而需要使用分布式的情况。

Command Service的API设计思路

接下来,我们看一下如何来设计一个command service。controller会调用command service,所以command service会被高并发的调用。那么command service该提供什么样的API呢?首先command service的职责是什么?是处理controller发送过来的command,那如何处理呢?大方向一般就两种,即同步执行command和异步处理command;同步的时候,用户希望command完全处理完才返回,中间如果遇到任何错误,就报异常,然后controller会捕获该异常,然后做后续处理;一般用户希望马上知道command有没有执行成功时,会用同步的方法来执行command。异步处理command,用户只需要把command发送给command service,然后不用等待command处理完成。但是用户可能希望知道command什么处理完成了,所以需要提供一个回调函数,通过回调函数来通知用户某个command处理完成了,一般异步编程的风格都会有回调函数的设计。基于上面的分析,enode的ICommandService接口的设计如下:
/// <summary>Represents a service to send or execute command.
    /// </summary>
    public interface ICommandService
    {
        /// <summary>Send a command asynchronously.
        /// </summary>
        void Send(ICommand command, Action<CommandAsyncResult> callback = null);
        /// <summary>Execute a command synchronously.
        /// </summary>
        void Execute(ICommand command);
    }
代码应该很清晰了,就不解释了。如果我们追求最快的用户可用性,那可以选择异步执行command,即调用send方法;如果每次都希望command执行完才返回,则可以使用同步执行command,即execute方法;目前框架中同步执行command的实现原理其实是在异步的基础上加了一个同步控制,
优点:
框架内部对command的处理流程可以完全一致了,不必因为需要同步和异步的处理而设计重复的代码,当然,我们可以做一些抽象,以减少重复的代码,但实际上这比较困难;
内部都是异步处理可以很方便的实现command的重试机制;
利用ManualResetEvent实现异步同步化,我们可以很方便的实现command的处理超时控制;
因为内部所有command的处理都是异步的,也就是所有的command都在固定的一些队列中排队等候处理,而队列的消费者,即处理command的线程我们在框架启动时就做了配置,所以访问domain in memory的并发线程数量可控,这样我们就可以一定程度上降低并发冲突的可能性;
缺点:
用户调用ICommandService.Execute方法法执行某个command时,他的意图是希望马上执行某个command并返回结果;但是我们现在内部并不是马上处理该command,而是先排队,然后用ManualResetEvent卡住当前线程,然后当command处理完成后,才允许当前线程继续往下走。当然,如果command迟迟未被执行(默认是10秒),则会自动超时,然后返回给command发起者;这样做的坏处是当并发很高的时候,同步执行command可能会超时;对于这一点,我在前一篇博文中已经对如何提高系统的“高吞吐量、低延迟、高可用”做了比较详细的思路分析,还没看过的朋友可以去看一下。
就写这些吧,下一篇继续分析。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: