单一世界【5】系统重演

系统重演是服务系统一个很重要的特性,但基本上很难。除了某些对系统可靠性要求很高的系统,比如交易系统。系统重演就像魔法时光回溯那样,将所有的过程不失真的完全执行一遍。这是个理想状态,但最大可能性的重演具有很高的意义,我希望能在单一世界中,实践这个特性。

我们首先界定下,什么是系统重演。我们认为对于系统,任何时间,任何环境下,只要相同的输入,都会得到相同的结果,那么,这个过程就是系统重演。这个定义借用了视频的概念,就像一个视频一样,任何时候播放,都是一样的画面和声音。

我们知道,最容易重演的情况发生一个线性执行的序列里面,而且这个序列完成功能所需要的资源,都是自包含的。比如一个C函数:

int max(int a,int b)

{

return a > b ? a : b ;

}

只要指定A和B,那么任何时候执行这个函数的结果都是一样的。如果MAX函数引用了全局变量C,那么情况就会发生改变,比如:

int c = 0;

int max(int a,int b)

{

int tmp = a > b ? a : b ;

return c > tmp ? c : tmp ;

}

如果C发生了变化,那么相同的输入就会面临发生改变。虽然,在服务系统中的情况远比这个要复杂很多,但是归根到底,原理还是一样。

我们将一个超大规模的系统分解为多个子系统,每个子系统又可以分析成更小的单位。当粒度达到了函数这个层次,那么重演就容易多了。可实际上,这只是理论上。因为,当粒度越小,那么要记录各个单位之间的数据流几乎是不可能的,那么重演也就成为不可能的。因此,首先确定一个最小的可重演的粒度,至关重要。

我们要决定这个粒度的时候要满足2个条件:1、重演单元之间数据量传输最小;2、划分出来的单元的数量最少。第二个条件是优先考虑的要素,因为系统重演是个庞大的工程,单元太多,基本上很难完成。

显然,一个系统可以由多个子系统构成的,如果每个子系统都是可重演的,那么整个系统就是可重演的。当然,并不是每个子系统都需要重演,不过,我们在探讨这个论题时,依然要假设每个子系统都需要重演。从重演的定义可以看出,影响一个系统不可重演的因素,主要包括几个因素:

1、系统对时间有依赖,导致在不同时间重演时,发生不同的结果。

2、系统对环境有依赖,导致在不同环境重演时,发生不同的结果。

我们知道,系统对时间依赖导致的不可重演的情况比较好理解,我们可以时钟当作一种外部资源,系统对时间的依赖实际上就是对时钟这种外部资源的依赖,那么,我们通过模拟时钟,依然可以完成系统重演。

而对环境的依赖则比较复杂,一种是外部环境,另外一种是内部环境。外部环境引起系统无法重演的因素很多,比如磁盘错误,比如网络失败,或者其他种种,往往是不可预测以及不可枚举的。对于这些因素我们可以通过分析日志,以及设置相应的触发条件来尽可能重现这种环境。

而内部环境引起不可重演的因素相对较少,但也同样不可预测。比如随机数,比如锁,等无法准备判断结果的因素。

对于一个可重演的系统,从设计角度来看,我们只需要将那些不可重演的子系统割裂出来,作为外部模块就可以了。那么串行化请求,以及记录结果日志这2个手段,就能实现系统重演。对内部环境的锁引起不可重演的情况,第一方式,就是割裂子模块的之间的依赖程度,尽量的并行化,从而避免资源竞争。将资源统一化管理,就能有序的控制和重现资源输出记录。

如果是一个高性能的系统,那么串行化请求,显然是不可取的。这又给我们提出另外一个问题,如何应对海量的请求呢?如果是无状态的请求,本身并没有系统依赖,难度相对要低很多,关键是具有上下文依赖的请求,在时序上,对结果存在影响,是考察的关键。一个上下文环境主要可以区分为3种,一个是由请求本身引起的局部变量,这个没啥关系。一个是全局变量,不算配置,配置的全局变量是静态的,没什么影响。另外一种是其他请求引起的局部变量,这种情况是最复杂的。比如A玩家攻击B玩家,那么最终的伤害,要依赖于B玩家本身的状态。这种情况,必须割裂A对B状态的依赖,因此设计上需要一个仲裁机制的存在。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结