在前面两篇的基础框架上引入可供skynet服务间数据共享的库,灵感是之前用Erlang写游戏业务时候,经常会用到ets来存储共享数据。ets表 是 Erlang 提供的一种高效的数据存储机制,可以看作是一个键值存储,其中每个键可以唯一标识一个值,支持快速的访问和修改操作。
skynet本身也有提供了sharetable
,sharedata
等库可以操作共享数据,但是相比以往用Erlang的ets使用上会复杂一丢丢,当做学习和扩展,就封装一个简单的ets库,提供查询/插入/更新/删除/清表的几种操作,在不同服务间可共享和查询/更新,线程安全。
使用场景:
比如需要保存当前服所有在线玩家信息,每个玩家信息包含玩家id,玩家服务进程addr,登录时间三部分。那就需要一张表名online_users
的表来保存信息列表,而保存的信息列表中,key
就是玩家id,val
则是{id = 玩家id, addr = 玩家服务进程, login_time = 登录时间}
。
代码示例:
A服务初始化和插入 (初始化只需要任意一个服务初始化一次即可)
local ets = require "db.ets"
--初始化表
ets.init("online_users")
--插入数据
local res = ets.insert("online_users", 玩家id(1001), {id = 玩家id, addr = 玩家服务进程1, login_time = 登录时间})
assert(res, "insert fail")
--更新数据
local res = ets.insert("online_users", 玩家id(1001), {id = 玩家id, addr = 玩家服务进程2, login_time = 登录时间})
assert(res, "update fail")
--查询数据
local res = ets.lookup("online_users", 玩家id(1001))
assert(res, "is nil")
print(res.id, res.addr, res.login_time)
--查询表所有数据
local res = ets.lookup_all("online_users")
for k, v in pairs(res) do
print(v.id, v.addr, v.login_time)
end
--删除数据
local res = ets.delete("online_users", 玩家id(1001))
assert(res, "delete fail")
--清表
local res = ets.delete_all("online_users")
assert(res, "delete_all fail")
B服务直接查询和修改
local ets = require "db.ets"
--查询A数据插入的数据
local res = ets.lookup("online_users", 玩家id(1001))
assert(res, "is nil")
print(res.id, res.addr, res.login_time)
支持val的多层table嵌套
ets.insert("xx", 100304, {id = 100304, score_list = {math = 100, englist = 98,
chemistry = 60}, other = {1, 2, 3}})
实现原理:
用 哈希表 和 链表 搭配使用,源码在工程目录的lualib-src的lua-etscache.c
ets表的键值数据用哈希表HashTable
保存, 而当值为table的情况下则用链表形式保存HashNode->next_node
- 每个表用单独的哈希表保存;
typedef struct HashTable{
char* name; //表名
int capacity; //表容量,默认4096
int size; //当前容量
HashNode** table; //哈希表
pthread_mutex_t lock; //锁
struct HashTable * next; //hash冲突的链表
}HashTable;
- 每个数据用单独节点保存,节点中包含数据,以及指向下一个节点的指针;
typedef struct{
char* key; //key
ValueType val_type; //val数据类型
Value val; //val数据
struct HashNode * next; //hash冲突的链表
struct HashNode * next_node; //val数据为table时, val.table_value使用链表
}HashNode;
- 节点的
key
目前只支持string和integer(integer插入时会转换成string,用来换算hash,查询时候再转换回integer返回);而val
通过union支持不同类型,目前只支持integer,string和table三种数据类型,其中table用链表保存。
// 标识不同数据类型
typedef enum {
TYPE_TABLE,
TYPE_INT,
TYPE_STRING
} ValueType;
// 存储不同类型的数据
typedef union {
struct HashNode *table_value; //存放table
long int_value;
char* string_value;
} Value;
性能:
浅浅在lua端调用压测了下,100w的插入耗时0.9s,100w的查询耗时0.3s.
额外:
简单练手的封装,一起学习用,可能运行有各种潜在问题或者报错,代码也写得粗暴和没在意细节,有问题的话请见谅或反馈