Back to Top

为go程序扩展读取lua配置的能力

背景

我们的后台框架支持两种语言:Lua和Go(rust有小伙伴在做了,开发中…)

目前业务主要使用Lua框架,但部分新业务使用Go框架。

配置表通过转表工具转换为Lua文件,由Lua框架读取,并支持热加载,因此,Go框架存在读取配置的问题。

最初在转表工具(Excel->Lua)转换后增加了一个流程,将Lua转换为JSON,Go框架读取JSON文件。

不过有些配置是由开发手工维护的,如果同时维护Lua和JSON两份配置,可能会存在漏改的问题。

如果都使用JSON,需要让Lua框架支持JSON文件的热加载。

一开始只有一个由Lua手工维护的配置表需要被Go框架读取,我们使用了让Go框架加载Lua配置的方案,使用ChatGPT生成读取Lua文本的代码,然而随着Lua配置的变更,解析变得更加复杂。

因此,我们考虑为Go引入Lua扩展,但仅用于配置的读取。

有几个常见的纯Go的Lua库,如:

  • go-lua,支持Lua5.2,最后更新时间为2022年10月
  • gopher-lua,支持Lua5.1,最后更新时间为2023年5月

使用这些库存在以下问题:

  1. Lua版本与我们的Lua版本不一致(Lua框架是5.3.4),可能存在兼容性问题
  2. 这些库具有一定的学习成本,而C版本我们非常熟悉
  3. 如果Go框架支持了Lua配置,策划配置也可以不再转为JSON,所以要在后台长时间运行,存在稳定性风险

最终,我们决定使用cgo,为其做了一层简单的封装,仅用于读取配置和执行简单的函数。

由于使用了cgo,我们约定了以下规范:

  1. 尽量减少cgo和Go调用栈的切换。对于高频函数中的多次c调用,使用c代码进行封装,以避免多次cgo调用。
  2. cgo代码只跑在一个协程中,防止线程泄露。

代码可以在cgo-lua中找到,同时也支持了我们一直使用的Lua模块方案,这方面可以参考lua小工具–>Import的lua版本中的介绍。

示例

func TestDoStringCircularRef(t *testing.T) {
	vm, err := Open()
	require.Nil(t, err)
	defer vm.Close()

	ret, err := vm.DoString(`
		local tb = {
			a = 1, 
			b = 2.1, 
			[1] = "d",
			[2.2] = "e",
		}
		tb.c = tb
		return tb
	`)
	require.Nil(t, err)
	retTab := ret[0].(LuaTable) // LuaTable 是 map[interface{}]interface{} 类型
	require.Equal(t, int64(1), retTab["a"])
	require.Equal(t, 2.1, retTab["b"])
	require.Equal(t, "d", retTab[int64(1)])
	require.Equal(t, "e", retTab[2.2])
	require.Equal(t, retTab, retTab["c"].(LuaTable))
}

本文链接, 未经许可,禁止转载