5.6 高级特性
为了提升应用性能,View提供了一系列高级特性,扩展了View的概念,定义了字段采集的概念,实现了数据裁剪。实际应用环境下,通过裁剪数据可以大大节省网络带宽,防止网络拥塞。是否能够裁剪,取决于逻辑功能的设计,比如,使用了数据裁剪,时序问题自然就失去了讨论的意义,所以严格时序的应用,不应该进行裁剪。
-
基本概念
-
数据采集
View的发送数据时机到达时进行的字段信息收集。
-
过程采集
采集的结果是从上一次采集结束算起到当前时间点为止的所有更新历史,两次采集时间范围内,同一字段更新了多少次,就有多少个结果,如果两次update的数据没有变化,第二次被简化为一个Touch。
-
集合采集(Limax基本方式)
集合采集只应用于过程采集,集合采集将过程采集器聚合起来,内部过程采集器每一次收集的数据与过程采集器本身的作为一个二元组,被顺序记录下来,所以集合采集可以保证采集字段之间的更新顺序。
-
状态采集(Limax扩展方式)
采集的结果是最近的一次更新结果,之前的数据全部废弃掉,状态采集的字段独立于其它字段,不存在时序上的概念,也不生成Touch。逻辑功能许可的前提下,比起过程采集,可以过滤掉大量数据,节省了网络带宽。
-
Immutable
非易变属性,View描述中的Variable元素对应了具有非易变属性的字段,Bind引用的Xbean中定义的map,set类型之外的字段也具有非易变属性,这样的字段不能修改,只能被原子的替换。非易变的字段,只能使用非易变采集器采集数据。
-
Mutable
易变属性,View描述中的Bind元素对应了具有易变属性的字段,Bind引用中的Xbean中定义的map,set类型的字段也具有易变属性,这样的字段可以被修改,例如,map可以在上面直接增删,而map对象本身的引用并没有改变。易变的字段,多数情况下使用易变采集器采集数据,特殊情况下也可以使用非易变采集器数据,例如,map尽管可以直接增删内容,但是整体的替换也是允许的。易变属性的使用使得数据的局部更新成为可能,只发送改变的数据,有利于节省网络带宽。Immutable,Mutable由系统根据配置自动选择,无需用户干预。
-
Tick
字段数据采集的周期,对于集合采集,一个周期内的所有变化,连同顺序被采集出来;对于状态采集,最后一个状态被采集出来。
-
-
xml描述扩展
<view name="TestTempView" lifecycle="temporary" tick="20"> <variable name="var1" type="int" clip="true" /> <bind name="bindfirst" table="roles" clip="true" snapshot="true" /> <subscribe name="_var1" ref="gs.for_session.firstview.var1" snapshot=”true”/> </view>
View元素的tick属性,对于全局View无意义。View元素内,variable,bind两个元素上,提供了两个属性,clip和snapshot;临时View的subscribe字段只提供snapshot属性。
-
tick属性
配置SessionView和TemporaryView生成代码的tick,上面设置为20,默认为10ms,程序内能够通过setTick修改。
-
clip属性
定义了clip属性,gen目录下_<ViewName>.java中将生成一个protected boolean permitXXX(Type p)函数,默认返回true。
src目录的<ViewName>.java文件里,可以重载该函数,根据应用功能要求决定返回true或者false。
如果返回false,XXX字段的修改将被忽略掉。
比如,通过View提供一个可丢弃的数据源,可以在这个点上应用令牌桶之类的算法进行流量控制。
与其它的View方法不同的是,permitXXX方法不一定在View对应的线程中调用,所以使用该方法需要注意线程安全性,传入的参数可以为过滤提供参考,但是不可修改,不可存储,除非另外拷贝一份。
3种View的描述中都可以应用clip。
特别的,Bind元素如果定义了clip属性,生成的代码将选择Immutable采集方式,这是因为,工作在Mutable方式下,可能进行数据的部分更新,这样的更新如果被丢弃,将破坏服务器,客户端的数据一致性。例如,一个Xbean包含两个字段A,B,第一次更新A,第二次更新B,如果第一次更新被丢弃,第二次更新发送给客户端,这种情况下,服务器,客户端看到的A字段必然不一致。然而,只要用Immutable方式对待这个Xbean,每次都发送全部数据,这种情况下,即使第一次更新被丢弃,第二次更新也包含了之前的全部修改。
-
snapshot属性
声明该字段为一个状态采集字段,关联一个状态采集器进行管理。
对于全局View,snapshot的设定没有意义,全局View的数据发送时间点由用户自己决定,发送的都是最新一个版本的数据,所以全局View的字段都使用状态采集器。更进一步,Mutable采集决定的局部更新对于全局View的字段也没有意义,最终,全局View的所有字段都选择Immutable状态采集器。
SessionView的字段采集方式与对应的临时View的订阅字段的采集方式不相干, 例如, TV._a 订阅了SV.a, SV.a采用过程采集, TV._a 采用状态采集没有任何问题, 实现上同一份更新数据被提交给两个View各自的字段采集器上, 最终输出结果取决于采集器类型。
没有定义snapshot属性的字段集合被提交给一个集合采集器管理。
-
采集器选择
1. 对于SessionView,临时View:
Variable Bind Subscribe Subscribe/snapshot="true" Variable Bind Variable Bind PCS PCS PCS PCS ISC MSC napshot="true" ISC MSC PCS PCS ISC MSC clip="true" PCS PCS PCS PCS ISC ISC snapshot="true",clip="true" ISC ISC PCS PCS ISC ISC 第一列表示Variable/Bind上定义的属性,最后两个Subscribe列只对临时View有意义,用是否定义snapshot属性区分两种情况,临时View上Subscribe关联的采集器,每个成员一份。
PCS:集合采集器,每次添加的数据被排队。
ISC:Immutable状态采集器,每次添加数据,使用新值简单替换前一个值。
MSC:Mutable状采集器,每次添加数据,新值归并到前一个值中。
实际采集过程为:首先读取PCS,然后逐个读取ISC/MSC,对于临时View的订阅字段,为本tick内进行过订阅字段的更新的成员,逐个执行上述过程。
2. 全局View所有字段,全部使用ISC。
-
-
大数据集合广播
-
全局View扩展
提供一个保护方法protected void onUpdate(String varname);重载以后可以获知某个字段已经发生了变化,这个地方提供一个syncToClient的机会,将数据发送给用户。
不过,需要注意的是,异步触发方式发送出去的数据可能不是当前更新的数据,而是最新的数据。这是因为全局View上的操作,都统一调度到到全局View对象自身的线程上,例如,同一字段上连续更新同一字段两次,通过onUpdate触发发送,setA之后setB,setA触发的syncToClientA将放到setB之后,所以发送的数据为B,B触发的syncToClientB还是发送B。这个例子从另一个角度说明了全局View的字段属于状态采集字段。
-
松散临时View
如果临时View的定义中没有包含任何订阅,生成代码中可以看见createLooseInstance()方法,该方法可以创建松散临时View。松散临时View将标准临时View的Membership同步功能裁剪掉,在操作大规模Membership的情况下可以提高广播性能,减小网络开销。
MyTemporaryView tview = MyTemporaryView.createLooseInstance();
服务器端看,松散临时View与标准临时View在使用上没有差别。
从客户端看,Membership的变动被裁减掉。所以,onOpen被调用时传入空sessionid列表;onAttach,onDetach方法永远不会被调用。
如上面的例子,异步方式的全局View广播,需要仔细控制时序,否则可能导致不合预期的结果,如果是同步方式,不使用onUpdate,则不存在这样的问题,比如执行序列setA,syncToClientA,setB,syncToClientB,4个任务会正确排序到全局View线程上。异步方式,又必须保证按正确的序列发送,相应发送字段采用了过程采集方式的松散临时View是一个比较好的选择。
状态采集方式下,全局View的用户可控程度更高,Membership集合非常大的情况下, 可以将Membership分割成多个片段,每个片段之间插入一些延迟逐个发送,可以有效防止网络拥塞。
设计上必须注意,大数据集广播的频率一定不能太高,否则数据总量决定了网络拥塞还是难以避免。
-
分区临时View
生成代码中可以看见createInstance(int partition);方法,如果存在createLooseInstance(),createLooseInstance(int partition);也会同时存在。
用分区的方式对Membership进行划分,总是选择包含较少成员的分区接受新成员,所以分区成员数量相对均衡。每个分区维护一个私有的数据投递队列。
每次Tick,进行一次数据采集,往队列追加数据,刷新当前分区队列,最后将当前分区切换成下一个分区。
对于集合采集,采集的数据被追加到所有分区的队列中,所有的数据将会发送给所有用户,只不过时机不同。
对于状态采集,采集数据仅追加到当前分区队列,每次Tick获取的状态数据,只发送给当前分区的这一部分用户。
普通临时View是只包含1个分区的分区临时View的特例。
对于任何一个用户,等效延迟为tick * partition,如果设计决定单个用户最长接受TICK延迟,创建分区临时View之后,应该view.setTick(TICK/partition);
从原理可以推论,使用分区临时View,可以减少突发性的网络拥塞。
-
比较
1. 大数据集广播,需要通告成员信息的情况下,从网络负担的角度看,分区临时View总是好于普通临时View,不过为了进行分区,CPU,内存开销会更大。
2. 大数据集广播,不需要通告成员信息的情况下,可以按下表判断。
全局View 松散临时View 松散分区临时View 过程采集 差,根本不适合 中 好 状态采集 很好,可控性高 中 好,自动控制
-
-
总结
1. 使用snapshot/clip属性定义能够有效裁减数据传输,减少带宽占用
2. 过程采集需要保留所有历史数据,tick到达时发送,所以tick延迟越长,内存开销越大。
3. Mutable状态采集需要保留所有历史修改信息,在tick到达时归并发送,所以tick延迟越长内存开销越大,并且突发的cpu占用也越大。
4. Immutable状态采集是最节省内存和cpu的方式——直接丢弃之前的数据,也不存在归并需求。
5. 对于结构简单,数据量较小的的Xbean,有时候可以利用clip的副作用,将本来属于Mutable采集的Bind字段强制改变成Immutable采集,减少一些cpu和内存的开销。
6. 一般情况下有效tick(也就是tick*partition)决定了内存占用,有效tick越大,内存占用越多;有效tick内,分区越多,cpu占用越多——需要进行partition次处理。
7. 大多数情况下,只要确认了某个字段可以接受状态数据,那么修改xml,设置snapshot属性,适当调整tick,无需修改任何代码,即可有效提升性能。