🔧 build: 调整库目录
parent
1370b072f7
commit
0702cee580
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
有用的 独立 基础 lua 库
|
||||
@ -0,0 +1,118 @@
|
||||
-- tbl_weight = {
|
||||
-- [xx] = { id = xx, weight = xx },
|
||||
-- ...
|
||||
-- }
|
||||
|
||||
-- https://github.com/kinbei/lua-misc/blob/master/weighted_random.lua
|
||||
|
||||
return function(tbl_weight, random_func, id, weight)
|
||||
random_func = random_func or math.random
|
||||
id = id or "id"
|
||||
weight = weight or "weight"
|
||||
|
||||
local t = {}
|
||||
local total_index = #tbl_weight
|
||||
local total_weight = 0
|
||||
|
||||
for _, v in pairs(tbl_weight) do
|
||||
assert(type(v[weight]) == "number")
|
||||
|
||||
total_weight = total_weight + v[weight]
|
||||
table.insert(t, {
|
||||
[id] = v[id],
|
||||
[weight] = v[weight],
|
||||
})
|
||||
end
|
||||
|
||||
return function()
|
||||
local rand_index
|
||||
|
||||
while true do
|
||||
rand_index = random_func(1, total_index)
|
||||
if random_func(1, total_weight) < t[rand_index].weight then
|
||||
return t[rand_index][id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------------------------------------
|
||||
-- math.randomseed( os.time() )
|
||||
|
||||
-- local function test_case_1()
|
||||
-- print("test_case_1")
|
||||
|
||||
-- local t = {}
|
||||
-- table.insert(t, { id = 1, weight = 400 } )
|
||||
-- table.insert(t, { id = 2, weight = 30 } )
|
||||
-- table.insert(t, { id = 3, weight = 100 } )
|
||||
-- table.insert(t, { id = 4, weight = 8 } )
|
||||
-- table.insert(t, { id = 5, weight = 30 } )
|
||||
-- table.insert(t, { id = 6, weight = 500 } )
|
||||
-- table.insert(t, { id = 7, weight = 20 } )
|
||||
-- table.insert(t, { id = 8, weight = 200 } )
|
||||
-- table.insert(t, { id = 9, weight = 40 } )
|
||||
-- table.insert(t, { id = 10, weight = 70 } )
|
||||
-- table.insert(t, { id = 11, weight = 300 } )
|
||||
-- table.insert(t, { id = 12, weight = 500 } )
|
||||
-- table.insert(t, { id = 13, weight = 350 } )
|
||||
-- table.insert(t, { id = 14, weight = 20 } )
|
||||
-- table.insert(t, { id = 15, weight = 480 } )
|
||||
-- table.insert(t, { id = 16, weight = 250 } )
|
||||
-- table.insert(t, { id = 17, weight = 500 } )
|
||||
-- table.insert(t, { id = 18, weight = 50 } )
|
||||
-- table.insert(t, { id = 19, weight = 300 } )
|
||||
-- table.insert(t, { id = 20, weight = 500 } )
|
||||
|
||||
-- local f = weighted_random(t)
|
||||
|
||||
-- local result = {}
|
||||
-- for i = 1, 4648 do
|
||||
-- local id = f()
|
||||
-- result[id] = result[id] or 0
|
||||
-- result[id] = result[id] + 1
|
||||
-- end
|
||||
|
||||
-- local r = {}
|
||||
-- for k, v in pairs(result) do
|
||||
-- table.insert(r, { id = k, times = v })
|
||||
-- end
|
||||
|
||||
-- table.sort(r, function(a, b) return a.times > b.times end)
|
||||
-- for _, v in ipairs(r) do
|
||||
-- print(v.id, v.times)
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- local function test_case_2()
|
||||
-- print("test_case_2")
|
||||
|
||||
-- local t = {}
|
||||
-- t[1] = { weight = 2000, id = 1 }
|
||||
-- t[2] = { weight = 2000, id = 2 }
|
||||
-- t[3] = { weight = 30, id = 3 }
|
||||
-- t[4] = { weight = 40, id = 4 }
|
||||
-- t[5] = { weight = 50, id = 5 }
|
||||
|
||||
-- local f = weighted_random(t)
|
||||
|
||||
-- local result = {}
|
||||
-- for i = 1, 4120 do
|
||||
-- local id = f()
|
||||
-- result[id] = result[id] or 0
|
||||
-- result[id] = result[id] + 1
|
||||
-- end
|
||||
|
||||
-- local r = {}
|
||||
-- for k, v in pairs(result) do
|
||||
-- table.insert(r, { id = k, times = v })
|
||||
-- end
|
||||
|
||||
-- table.sort(r, function(a, b) return a.times > b.times end)
|
||||
-- for _, v in ipairs(r) do
|
||||
-- print(v.id, v.times)
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- test_case_1()
|
||||
-- test_case_2()
|
||||
@ -1,6 +0,0 @@
|
||||
local skynet = require "skynet"
|
||||
|
||||
return function(dbconf)
|
||||
local addr = skynet.uniqueservice("dbmgr")
|
||||
return skynet.call(addr, "lua", "init", dbconf)
|
||||
end
|
||||
@ -1,76 +0,0 @@
|
||||
local skynet = require "skynet"
|
||||
local log_define = require "zlog.log_define"
|
||||
|
||||
local getinfo = debug.getinfo
|
||||
local LOG_LEVEL = log_define.LOG_LEVEL
|
||||
local DEFAULT_CATEGORY = log_define.DEFAULT_CATEGORY
|
||||
local log_format = log_define.format
|
||||
|
||||
local category_addr = {}
|
||||
|
||||
local function get_service(category)
|
||||
local addr = category_addr[category]
|
||||
if addr then
|
||||
return addr
|
||||
end
|
||||
|
||||
local root_addr = skynet.localname(".logger")
|
||||
if not root_addr then
|
||||
-- no logger config
|
||||
root_addr = skynet.uniqueservice("logger")
|
||||
end
|
||||
|
||||
addr = skynet.call(root_addr, "lua", "get_service", category)
|
||||
category_addr[category] = addr
|
||||
return addr
|
||||
end
|
||||
|
||||
local function sendlog(category, level, ...)
|
||||
local di = getinfo(3, "Sl")
|
||||
local msg = log_format(skynet.self(), level, di, ...)
|
||||
skynet.call(get_service(category), "lua", "log", level, msg)
|
||||
end
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.d(...)
|
||||
sendlog(DEFAULT_CATEGORY, LOG_LEVEL.DEBUG, ...)
|
||||
end
|
||||
|
||||
function M.d2(category, ...)
|
||||
sendlog(category, LOG_LEVEL.DEBUG, ...)
|
||||
end
|
||||
|
||||
function M.i(...)
|
||||
sendlog(DEFAULT_CATEGORY, LOG_LEVEL.INFO, ...)
|
||||
end
|
||||
|
||||
function M.i2(category, ...)
|
||||
sendlog(category, LOG_LEVEL.INFO, ...)
|
||||
end
|
||||
|
||||
function M.w(...)
|
||||
sendlog(DEFAULT_CATEGORY, LOG_LEVEL.WARN, ...)
|
||||
end
|
||||
|
||||
function M.w2(category, ...)
|
||||
sendlog(category, LOG_LEVEL.WARN, ...)
|
||||
end
|
||||
|
||||
function M.e(...)
|
||||
sendlog(DEFAULT_CATEGORY, LOG_LEVEL.ERROR, ...)
|
||||
end
|
||||
|
||||
function M.e2(category, ...)
|
||||
sendlog(category, LOG_LEVEL.ERROR, ...)
|
||||
end
|
||||
|
||||
function M.f(...)
|
||||
sendlog(DEFAULT_CATEGORY, LOG_LEVEL.FATAL, ...)
|
||||
end
|
||||
|
||||
function M.f2(category, ...)
|
||||
sendlog(category, LOG_LEVEL.FATAL, ...)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -1,77 +0,0 @@
|
||||
local string_format = string.format
|
||||
local tconcat = table.concat
|
||||
local os_date = os.date
|
||||
local string_sub = string.sub
|
||||
local os_clock = os.clock
|
||||
|
||||
local ESC = string.char(27, 91)
|
||||
local RESET = ESC .. '0m'
|
||||
|
||||
local M = {}
|
||||
|
||||
M.LOG_LEVEL = {
|
||||
DEBUG = 1,
|
||||
INFO = 2,
|
||||
WARN = 3,
|
||||
ERROR = 4,
|
||||
FATAL = 5,
|
||||
}
|
||||
|
||||
M.LOG_LEVEL_NAME = {
|
||||
[1] = "DEBUG",
|
||||
[2] = "INFO ",
|
||||
[3] = "WARN ",
|
||||
[4] = "ERROR",
|
||||
[5] = "FATAL",
|
||||
}
|
||||
|
||||
M.LOG_COLOR = {
|
||||
[1] = ESC .. '34m',
|
||||
[2] = ESC .. '32m',
|
||||
[3] = ESC .. '33m',
|
||||
[4] = ESC .. '31m',
|
||||
[5] = ESC .. '35m',
|
||||
}
|
||||
|
||||
M.DEFAULT_CATEGORY = "root"
|
||||
|
||||
function M.log_dir(log_root, date)
|
||||
return string_format("%s/%04d-%02d-%02d", log_root or ".", date.year, date.month, date.day)
|
||||
end
|
||||
|
||||
function M.log_path(dir, prefix, category, date)
|
||||
return string_format("%s/%s%s_%04d-%02d-%02d.log", dir or ".", prefix or "", category or M.DEFAULT_CATEGORY,
|
||||
date.year, date.month, date.day)
|
||||
end
|
||||
|
||||
function M.format(addr, level, di, ...)
|
||||
local param = {...}
|
||||
local date = os_date("*t")
|
||||
local ms = string_sub(os_clock(), 3, 6)
|
||||
|
||||
local time = string_format("%02d:%02d:%02d.%02d", date.hour, date.min, date.sec, ms)
|
||||
|
||||
local fileline = ""
|
||||
if di then
|
||||
fileline = (" [%s:%d]"):format(di.short_src, di.currentline)
|
||||
end
|
||||
|
||||
for k, v in pairs(param) do
|
||||
param[k] = tostring(v)
|
||||
end
|
||||
|
||||
local msg =
|
||||
string_format("[:%08x][%s][%s] %s%s", addr, M.LOG_LEVEL_NAME[level], time, tconcat(param, " "), fileline)
|
||||
|
||||
return msg
|
||||
end
|
||||
|
||||
function M.color(level, msg)
|
||||
local c = M.LOG_COLOR[level]
|
||||
if not c then
|
||||
return msg
|
||||
end
|
||||
return c .. msg .. RESET
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,75 @@
|
||||
local skynet = require "skynet"
|
||||
require "skynet.manager"
|
||||
|
||||
local dbconf
|
||||
|
||||
local CMD = {}
|
||||
|
||||
function CMD.init(cconf)
|
||||
assert(not dbconf, "dbmgr has been initialized.")
|
||||
dbconf = cconf -- init is allowed only once
|
||||
|
||||
local redisconf = dbconf.redis
|
||||
for dbkey, conf in pairs(redisconf) do
|
||||
for index = 1, conf.service_num do
|
||||
local addr = skynet.newservice("redisd", dbkey, index)
|
||||
local ok = skynet.call(addr, "lua", "init", conf)
|
||||
if not ok then
|
||||
assert(false, ("redisd init failed. [dbkey] %s [id] %d"):format(dbkey, index))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local mysqlconf = dbconf.mysql
|
||||
for dbkey, conf in pairs(mysqlconf) do
|
||||
for index = 1, conf.service_num do
|
||||
local addr = skynet.newservice("mysqld", dbkey, index)
|
||||
local ok = skynet.call(addr, "lua", "init", conf)
|
||||
if not ok then
|
||||
assert(false, ("mysqld init failed. [dbkey] %s [id] %d"):format(dbkey, index))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function CMD.mysql_service_num(dbkey)
|
||||
if not dbconf then
|
||||
return
|
||||
end
|
||||
local mysqlconf = dbconf.mysql
|
||||
if not mysqlconf then
|
||||
return
|
||||
end
|
||||
local conf = mysqlconf[dbkey]
|
||||
if not conf then
|
||||
return
|
||||
end
|
||||
return conf.service_num
|
||||
end
|
||||
|
||||
function CMD.redis_service_num(dbkey)
|
||||
if not dbconf then
|
||||
return
|
||||
end
|
||||
local redisconf = dbconf.redis
|
||||
if not redisconf then
|
||||
return
|
||||
end
|
||||
local conf = redisconf[dbkey]
|
||||
if not conf then
|
||||
return
|
||||
end
|
||||
return conf.service_num
|
||||
end
|
||||
|
||||
skynet.start(function()
|
||||
skynet.dispatch("lua", function(_, _, cmd, ...)
|
||||
local f = CMD[cmd]
|
||||
assert(f, cmd)
|
||||
skynet.retpack(f(...))
|
||||
end)
|
||||
end)
|
||||
|
||||
skynet.register(".dbmgr")
|
||||
@ -1,34 +0,0 @@
|
||||
local skynet = require "skynet"
|
||||
local st = require "skynet.sharetable"
|
||||
local mc = require "skynet.multicast"
|
||||
|
||||
local channel
|
||||
|
||||
local CMD = {}
|
||||
|
||||
function CMD.query(filename)
|
||||
assert(channel)
|
||||
return st.query(filename)
|
||||
end
|
||||
|
||||
function CMD.loadfiles(filenames)
|
||||
assert(type(filenames) == "table")
|
||||
for _, filename in pairs(filenames) do
|
||||
st.loadfile(filename)
|
||||
end
|
||||
channel:publish(filenames)
|
||||
end
|
||||
|
||||
function CMD.channel()
|
||||
return channel.channel
|
||||
end
|
||||
|
||||
skynet.start(function()
|
||||
channel = mc.new()
|
||||
skynet.dispatch("lua", function(_, _, cmd, ...)
|
||||
local f = CMD[cmd]
|
||||
assert(f, cmd)
|
||||
skynet.retpack(f(...))
|
||||
end)
|
||||
end)
|
||||
|
||||
@ -1,212 +0,0 @@
|
||||
local skynet = require "skynet"
|
||||
local log_define = require "zlog.log_define"
|
||||
local queue = require "skynet.queue"
|
||||
require "skynet.manager"
|
||||
|
||||
local LOG_LEVEL = log_define.LOG_LEVEL
|
||||
local DEFAULT_CATEGORY = log_define.DEFAULT_CATEGORY
|
||||
local log_format = log_define.format
|
||||
local color = log_define.color
|
||||
local string_match = string.match
|
||||
|
||||
local skynet_env = require("skynet.env")
|
||||
local nodeid = skynet_env.get("nodeid")
|
||||
|
||||
local zenv = require("zenv.init")
|
||||
local define = zenv.Logger
|
||||
|
||||
local log_root = define.log_root
|
||||
local log_level = define.log_level or LOG_LEVEL.INFO
|
||||
local log_console = define.log_console
|
||||
|
||||
local name = zenv.get_node_conf(nodeid).name
|
||||
local log_prefix = name .. "_"
|
||||
|
||||
local last_day = -1
|
||||
local category = ...
|
||||
local is_master = not category
|
||||
local category_addr = {}
|
||||
local lock = queue()
|
||||
local file
|
||||
|
||||
local function close_file()
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
file:close()
|
||||
file = nil
|
||||
end
|
||||
|
||||
local function open_file(date)
|
||||
date = date or os.date("*t")
|
||||
|
||||
local dir = log_define.log_dir(log_root, date)
|
||||
if not os.rename(dir, dir) then
|
||||
os.execute("mkdir -p " .. dir)
|
||||
end
|
||||
|
||||
if file then
|
||||
close_file()
|
||||
end
|
||||
|
||||
local path = log_define.log_path(dir, log_prefix, category, date)
|
||||
local f, e = io.open(path, "a")
|
||||
if not f then
|
||||
print("logger error:", tostring(e))
|
||||
return
|
||||
end
|
||||
|
||||
file = f
|
||||
last_day = date.day
|
||||
end
|
||||
|
||||
local CMD = {}
|
||||
|
||||
function CMD.console(level, msg)
|
||||
print(color(level, msg))
|
||||
end
|
||||
|
||||
function CMD.log(level, msg)
|
||||
if level < log_level then
|
||||
return
|
||||
end
|
||||
|
||||
msg = msg or ""
|
||||
local date = os.date("*t")
|
||||
if not file or date.day ~= last_day then
|
||||
open_file(date)
|
||||
end
|
||||
|
||||
file:write(msg .. '\n')
|
||||
file:flush()
|
||||
|
||||
if log_console then
|
||||
if is_master then
|
||||
CMD.console(level, msg)
|
||||
else
|
||||
skynet.call(".logger", "lua", "console", level, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CMD.set_console(is_open)
|
||||
log_console = is_open
|
||||
if is_master then
|
||||
for _, addr in pairs(category_addr) do
|
||||
skynet.call(addr, "lua", "set_console", is_open)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CMD.set_level(level)
|
||||
log_level = level
|
||||
if is_master then
|
||||
for _, addr in pairs(category_addr) do
|
||||
skynet.call(addr, "lua", "set_level", level)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CMD.get_service(cate)
|
||||
if not is_master then
|
||||
return
|
||||
end
|
||||
|
||||
local addr
|
||||
lock(function()
|
||||
addr = category_addr[cate]
|
||||
if not addr then
|
||||
addr = skynet.newservice("logger", cate)
|
||||
category_addr[cate] = addr
|
||||
end
|
||||
end)
|
||||
return addr
|
||||
end
|
||||
|
||||
if is_master then
|
||||
skynet.info_func(function()
|
||||
return {
|
||||
log_console = log_console,
|
||||
log_level = log_level,
|
||||
}
|
||||
end)
|
||||
|
||||
skynet.register_protocol {
|
||||
name = "text",
|
||||
id = skynet.PTYPE_TEXT,
|
||||
unpack = skynet.tostring,
|
||||
dispatch = function(_, addr, msg)
|
||||
local level = LOG_LEVEL.INFO
|
||||
if string_match(msg, "maybe in an endless loop") then
|
||||
level = LOG_LEVEL.WARN
|
||||
end
|
||||
if string_match(msg, "stack traceback:") then
|
||||
level = LOG_LEVEL.ERROR
|
||||
end
|
||||
msg = log_format(addr, level, nil, msg)
|
||||
CMD.log(level, msg)
|
||||
end,
|
||||
}
|
||||
|
||||
local SIGHUP_CMD = {}
|
||||
local function get_first_line(filename)
|
||||
local f = io.open(filename, "r")
|
||||
if not f then
|
||||
return
|
||||
end
|
||||
|
||||
local first_line = f:read("l")
|
||||
f:close()
|
||||
return first_line
|
||||
end
|
||||
|
||||
local function get_sighup_cmd(sighup_file)
|
||||
local cmd = get_first_line(sighup_file)
|
||||
if not cmd then
|
||||
return
|
||||
end
|
||||
return SIGHUP_CMD[cmd]
|
||||
end
|
||||
|
||||
local sighup_file = "./.sighup_file"
|
||||
|
||||
-- 捕捉sighup信号(kill -1)
|
||||
skynet.register_protocol {
|
||||
name = "SYSTEM",
|
||||
id = skynet.PTYPE_SYSTEM,
|
||||
unpack = function(...)
|
||||
return ...
|
||||
end,
|
||||
dispatch = function(_, addr)
|
||||
-- https://blog.hanxi.cc/p/75/
|
||||
local cmd = get_sighup_cmd(sighup_file)
|
||||
if cmd then
|
||||
cmd = cmd:match("^%s*(.-)%s*$")
|
||||
local func = SIGHUP_CMD[cmd]
|
||||
if func then
|
||||
func()
|
||||
else
|
||||
skynet.error(
|
||||
string.format("unknow sighup cmd, need set sighup file. sighup_file: '%s'", sighup_file))
|
||||
end
|
||||
else
|
||||
local level = LOG_LEVEL.FATAL
|
||||
local msg = log_format(addr, level, nil, "SIGHUP")
|
||||
CMD.log(level, msg)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
category_addr[DEFAULT_CATEGORY] = skynet.self()
|
||||
skynet.register(".logger")
|
||||
end
|
||||
|
||||
open_file()
|
||||
|
||||
skynet.start(function()
|
||||
skynet.dispatch("lua", function(_, _, cmd, ...)
|
||||
local f = CMD[cmd]
|
||||
assert(f, cmd)
|
||||
return skynet.retpack(f(...))
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,50 @@
|
||||
local skynet = require "skynet"
|
||||
local mysql = require "skynet.db.mysql"
|
||||
local util = require "store_util"
|
||||
require "skynet.manager"
|
||||
|
||||
local traceback = debug.traceback
|
||||
|
||||
local dbkey, index = ...
|
||||
local db
|
||||
|
||||
local CMD = {}
|
||||
|
||||
local function success(ret)
|
||||
if not ret or ret.err or ret.badresult then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function CMD.init(conf)
|
||||
db = mysql.connect(conf)
|
||||
db:query("set names utf8mb4")
|
||||
return true
|
||||
end
|
||||
|
||||
function CMD.exec_one(sql)
|
||||
local ok, ret = xpcall(db.query, traceback, db, sql)
|
||||
if not ok or not success(ret) then
|
||||
assert(false, ("sql=[%s] ret=[%s]"):format(sql, util.encode(ret)))
|
||||
return
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function CMD.exec(sqls)
|
||||
for i = 1, #sqls do
|
||||
local sql = sqls[i]
|
||||
CMD.exec_one(sql)
|
||||
end
|
||||
end
|
||||
|
||||
skynet.start(function()
|
||||
skynet.dispatch("lua", function(_, _, cmd, ...)
|
||||
local f = CMD[cmd]
|
||||
assert(f, cmd)
|
||||
skynet.retpack(f(...))
|
||||
end)
|
||||
end)
|
||||
|
||||
skynet.register(util.mysql_sname(dbkey, index))
|
||||
@ -0,0 +1,42 @@
|
||||
local skynet = require "skynet"
|
||||
local redis = require "skynet.db.redis"
|
||||
local util = require "store_util"
|
||||
require "skynet.manager"
|
||||
|
||||
local traceback = debug.traceback
|
||||
local tunpack = table.unpack
|
||||
local tconcat = table.concat
|
||||
local dbkey, index = ...
|
||||
local db
|
||||
|
||||
local CMD = {}
|
||||
|
||||
function CMD.init(conf)
|
||||
db = redis.connect(conf)
|
||||
return true
|
||||
end
|
||||
|
||||
function CMD.exec_one(cmd, ...)
|
||||
local ok, ret = xpcall(db[cmd], traceback, db, ...)
|
||||
if not ok then
|
||||
assert(false, ("cmd=[%s %s] ret=[%s]"):format(cmd, tconcat({...}, " "), ret))
|
||||
return
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function CMD.exec(cmds)
|
||||
for _, cmd in pairs(cmds) do
|
||||
xpcall(CMD.exec_one, traceback, tunpack(cmd))
|
||||
end
|
||||
end
|
||||
|
||||
skynet.start(function()
|
||||
skynet.dispatch("lua", function(_, _, cmd, ...)
|
||||
local f = CMD[cmd]
|
||||
assert(f, cmd)
|
||||
skynet.retpack(f(...))
|
||||
end)
|
||||
end)
|
||||
|
||||
skynet.register(util.redis_sname(dbkey, index))
|
||||
@ -1,198 +0,0 @@
|
||||
local skynet = require "skynet"
|
||||
local redis = require "skynet.db.redis"
|
||||
local crypt = require "skynet.crypt"
|
||||
|
||||
local math_floor = math.floor
|
||||
local math_ceil = math.ceil
|
||||
local math_random = math.random
|
||||
|
||||
local function hash(script)
|
||||
local key = crypt.sha1(script)
|
||||
return crypt.hexencode(key)
|
||||
end
|
||||
|
||||
local QUORUM
|
||||
|
||||
local SCRIPT = {
|
||||
LOCK = [[
|
||||
local key = KEYS[1]
|
||||
if redis.call("exists", key) == 1 then
|
||||
return 0
|
||||
end
|
||||
redis.call("set", key, ARGV[1], "PX", ARGV[2])
|
||||
return 1
|
||||
]],
|
||||
UNLOCK = [[
|
||||
local key = KEYS[1]
|
||||
if redis.call("get", key) == ARGV[1] then
|
||||
redis.pcall("del", key)
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
]],
|
||||
EXTEND = [[
|
||||
local key = KEYS[1]
|
||||
if redis.call("get", key) ~= ARGV[1] then
|
||||
return 0
|
||||
end
|
||||
redis.call("set", key, ARGV[1], "PX", ARGV[2])
|
||||
return 1
|
||||
]],
|
||||
}
|
||||
|
||||
local SCRIPT_HASH = {
|
||||
LOCK = hash(SCRIPT.LOCK),
|
||||
UNLOCK = hash(SCRIPT.UNLOCK),
|
||||
EXTEND = hash(SCRIPT.EXTEND),
|
||||
}
|
||||
|
||||
local conf
|
||||
local dbs = {}
|
||||
local sessions = {}
|
||||
|
||||
local function execute_script(db, type, s)
|
||||
local ok, ret = pcall(db["evalsha"], db, SCRIPT_HASH[type], 1, s.lockname, s.uuid, s.timeout)
|
||||
if not ok and ret:find("NOSCRIPT") then
|
||||
ok, ret = pcall(db["eval"], db, SCRIPT[type], 1, s.lockname, s.uuid, s.timeout)
|
||||
end
|
||||
if not ok then
|
||||
skynet.error("redis execute_script err.", ret, s.lockname, s.uuid, s.timeout)
|
||||
return false
|
||||
end
|
||||
if ret == 1 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function execute_script_timeout(db, type, s)
|
||||
local co = coroutine.running()
|
||||
local ok, ret = false, "timeout"
|
||||
|
||||
skynet.fork(function()
|
||||
ok, ret = execute_script(db, type, s)
|
||||
if co then
|
||||
skynet.wakeup(co)
|
||||
co = nil
|
||||
end
|
||||
end)
|
||||
|
||||
skynet.sleep(conf.request_timeout / 10)
|
||||
if co then
|
||||
co = nil
|
||||
end
|
||||
return ok, ret
|
||||
end
|
||||
|
||||
local function calc_time(s)
|
||||
local now = skynet.now() * 10
|
||||
local drift = math_floor(conf.drift_factor * s.timeout) + 2
|
||||
s.starttime = now
|
||||
s.endtime = now + s.timeout - drift
|
||||
end
|
||||
|
||||
local function make_session(lockname, uuid, timeout)
|
||||
local s = {
|
||||
lockname = lockname,
|
||||
uuid = uuid,
|
||||
timeout = timeout,
|
||||
attempts = 0,
|
||||
starttime = 0,
|
||||
endtime = 0,
|
||||
}
|
||||
calc_time(s)
|
||||
return s
|
||||
end
|
||||
|
||||
local function unlock(s)
|
||||
s.endtime = 0
|
||||
for _, db in pairs(dbs) do
|
||||
execute_script(db, "UNLOCK", s)
|
||||
end
|
||||
end
|
||||
|
||||
local function attempt(s, is_extend)
|
||||
s.attempts = s.attempts + 1
|
||||
local votes = 0
|
||||
for _, db in pairs(dbs) do
|
||||
local ok
|
||||
if is_extend then
|
||||
ok = execute_script_timeout(db, "EXTEND", s)
|
||||
else
|
||||
ok = execute_script_timeout(db, "LOCK", s)
|
||||
end
|
||||
if ok then
|
||||
votes = votes + 1
|
||||
end
|
||||
end
|
||||
|
||||
local now = skynet.now() * 10
|
||||
if votes >= QUORUM and s.endtime > now then
|
||||
local ti = s.timeout / 3 - (now - s.starttime)
|
||||
ti = math_floor(ti / 10)
|
||||
if ti < 0 then
|
||||
ti = 0
|
||||
end
|
||||
skynet.timeout(ti, function()
|
||||
if s.endtime == 0 then
|
||||
return
|
||||
end
|
||||
s.attempts = 0
|
||||
calc_time(s)
|
||||
attempt(s, true)
|
||||
end)
|
||||
return true
|
||||
else
|
||||
unlock(s)
|
||||
-- retry
|
||||
if conf.retry_count == -1 or s.attempts <= conf.retry_count then
|
||||
local t = conf.retry_delay + math_floor((math_random() * 2 - 1) * conf.retry_jitter)
|
||||
skynet.sleep(math_ceil(t / 10))
|
||||
calc_time(s)
|
||||
return attempt(s)
|
||||
end
|
||||
-- failed
|
||||
sessions[s.uuid] = nil
|
||||
return false, "timeout"
|
||||
end
|
||||
end
|
||||
|
||||
local CMD = {}
|
||||
|
||||
function CMD.lock(lockname, uuid, timeout)
|
||||
timeout = timeout or conf.timeout
|
||||
local s = sessions[uuid]
|
||||
if s then
|
||||
return false, "session exist"
|
||||
end
|
||||
s = make_session(lockname, uuid, timeout)
|
||||
sessions[uuid] = s
|
||||
|
||||
return attempt(s)
|
||||
end
|
||||
|
||||
function CMD.unlock(_, uuid)
|
||||
local s = sessions[uuid]
|
||||
if not s then
|
||||
return false, "session not exist."
|
||||
end
|
||||
sessions[uuid] = nil
|
||||
return unlock(s)
|
||||
end
|
||||
|
||||
skynet.init(function()
|
||||
conf = require "redlock_conf"
|
||||
for _, client in ipairs(conf.servers) do
|
||||
table.insert(dbs, redis.connect(client))
|
||||
end
|
||||
QUORUM = math_floor(#conf.servers / 2) + 1
|
||||
end)
|
||||
|
||||
skynet.start(function()
|
||||
skynet.dispatch("lua", function(_, _, cmd, ...)
|
||||
local f = CMD[cmd]
|
||||
assert(f, cmd)
|
||||
skynet.retpack(f(...))
|
||||
end)
|
||||
end)
|
||||
|
||||
Loading…
Reference in New Issue