搞了好久的,终于为 SDB 增加了 lua 脚本

2022-10-28 01:45:14 +08:00
 Aidenboss

SDB 背后的思考 ———— lua 脚本支持


lua 脚本是酷炫的,Redis 、Nginx 等开源项目都可以内嵌 lua 实现业务逻辑。所以 SDB 也打算支撑 lua 脚本。还好找到了优秀的开源项目:gopher-lua

初始化

L := lua.NewState()
defer L.Close()

方法注册

将 SDB 的方法注册到 lua 脚本中,以 LCount 为例子:

L.SetGlobal("LCount", L.NewFunction(func(L *lua.LState) int {
	userKey := L.CheckString(1)
	luaLogger.Printf("[LCount] userKey: %s", userKey)

	res, err := luaService.list.Count(batch, []byte(userKey))
	if err != nil {
		L.RaiseError("%s", err)
	}
	L.Push(lua.LNumber(res))
	return 1
}))

如何保证事务操作?

lua 脚本是灵活的,在上面我们可以写很多的业务逻辑,如果我们在 lua 中用了:LLPush 、LRPush 如何能保证多个方法是操作是事务的呢?也比较简单,我们只需要创建一个 batch 对象,执行完我们的 lua 脚本后进行 commit 。保证执行每一次脚本是事务的。

加锁逻辑?

这其实是最容易被忽略的一点。对比 Redis 来说,由于是单线程的,所以 lua 脚本是不需要考虑锁的。

但是 SDB 不同,SDB 首先是支持多线程的,那么对 userKey 的写操作会进行加锁。lua 脚本在某种程度上破坏了对单个 userKey 的加锁策略。

首先 lua 脚本得让用户传入会操作的 userKey 列表,然后对每个 userKey 进行加锁。只有获取了所有锁,lua 脚本才能开始运行。

但这是不够的,可能会出现死锁问题。如当一个 lua 脚本操作 a 、b 两个 userKey ,另一个 lua 脚本操作 b 、d 两个 userKey 。假设 d 和 a 的锁是同一把。就会出现死锁的问题。

防止死锁的解决方法也很简单,只需要保证先对 lua 脚本的 userKey 按某种顺序依次获取锁既可。

1437 次点击
所在节点    Go 编程语言
4 条回复
ClarkAbe
2022-10-28 09:00:38 +08:00
这个库的 State 本身就不支持真正的并发,哪怕 Clone State 也是一样....在多个时间片段同时读写就会触发恐慌.....需要自己 fork 把里面一堆 map 全部改了才行......
Aidenboss
2022-10-28 09:17:51 +08:00
@ClarkAbe 如果每个 lua 请求都是新的 State 呢
lesismal
2022-11-04 16:52:05 +08:00
gopher-lua 的性能比较差,而且不管是 c 版还是 go 版的 lua 都不支持并发,多个 lua state 对性能提升意义也不大。另外,如果 lua 写复杂了 gopher-lua 的语法解析还可能出错、运行不正确。

> 如果每个 lua 请求都是新的 State 呢

@Aidenboss 这个成本就更高了,这会让你的程序慢太多了,只适合请求量低的系统。
Aidenboss
2022-11-05 01:55:56 +08:00
@lesismal 嗯嗯,那看起来是:gopher-lua 不是并发安全的,但多个 state 是没问题的。至于性能,没有考虑的,如果要考虑限制下 lua 脚本的并发数量就好了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/890547

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX