烦恼一般都是想太多了。

0%

我们都知道二进制日志是一个非常好的习惯和工具。其可以用来进行恢复数据到某一时间点,或重放整个数据的变更过程,但其实这里面还有不少学问。

简介

二进制日志包含的是描述数据库改变(比如表建立或表数据修改)的事件。在使用 基于行 的日志格式时也会包含可能会改变数据的事件(如 DELETE语句,但并无匹配行)。二进制日志也会记录每个语句更新数据所消耗的时间。二进制日志的设计,有两个重要的目标:

  • 复制,二进制日志记录了在主服务器上数据变更记录,用来发送给从服务器。主服务器会把二进制日志中的事件发送给从服务器,然后在从服务器上再次执行。
  • 一些数据恢复操作需要二进制日志。在一个备份恢复后,二进制日志中在此备份后的事件可以被重新执行。这能保证让数据库从备份时恢复到最新状态。

二进制日志并不会记录类似 select, show这些不会改变数据的语句。

开启二进制日志会使服务器性能轻微下降,但开启这个日志来设置从服务器和数据恢复的好处远远大过了性能的降低。

要保护好二进制日志,因为记录的语句中可能会含有密码。

二进制日志格式

二进制日志有几种格式。

  • 基于语句的日志格式。选项 --binlog-format=STATEMENT
  • 基于行的格式。主服务器将表示每个单独表行受影响的事件写入二进制日志。选项--binlog-format=ROW
  • 混合格式。当设置这个选项--bilong-format=MIXED,默认会使用 基于行的格式,但在某些情况下会自动切换为基于语句的格式。

在MySQL5.5中,默认的二进制日志格式是 STATEMET。

日志格式可以被存储引擎设置或限制。这用来避免在使用不同存储引擎的主从服务器在执行特定语句时的问题。

在基于语句的复制中,在复制非确定性的语句时可能会出现问题。在确定一个语句使用基于语句的复制是否安全时,MySQL会确定能不能保证这个语句可以用基于语句的格式进行复制。如果不能保证,则会抛出警告。为了避免这个问题,可以采用基于行的复制。

Lua中,没有什么其他的数据对象,只有表。但是其提供的元表和元方法,让我们的程序有了更多的可能。另外,我们必须明白一点的就是,cocos2dx框架中,开启了一个Lua State,我们所有的脚本,逻辑都是在这里面执行,然后这个里面会调用一些 cocos2dx 导出给 lua 使用的接口,最终还是通过 c 代码来完成工作的。

Read more »

可能以前用的项目就是Lua,所以比较喜欢Lua。服务端也用skynet框架,都用Lua,能统一的话是最好的了。完全是个人爱好。但是有必要看一下,我对客户端是最不熟的了,图形这一块。

启动

新建一个Lua项目后:

cocos new -l lua -p com.example.me -d game

进入目录:

cd game/MyLuaGame/frameworks/rutime-src/Classes

AppDelegate.cpp是我们需要关注的文件。他干了一系列的事情:

bool AppDelegate::applicationDidFinishLaunching()
{
// set default FPS
Director::getInstance()->setAnimationInterval(1.0 / 60.0f);

// register lua module
// 注册 lua 模块
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);
lua_State* L = engine->getLuaStack()->getLuaState();
lua_module_register(L);

register_all_packages();

LuaStack* stack = engine->getLuaStack();
stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));

//register custom function
//LuaStack* stack = engine->getLuaStack();
//register_custom_function(stack->getLuaState());

// 添加的这两个路径,我们其实都不用手动在项目内添加了
#if CC_64BITS
FileUtils::getInstance()->addSearchPath("src/64bit");
#endif
FileUtils::getInstance()->addSearchPath("src");
FileUtils::getInstance()->addSearchPath("res");
if (engine->executeScriptFile("main.lua"))
{
return false;
}

return true;
}

这个类其实就干了两个事情:

  • 开个Lua State 把模块都注册进去
  • 引擎执行 main.lua

另外,其实以前那种写法:

cc.FileUtils:getInstance():addSearchPath("src/")
cc.FileUtils:getInstance():addSearchPath("res/")

