ShareData

来源:互联网 发布:网络点播系统 编辑:程序博客网 时间:2024/06/09 16:19

当你把业务拆分到多个服务中去后,数据如何共享,可能是最易面临的问题,


最简单粗暴的方法是通过消息传递数据,如果A服务需要B服务中的数据,可以由B服务发送一个消息,将数据打包携带过去,如果是一份数据,很多地方都需要获得它,那么用一个服务装下这组数据,提供一组查询接口即可,DataCenter模块对此作了简单的封装.


如果你仅仅需要一组只读的结构分享给很多服务,(比如一些配置数据),你可以把数据写到一个lua文件中,让不同的服务加载它,Cluster的配置文件就是这样做的,注意:默认skynet使用自己的修改版lua,会缓存lua源文件,当一个lua文件通过loadfile加载后,磁盘上的修改不会影响下一次加载,所以你需要直接用io.open打开文件,再用load加载内存中的string.


另一个更好的方法是使用sharedata模块.


ShareData


sharedata正是为了大量共享结构化数据却不常更新它们,这种需求而设计出来的,sharedata只支持在同意节点内(同意进程下)共享数据,如果需要跨节点,需要自行同步处理.


local sharedata = require "sharedata"

可以引用这个模块.


sharedata.new(name,value)  在当前节点内创建一个共享数据对象.

value 可以是一张lua table ,但不可以有环,且key必须是字符串和正整数.

value还可以是一段lua文本代码,而sharedata模块将解析这段代码,把它封装到一个沙盒中运行,最终取得它返回的table,如果它不返回table,则采用它的沙盒全局环境.

如果value是一个以@开头的字符串,这个字符串会被解释为一个文件名,sharedata模块将加载改文件名制定的文件.

sharedata.update(name,value) 更新当前节点的共享数据对象.

sharedata.delete(name)  删除当前节点的共享数据对象.

sharedata.query(name) 获取当前节点的共享数据对象.


一旦query到一个共享数据对象,你可以像普通lua表那样读取其中的数据,但禁止改写,把其中的分支赋值给local变量是安全的,但如果你把最终的叶节点的值取出来后,就不可能被数据源的update操作更新了,所以,一般你需要持有至少一级表,每次用它来索引其下的数据.


注意:你不应该在同一服务中query相同的对象多次,(通常这样做也没有意义),那会造成在本地生成多份代理对象以及监控协程.如果有必要,你应该cache住query的对象反复使用,此外,query到一个对象后,除非该对象被delete,暂没有任何手段可以清除本地服务中的proxy对象.


一旦有人调用sharedata.update,所有持有这个对象的服务都会自动去数据源头更新数据,但由于这是一个并行的过程,更新并不保证立刻生效,但使用共享数据的读取方一定能在同一时间片(单此skynet消息处理过程)访问到同一版本的共享数据.


sharedata是基于共享内存工作的,且访问共享对象内的数据并不会阻塞当前的服务,所以可以保证不错的性能,并节省大量的内存.


sharedata的缺点是更新一次的成本非常大,所以不适合做服务间的数据交换,你可以考虑它的替代品:stm模块.


STM


STM(Software transactional memory)模块同样基于共享内存,所以也只能用于同一个skynet节点内,它是一个实验性的模块,不一定比消息传递的方式更好,只是提供一个新思路来进行同一节点内的服务间数据交换.


因为它不经过skynet的消息投递,信息传递的时效性比消息投递要好一些,但由于依旧需要在不同的lua vm间交换数据,序列化(使用skynet.pack和skynet.unpack)必不可少,因为绕过了消息投递,它还可以用于广播,多个读取者可以同时去读一个写入者更新的数据.


stm是以一个C编写的lua模块形式提供的.

local stm = require "stm"

可以引入这个模块,由于api多是操作C指针,所以调用其中的api上需要小心(否则会有内存泄露).


stm.new(pointer,size)可以生成一个共享对象,生成者可以改写这个对象,pointer/size是一个C指针以及长度.skynet.pack可以正确生成他们,它返回一个stmobj,是一个userdata,lua的gc会正确的回收它引用的内存.

stm.copy(stmobj) 可以从一个共享对象中生成一份读拷贝,它返回一个stmcopy,是一个lightuserdata.通常一定要把这个lightuserdata传到需要读取这份数据的服务.所以丢弃这个指针会导致内存泄漏.注:一个拷贝只能供一个读取者使用,你不可以把这个指针传递给多个服务.如果你有对个读取者,为每个人调用copy生成一份读拷贝 .

stm.newcopy(stmcopy) 把一个C指针转换为一份读拷贝,只有经过转换,stm才能正确的管理它的生命期.


持有stmcopy,则是这个共享对象的写入者,你可以用stmobj(pointer,size)的方式更新其中携带的信息,(这个userdata重载了call方法).


持有stmcopy,则是这个共享对象的读取者,stmcopy是用stm.copy生成的那个指针,传递给stm.newcopy构造出来的,你可以用stmcopy(function(pointer,size[,ud])...end[,ud])的方式读出其中的数据.如果数据没有更新,将返回false;否则,将更新过的数据指针pointer以及长度,传递给传入的反序列化函数,并返回true以及反序列化函数的结果.


test/teststm.lua是一个简单的使用范例.


ShareMap

sharemap 是对stm的简单应用,你可以用sharemap创建一个对象负责读写一张预定义的数据结构(使用sproto描述的结果).然后构造出读对象传递给其他服务.


当读写方修改了数据内容后,可以通过调用commit将修改后的副本同步给所有读取方,而读取方则需要主动调用update获得最新副本.

test/teststm.lua是一个简单的使用范例


注意: 如果需要同步的数据结构比较大,这种方式的成本也会增加,因为 每次commit都会全量序列化整个结构.

0 0
原创粉丝点击