6.4 维护
-
停机
Switcher这类服务可以用外部结束进程的方式关闭。
集成了Zdb服务的应用,比如Auany,GlobalId,Provider之类的服务,有两种关闭方法。
1. 结束Switcher后,等待一个以上的Zdb checkpoint周期,再结束进程,确保数据完全刷新到底层数据库。
2. 正确配置服务的JmxServer参数,然后使用jmxtool的Stop子命令,确保Zdb数据刷新到底层数据库,服务按正确顺序停止。例如,结束auany服务,可以执行命令:
java -jar limax.jar jmxtool stop -c "service:jmx:rmi://localhost:10202/jndi/rmi://localhost:10201/jmxrmi"
该命令允许一个额外的-d delay参数,delay为毫秒,指令服务器在delay时限达到之后停机。
-
备份与恢复
备份与恢复的方式依赖于使用的底层数据库引擎。
-
使用EDB引擎的备份
使用jmxtool的backup子命令实现备份功能,支持全备份,增量备份两种方式。
backup子命令参数:
1. –d <backupdirectory> 指定备份目录
2. –i <true or false> true表示执行增量备份,false表示不进行增量备份。
这里备份与sqlserver之类的备份方式稍有区别,sqlserver类数据库,要求增量备份之前必须存在一个全备份。所以要做增量备份必须按照dump database,(dump transaction)+ 的方式执行。limax上实现为只要执行了backup子命令作增量备份,首先执行全备份,logrotate的时候自动复制数据库日志到备份目录。这种方式可以简化备份规则设计,例如,要求以一天为周期作增量备份,只需要在每天特定时间点执行一次增量备份即可。
例如:增量备份auany的zdb数据库,执行如下命令即可
java -jar limax.jar jmxtool backup -c "service:jmx:rmi://localhost:10202/jndi/rmi://localhost:10201/jmxrmi" -d c:\temp\backup –i true
-
使用EDB引擎的恢复
如果恢复的是全备份,直接将备份目录拷贝成zdb目录即可。
如果在增量备份上恢复,则按下述步骤进行:
1. zdb目录重命名成zdb.old
2. 备份目录拷贝成zdb目录
3. 将zdb.old/log目录下的文件覆盖到zdb/log中。这是由于zdb.old/log目录下的日志文件中可能存在最新的还没有提交到备份目录的checkpoint信息。
如果要按选择的时间点恢复,上面的拷贝完成之后在zdb目录上用命令行方式执行EDB的交互工具edbtool:
java -jar limax.jar edbtool #help rescue <src dbpath> <dst dbpath> list checkpoint <dbpath> recover checkpoint <dbpath> <recordNumber> out <filename> <charset> #default System.out UTF-8 exit quit #list checkpoint c:\temp\backup 0 : 2015-04-18 16:15:36.210 #
关键命令有两条,list checkpoint命令,列出当前数据库所有checkpoint时间点,从0开始编号,在列表中选择期望的时间点编号执行recover checkpoint命令, 数据库数据量较大,时间点较多的情况下,命令可能执行较长时间,命令完成后,数据库恢复到选择的那个时间点的状态。
-
使用Mysql引擎的备份与恢复:
直接使用Mysql的备份与恢复策略。
-
-
数据格式转换
应用升级以后,可能需要转换Zdb中存储的数据格式,需要实现类似sql数据库的ALTER TABLE功能。这项工作的大部分任务应该交由应用的开发者完成, 提供相应的转换程序, 交由运营者执行生产环境上的转换。
Limax框架提供转换所需的相应的支持,这里以提供一个以前面的服务器开发章节example提供的zdb描述为例,介绍转换方法。
-
转换实例
1. zdb描述
2. 添加一条记录
import limax.util.Pair; import limax.util.Trace; import limax.zdb.DBC; import limax.zdb.Procedure; import limax.zdb.Zdb; import limax.zdb.tool.DataWalker; public final class ConvertTest { public static void main(String[] args) throws Exception { new java.io.File("zdb").mkdir(); Trace.set(Trace.ERROR); limax.xmlgen.Zdb meta = limax.xmlgen.Zdb.loadFromClass(); meta.setDbHome("zdb"); Zdb.getInstance().start(meta); Procedure.call(() -> { Pair<Long, xbean.MyXbean> pair = table.Mytable.insert(); pair.getValue().setVar0((short) 123); pair.getValue().getSlist().add("name123"); return true; }); Zdb.getInstance().stop(); DBC.start(); DBC dbc = DBC.open(meta); DataWalker.walk(dbc.openTable("mytable"), kv -> { System.out.println(kv.getKey() + ", " + kv.getValue()); return true; }); DBC.stop(); } }
运行这段代码,返回如下结果
4096, {var0:123, slist:["name123", ], }
3. 更新zdb的xml描述
<xbean name="MyXbean"> <variable name="var0" type="short" /> <variable name="slist" type="vector" value="string" /> </xbean> <table name="mytable" key="long" value="MyXbean" autoIncrement="true"/>
注意到,MyXbean.var0 类型从int改变为short
4. 重新生成代码,刷新eclipse,这时发现代码出现错误,将pair.getValue().setVar0(123); 改为 pair.getValue().setVar0((short)123);5. 再次运行
这时,运行报告错误
Exception in thread "main" limax.zdb.XError: convert needed: {mytable=MANUAL}运行结果指明了,当前版本应用与前一版本应用的Zdb数据库不兼容,mytable这张表需要手动转换才能兼容。
6. 运行转换工具生成转换代码
在应用的当前目录下创建zdbcov目录,执行命令
java -cp <path to limax.jar>;bin limax.zdb.tool.DBTool -e "convert zdb zdbcov"
注意,这里指明了2个classpath,一个是limax.jar,另一个是当前应用的bin目录。
这时,获得如下输出:
mytable MANUAL
-----COV.class not found, generate-----
make dir [cov]
make dir [cov\convert]
generating cov\convert\Mytable.java
generating cov\COV.java
这个输出,指明了mytable表需要手动转换,创建了一cov目录,里面放置了转换用框架代码。
刷新eclipse,配置项目属性,将cov目录设置为源码目录。
在Mytable.java里面寻找//TODO这一行,这里就是填写手工转换代码的地方。
// TODO var0 = s.var0;
假设我们将这行修改为:
var0 = (short) -s.var0;
7. 再次运行代码转换工具
java -cp <path to limax.jar>;bin limax.zdb.tool.DBTool -e "convert zdb zdbcov"
获得如下结果
2015-05-12 17:56:14.283 INFO <main> limax.zdb.DBC start ...
mytable MANUAL
-----COV.class found, manual convert start-----
copying... _sys_
converting... mytable
2015-05-12 17:56:14.390 INFO <main> limax.zdb.DBC stop begin
2015-05-12 17:56:14.397 INFO <main> limax.zdb.DBC stop end
-----manual convert end-----
在这里,能够看到创建了新目录zdbcov存放转换后的数据库,mytable表上执行了转换,不需要转换的_sys_表被直接拷贝。
验证转换效果
前一版本的zdb目录重命名为zdb.old,zdbcov目录重命名为zdb
重新执行ConvertTest.java,获得如下结果:
4096, {var0:-123, slist:["name123", ], }
8192, {var0:123, slist:["name123", ], }
注意到,key=4096行,var0变为了-123,这正是转换代码实现的功能,转换成功。
-
转换相关的细节
前面的例子mytable的转换类型报告为MANUAL,_sys_的转换类型报告为SAME,事实上系统中提供4种转换类型,定义在limax.zdb.tool.ConvertType中, 分别为SAME, AUTO, MAYBE_AUTO, MANUAL。
转换类型含义如下:
-
SAME
相同。这样的表在转换时直接拷贝。
-
AUTO
转换不会损失精度,例如整数从短到长的转换;一个bean,去掉了某些字段,并且该bean又没有作为任何map的key存在。这类转换可以自动进行,无需用户干预。
-
MAYBE_AUTO
可能损失精度的转换,例如整数转换为浮点;一个bean,添加了某些字段,需要初始化添加的字段。这类转换可以自动进行,用户愿意干预也可以进行干预。
-
MANUAL
其他一切情况,必须用户干预才能进行的转换。
如果新版应用启动失败,需要进行转换,首先应该使用命令:
java -cp <path to limax.jar>;bin limax.zdb.tool.DBTool -e "convert zdb zdbcov"
获取转换类型,如果存在MANUAL或者MAYBE_AUTO类型的转换,则该命令会生成框架代码,填写相应的TODO,编译完成后再次运行代码转换工具即可。
对于在上述的结果中只有MAYBE_AUTO,没有MANUAL的情况下,如果允许执行损失精度的转换,则可以删除先前生成的cov目录,直接使用命令:
java -cp <path to limax.jar>;bin limax.zdb.tool.DBTool -e "convert zdb zdbcov true"
例如,将上例中的var0类型改变为float,运行上述命令获得如下结果:
mytable MAYBE_AUTO
-----no need generate!, auto convert start-----
2015-05-12 23:40:52.532 INFO <main> limax.zdb.DBC start ...
mytable MAYBE_AUTO
copying... _sys_
auto converting... mytable
2015-05-12 23:40:52.623 INFO <main> limax.zdb.DBC stop begin
2015-05-12 23:40:52.630 INFO <main> limax.zdb.DBC stop end
-----auto convert end-----
这里可以看到,MAYBE_AUTO的表也被自动转换了。
事实上,convert命令的参数格式如下:
convert [fromDB [toDB [autoConvertWhenMaybeAuto [generateSolver]]]]
fromDB 指定了转换源,默认为zdb
toDB 指定了转换目标,默认为zdbcov
autoConvertWhenMaybeAuto 指示是否直接转换MAYBE_AUTO类型的表,默认false
generateSolver 指定了是否生成合并代码,默认false。数据库的合并后面介绍。
-
-
转换总结
1. 对于应用开发者而言:
完成新版本开发之后,应该在上一个版本的zdb数据库上运行应用,如果报告错误提示数据转换,则应该在当前项目目录创建zdbcov目录并运行:
java -cp <path to limax.jar>;bin limax.zdb.tool.DBTool -e "convert zdb zdbcov"
如果生成了转换代码,根据需要提供自己的记录转换实现,打包新版本应用,集成转换工具,测试通过后,提交给运营环境在生产系统中执行。
2. 对于运营环境而言:
获取新版本应用以后,如果获知需要转换,则执行转换
java -cp limax.jar;application.jar limax.zdb.tool.DBTool -e "convert zdb zdbcov"
执行完成以后,备份原始zdb目录,将zdbcov目录改名为zdb。
最后,启动新版应用。
3. convert的fromDB,toDB参数如果是MYSQL url,则需要在java –cp参数之后,追加mysql/Java连接器的jar包。
4. 转换目标zdbcov,必须手工创建,对于EDB引擎创建一个目录,对于MYSQL引擎创建相应数据库。如果使用了MYSQL数据库,转换之后,可以将应用配置文件中dbhome直接指向新的数据库,减少数据库拷贝。
-
注意事项
对于关系数据库而言,大型表格的ALTER TABLE,非常耗时,同理,转换大型zdb数据库,也非常耗时。实际使用中,如果涉及到大型zdb数据库的转换,建议首先使用备份数据进行转换时长测试,确认停机时间上限,如果停机时间不可接受,则只能特别设计应用,在运行过程中逐步转换。
-
-
数据库合并
存在这样的应用,开始阶段分立运营,一段时间之后可能出现合并数据库的需求。Limax通过一系列手段支持这样的应用。
-
合并的支持
1. 使用GlobalId服务,同一GlobalId域之内的应用,通过GloalId服务提供唯一id,相应的id作为表的key,这样的表可以安全合并,而不会发生key冲突。
2. Zdb的自增量key的配置,autoKeyInitValue,autoKeyStep,分立运营的同种应用,一开始配置相同的autoKeyStep,不同的autoKeyInitValue。这样,凡是使用自增量key的表可以安全合并,不会发生key冲突。
3. 数据库合并,事实上是一种特殊类型的格式转换,与前面提到的格式转换相比,普通的格式转换目标数据库为空,执行合并时,目标数据库存在。对于同名表,如果在源数据库与目标数据库中,发现了相同的key,则认为发生冲突,这种情况下,可以生成相应代码框架,提示用户解决冲突。
-
合并的操作
1. 备份目标数据库, zdb -> zdb.bak
2. 准备源数据库,假设为zdbsrc
3. 执行合并
java -cp limax.jar;application.jar limax.zdb.tool.DBTool -e "convert zdbsrc zdb"
4. 如果没有冲突,合并完成,如果报告冲突,执行下面步骤。
java -cp limax.jar;application.jar limax.zdb.tool.DBTool -e "convert zdbsrc zdb false true"
convert的最后一个参数true指明了需要生成冲突处理代码,将生成的cov目录提交给应用
5. 应用将cov目录作为源码目录,可以看到比起前面的数据格式转换,cov目录中多出一个solver包,里面存在所有数据库表的相关冲突解决代码。找到代码中的方法,
public OctetsStream solve(OctetsStream sourceValue, OctetsStream targetValue, OctetsStream key)
填写需要的TODO代码,重新打包应用。
6. 提交应用,重新执行第3步。
-
注意事项
1. 建议尽量使用GlobalId,和正确配置Zdb自增量key,避免出现需要解决冲突的情况。
2. 对于可能出现的冲突,设计上应该有预见。事实上,出现上一节提到的在运营阶段出现合并冲突,生成冲突解决代码,解决冲突的过程是不应该出现的。出现这一情况,应该理解为设计缺陷。
3. 如果应用运行在MYSQL引擎上,并且能够确认合并操作决不会发生冲突,则可以直接在MYSQL上用SQL命令合并_meta_,_sys_之外的所有表,对于这两张表,保留目标数据库的版本即可。
4. convert可以在EDB数据库和MYSQL数据库之间相互转换。
5. convert可以同时生成格式转换代码和合并时的冲突解决代码,也就是说,格式转换与合并可以同时进行。为了避免混乱,建议按照先转换,再合并的顺序,分步操作。
-