这样的代码已经不需要了。程序已经自动注册了这两个路径了。程序会自动在这两个地方寻找资源。

模块及函数的注册到State

lua_module_register(L);

这玩意会在我们的Lua State内注册一系列的模块。我们取个例子来看一下,比如第一个模块。

// frameworks/cocos2d-x/cocos/scripting/lua-bindings/manual/lua_module_register.cpp

int lua_module_register(lua_State* L)
{
// Don't change the module register order unless you know what your are doing
register_cocosdenshion_module(L);
register_network_module(L);
register_cocosbuilder_module(L);
register_cocostudio_module(L);
register_ui_module(L);
register_extension_module(L);
register_spine_module(L);
register_cocos3d_module(L);
register_audioengine_module(L);
#if CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION
register_physics3d_module(L);
#endif
#if CC_USE_NAVMESH
register_navmesh_module(L);
#endif
return 1;
}

register_cocosdenshion_module

这个函数干的活比较简单,就是获取一下Lua的全局环境,然后把所有的模块内容注册进去。

//scripting/lua-bindings/manual/cocosdenshion/lua_cocos2dx_cocosdenshion_manual.cpp

int register_cocosdenshion_module(lua_State* L)
{
lua_getglobal(L, "_G");
if (lua_istable(L,-1))//stack:...,_G,
{
register_all_cocos2dx_cocosdenshion(L);
}
lua_pop(L, 1);
return 1;
}

真正干活的,还是下面一个函数。

register_all_cocos2dx_cocosdenshion

tolua 没有用过,但是一下他们的代码就知道了。很明显,是用了open, module, beginmodule, endmodule来干活的。

TOLUA_API int register_all_cocos2dx_cocosdenshion(lua_State* tolua_S)
{
tolua_open(tolua_S);

tolua_module(tolua_S,"cc",0);
tolua_beginmodule(tolua_S,"cc");

lua_register_cocos2dx_cocosdenshion_SimpleAudioEngine(tolua_S);

tolua_endmodule(tolua_S);
return 1;
}

小插曲

我们先来看一下,Lua关于注册表的概念。

Lua提供了应该注册表,这个表可以被任何C代码用来存储任何的Lua值。注册表总是位于伪索引 LUA_REISTRYINDEX上(并不在Lua State的真正的栈上)。任何C库都可以使用这个表来存储数据,但必须谨慎选择键,以避免出现冲突。典型滴,用一包含你库名的字符串作为键,或者是一个light userdata(C对象的指针),或者任何被自己代码创建的Lua对象。和变量名字一样,以一个下划线后跟大写字母的字符串是保留的。

整型的键用来做索引算法(luaL_ref)和一些预定义的值。因此,整型键不应该用作其他目录。

当建一个新的Lua State时,这个注册您就有一些预定义的值。这些预定义的值以lua.h定义的常量整数作为键,下面的两个是定义好的:

LUA_RIDX_MAINTHREAD 注册内保存的State的主线程(与State一起建立的那个线程)
LUA_RIDX_GLOBALS 注册表内的这个索引保存了全局环境。

我们可以假设这个注册表刚开始的时候是这样的:

t_reg = {  [LUA_RIDX_MAINTHREAD] = value,
[LUA_RIDX_GLOBALS] = _G,
}

lua_settable lua_rawset

luasettable(L, index)
luarawset(L, index)

这两个函数和代码 t[k] = v是一样的,t 就是位于 index 处的值,v 是栈的顶部的值, k 是在栈顶部下的值。

这两个函数会将 键 和 值都弹出。

不同的是,luarawset 并不会触发事件 __newindex 的元方法。

lua_gettable lua_rawget

lua_gettable(L, index)
lua_rawget(L, index)

t[k]的值压入栈,tindex 指定的值,k 是栈顶部的值。

这两个函数都会把键弹出,然后把结果值压入那个位置。lua_rawget不会触发 __index 事件的元方法。

返回值是是结果值的类型。

tolua

tolua_open

首先调用的是 tolua_open 函数:

// frameworks/cocos2d-x/external/lua/tolua/tolua_map.c

TOLUA_API void tolua_open (lua_State* L)
{
int top = lua_gettop(L);
// 检查 tolua 是否打开。用 t["tolua_opened"] = true | false 来判断
lua_pushstring(L,"tolua_opened");
lua_rawget(L,LUA_REGISTRYINDEX);
if (!lua_isboolean(L,-1))
{
// 如果没打开就打开它
// t_reg["tolua_opened"] = 1
lua_pushstring(L,"tolua_opened");
lua_pushboolean(L,1);
lua_rawset(L,LUA_REGISTRYINDEX);

// create value root table
// 建立根表
// t_reg["tolua_value_root"] = {}
lua_pushstring(L, TOLUA_VALUE_ROOT);
lua_newtable(L);
lua_rawset(L, LUA_REGISTRYINDEX);

#ifndef LUA_VERSION_NUM /* only prior to lua 5.1 */
/* create peer object table */
lua_pushstring(L, "tolua_peers");
lua_newtable(L);
/* make weak key metatable for peers indexed by userdata object */
lua_newtable(L);
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "k");
lua_rawset(L, -3); /* stack: string peers mt */
lua_setmetatable(L, -2); /* stack: string peers */
lua_rawset(L,LUA_REGISTRYINDEX);
#endif

/* create object ptr -> udata mapping table */
lua_pushstring(L,"tolua_ubox");
lua_newtable(L);
/* make weak value metatable for ubox table to allow userdata to be
garbage-collected */
lua_newtable(L);
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "v");
lua_rawset(L, -3); /* stack: string ubox mt */
lua_setmetatable(L, -2); /* stack: string ubox */
lua_rawset(L,LUA_REGISTRYINDEX);

// /* create object ptr -> class type mapping table */
// lua_pushstring(L, "tolua_ptr2type");
// lua_newtable(L);
// lua_rawset(L, LUA_REGISTRYINDEX);

lua_pushstring(L,"tolua_super");
lua_newtable(L);
lua_rawset(L,LUA_REGISTRYINDEX);
lua_pushstring(L,"tolua_gc");
lua_newtable(L);
lua_rawset(L,LUA_REGISTRYINDEX);

/* create gc_event closure */
lua_pushstring(L, "tolua_gc_event");
lua_pushstring(L, "tolua_gc");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushstring(L, "tolua_super");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushcclosure(L, class_gc_event, 2);
lua_rawset(L, LUA_REGISTRYINDEX);

tolua_newmetatable(L,"tolua_commonclass");

tolua_module(L,NULL,0);
tolua_beginmodule(L,NULL);
tolua_module(L,"tolua",0);
tolua_beginmodule(L,"tolua");
tolua_function(L,"type",tolua_bnd_type);
tolua_function(L,"takeownership",tolua_bnd_takeownership);
tolua_function(L,"releaseownership",tolua_bnd_releaseownership);
tolua_function(L,"cast",tolua_bnd_cast);
tolua_function(L,"isnull",tolua_bnd_isnulluserdata);
tolua_function(L,"inherit", tolua_bnd_inherit);
#ifdef LUA_VERSION_NUM /* lua 5.1 */
tolua_function(L, "setpeer", tolua_bnd_setpeer);
tolua_function(L, "getpeer", tolua_bnd_getpeer);
#endif
tolua_function(L,"getcfunction", tolua_bnd_getcfunction);
tolua_function(L,"iskindof", tolua_bnd_iskindof);

tolua_endmodule(L);
tolua_endmodule(L);
}
lua_settop(L,top);
}

这些都不用多说了,反正就是在 注册表内,添加两很多元素。

tolua_module

这个函数会创建一个模块。

// frameworks/cocos2d-x/external/lua/tolua/tolua_map.c


TOLUA_API void tolua_module (lua_State* L, const char* name, int hasvar)
{
if (name)
{
/* tolua module */
lua_pushstring(L,name);
lua_rawget(L,-2);
if (!lua_istable(L,-1)) /* check if module already exists */
{
lua_pop(L,1);
lua_newtable(L);
lua_pushstring(L,name);
lua_pushvalue(L,-2);
lua_rawset(L,-4); /* assing module into module */
}
}
else
{
/* global table */
lua_pushvalue(L,LUA_GLOBALSINDEX);
}
if (hasvar)
{
if (!tolua_ismodulemetatable(L)) /* check if it already has a module metatable */
{
/* create metatable to get/set C/C++ variable */
lua_newtable(L);
tolua_moduleevents(L);
if (lua_getmetatable(L,-2))
lua_setmetatable(L,-2); /* set old metatable as metatable of metatable */
lua_setmetatable(L,-2);
}
}
lua_pop(L,1); /* pop module */
}

这个函数么,就是在注册表内,建立一个新模块。havar 表示是不是这个模块有元表。

注册完成

在执行了函数 register_all_cocos2dx_cocosdenshion后,我们的注册表可能看起来是这样的:

t_reg = {
... -- 预定义的值
["cc"] = {
["getInstance"] = lua_cocos2dx_cocosdenshion_SimpleAudioEngi, -- 这就是导出来给我们在Lua中用的
.....
}
}

main.lua的执行

engine->executeScriptFile("main.lua")就开始执行 main.lua了。

之后,其实就跟普通的Lua脚本执行没有什么差别了,通过在State中调用C函数,来操作整个引擎了。

而对于,导出的函数,继续以 Lua 进行封装后以模块的方式调用,其实也没有什么特别的。

这就是 C -> Lua State -> lua script -> Cfunction -> Cobj 流程类似了。

不信你看一下,项目中 src 目录下的 cocos中。全是这样干的。

在我们所有的项目中,都有:

require "config"
require "cocos.init"

其实就是加载我们的配置文件,然后再加载 cocos Lua封装的初始化文件。打开 cocos/init.lua 就可以看到,其一一个个个 require 语句,用来加载封装成Lua的各个模块。

我们关注有一句:

-- src/cocos/init.lua
if CC_USE_FRAMEWORK then
require "cocos.framework.init"
end

其实这一段,是用了更高层的封装,更加方便使用,其实应该就是 Quick 干的事情。

如果我们在我们的 config.lua 中定义了这个变量,那么,就可以使用那些封装了。

尽管我们说Lua是一个解释型的语言,但Lua总是在运行代码前会编译成一种中间格式。(这并不重要,很多解释型也会这样做)编译阶段的存在对于解释型语言听起来有点不太对。然而,解释型语言的重要特性不是说他们不会被编译,而是说其轻易执行在空中生成的代码。我们可以说,一个dofile这样的函数存在给为了我们把Lua称为解释型语言的资格。

Read more »

通常,Lua并不设置什么规则,而是提供足够的方法给开发者来实现最适合他们自己的规则。然而,这些方法对于模块来说工作得并不好。模块系统的一个重要目的就是允许不同的团队共享代码。通用规则的缺乏阻碍了这个共享的实现。

Read more »

在大多数编程语言中,全局变量是非常讨厌的。一方面,使用全局变量会导致复杂的代码,让程序中不相关的不相关的部分看起来纠缠在了一起。另一方面,谨慎的使用全局变量可以很好的表达一个程序中的全局部分;但是,全局常量是没有大的问题的,但是像Lua一样的动态语言没有办法来分别一个变量是不是常量。

Read more »

Sproto是一个用C编写的高效序列化库,主要是想用来做Lua绑定。类似Google的 protocol buffers,但是速度更快。其设计得非常简单。只支持Lua支持的几种数据类型,其可以很容易的绑定到其他动态语言,或者直接在C中使用。

Read more »

对于大多数的编程场景来多,很多时候的任务都是在处理I/O,因为读写设备的不同,所以需要花很多的心思来整。从POSIX的标准来看,其提供了 select, poll来实现多个描述符的监控读写。而Linux自身还实现了一个更高效的 epoll。

Read more »

网络信息与普通消息的封装似乎有所不同,所以关注一下这个过程是非常的有必要的。我们先从网络服务的注册开始说起。skynet封装了一个socket库作为Lua模块来给我们使用。我们可以看一下对于一个socket的注册是怎么样的。

Read more »