之前的一个自动化操作项目,因为涉及到资金的操作,操作又需要特别的频繁,所以对响应速度有很高的要求,使用MYSQL最快也需要一百多毫秒,所以感觉还是太慢了,最后使用Redis做数据库,因为没有筛选功能,只能自己写个程序来实现这个功能,LUA脚本为Redis所支持的语言,最终使HTTP响应速度在30-50毫秒之间,比较满意了,代码仅供参考交流
按时间查询指定数据
params = {
'page': page, "limit": limit, "is_on": is_on, "day": day, "end_bid_day": end_bid_day, "user": user
}
query_data = function(now_day, end_bid_day, key)
--[[
受限的Lua库:Redis Lua环境仅包含下列Lua库:table、string、math、debug、cjson和cmsgpack
布尔值返回:true:1,false:None
]]--
local v_include = function(list, value)
--判断table中是否包含某个值
if not list then
return false
end
for k, v in pairs(list) do
if v == value then
return true
end
end
return false
end
local function merge_data(data, is_on_list)
-- 使用合并数组的方式,使开启的数据排前面
for k, v in pairs(is_on_list) do
table.insert(data, 1, v);
end
return data
end
local function isEmpty(data)
-- 判断字符,以及表是否为空
local t = type(data);
if t == "number" or t == "string" then
return data == nil or data == ''
elseif t == "table" then
if next(data) == nil then
return true
else
return false
end
end
return data
end
local table_diff = function(tbl_a, tbl_b)
-- 数组差集计算
if type(tbl_a) ~= 'table' or next(tbl_a) == nil then
return nil
end
if type(tbl_b) ~= 'table' or next(tbl_b) == nil then
return tbl_a
end
local tbl_c = {}
for _, v in pairs(tbl_a) do
for _, vv in pairs(tbl_b) do
if v == vv then
--存在于tbl_b, 赋值成nil,不写入新tbl_c
v = nil
break
end
end
if v ~= nil then
table.insert(tbl_c, v)
end
end
table.sort(tbl_c)
return tbl_c
end
local table_len = function(tab)
-- 计算键值对的长度 计算数组的长度 可以直接使用:table.getn(tab) #tab
local l = 0
for _, _ in pairs(tab)
do
l = l + 1
end
return l
end
local split = function(str, reps)
local resultStrList = {}
string.gsub(str, '[^' .. reps .. ']+', function(w)
table.insert(resultStrList, w)
end)
return resultStrList
end
local query_data = function(now_day, end_bid_day, key)
--[[
参数前增加 ( 符号来使用可选的开区间 (小于或大于)
end_bid_day:有参,仅显示当天的数据
now_day:显包含当天及之后天数的数据
]]--
local data = {}
if end_bid_day ~= nil then
data = redis.call('ZRANGEBYSCORE', key, end_bid_day, end_bid_day)
else
data = redis.call('ZRANGEBYSCORE', key, now_day, '+inf')
end
return data
end
if KEYS[1] == "set_bid_over" then
-- 数据写入,需要先判断键名是否存,然后再进行写入,返回布尔值
local i = 0
for k, v in pairs(cjson.decode(ARGV[2])) do
local uid = ARGV[1]
local end_time = string.gsub(split(v["jssj"], " ")[1], "-", "")
local number_id = v['id']
local key = table.concat({ "data", uid, number_id }, ":")
local is_exist = redis.call('EXISTS', key)
if is_exist == 1 then
-- 将数据处理一下,得标的状态:1001,失标:-1001
if v['zt'] == "失标" then
redis.call('HSET', key, 'status_code', "-1000")
end
if v['zt'] == "得标" then
redis.call('HSET', key, 'status_code', "1000")
end
-- 得标或是失标,需要将剩余时间写0,方便后面正常排序
redis.call('HSET', key, 'surplus_time', 0)
redis.call('HSET', key, 'bid_over', cjson.encode(v))
i = i + 1
end
end
return i
end
if KEYS[1] == "query_domain" then
-- 域名数据查询,需要支持多域名,支持分页功能,因为返回不了完整理的结果,可以只返回一个结果的键,然后脚本,再按键获取结果
-- 传入参数部分处理
local params = cjson.decode(KEYS[2])
local user = params['user']
local page = tonumber(params['page'])
local limit = tonumber(params['limit'])
local start = page * limit - limit + 1
local is_on = tonumber(params['is_on'])
local now_day = tonumber(params['day'])
-- 先日期排序,方便超过当天日期的就直接过滤,对值的格式,内容进行一个规范
local end_bid_day = params['end_bid_day']
local second
if end_bid_day ~= nil and string.len(end_bid_day) >= 8 then
-- 判断是否带了秒的信息,防止 end_bid_day 空白字符导致的非 nil 状态
local arr_time = split(end_bid_day, '|')
if table.getn(arr_time) == 2 then
second = tonumber(arr_time[2])
end
end_bid_day = string.gsub(arr_time[1], "-", "")
end
end_bid_day = tonumber(end_bid_day)
local domain_list = cjson.decode(ARGV[1])
local offList = {}
local onList = {}
local i = 0
local total
-- ----------------------------------------------- 内部函数功能 Start ---------------------------------------
local get_hash_data = function(v)
-- 将转入的hash键名数据,数组格式转成字典格式数据
local record = redis.call('HGETALL', v)
--将记录处理成字典格式
local res_record = {}
for rk, rv in pairs(record) do
if rk % 2 == 1 then
res_record[rv] = record[rk + 1]
end
end
return res_record
end
local page_limit = function(start, page, limit, data)
-- 数组分页设计 带搜索规则的可能需要遍历到最后
local page_record = {}
for i = start, page * limit do
-- 无记录就不用遍历了
if data[i] == nil then
break
end
table.insert(page_record, data[i])
end
return page_record
end
local table_sort = function(tab, order)
-- 排序,按日期排序,日期包含的信息在'end_time'中,键名已经是按日期排序的,只是没有精确到时,分
table.sort(tab, function(a, b)
if order == "-" then
return tonumber(a.surplus_time) > tonumber(b.surplus_time)
else
return tonumber(a.surplus_time) < tonumber(b.surplus_time)
end
end)
return tab
end
local data_split = function(data_list, reps)
-- 在数组中返回过滤符合条件的数据
local data = {}
for k, v in pairs(data_list) do
table.insert(data, split(v, reps)[1]);
end
return data
end
local str_to_json = function(data)
-- JSON 格式转换 page_record 数组内容直接被改写了
for rk, rv in pairs(data) do
if rv["detail"] ~= nil then
rv["detail"] = cjson.decode(rv["detail"])
end
if rv["offer_record"] ~= nil then
rv["offer_record"] = cjson.decode(rv["offer_record"])
end
if rv["bid_over"] ~= nil then
rv["bid_over"] = cjson.decode(rv["bid_over"])
end
end
return data
end
local wr_data = function(res_record, tab)
-- 统一写入数据的函数,is_on:true:使用数组,是为了与服务程序取值方式保持一致性,未使用:res_record['is_on'] == '1'
if v_include(tab, res_record['id']) then
table.insert(onList, res_record);
else
table.insert(offList, res_record);
end
end
local is_time_area = function(second, data)
-- 针对秒范围内的数据处理,还需要考虑剩余秒数在负数的,允许在结标之前的5分钟数据显示
if second == nil then
return true
end
local surplus_time = tonumber(data["surplus_time"])
local now_second = tonumber(redis.call('TIME')[1])
local get_time = tonumber(data["get_time"])
local calculate_sc = (get_time + surplus_time) - now_second
if (second >= surplus_time or second >= calculate_sc) and calculate_sc >= -300 then
return true
end
return false
end
local filter_user = function(value, data_list)
-- 在数组中返回过滤符合条件的数据
local user_list = {}
for k, v in pairs(data_list) do
local key = split(v, ':')
if string.find(key[2], value) ~= nil then
table.insert(user_list, v);
end
end
return user_list
end
-- ----------------------------------------------- 内部函数功能 End ---------------------------------------
-- 代码区,起始 普通数据 默认数据或是未开启的
local all_data = query_data(now_day, end_bid_day, 'data:total')
if next(all_data) == nil then
return cjson.encode({ data = {} })
end
all_data = data_split(all_data, "#")
-- 代码区 开始了的数据 未开启的也需要使用开启的做排除,无数据:data_is_on = []
local data_is_on = query_data(now_day, end_bid_day, 'is_on:true')
data_is_on = data_split(data_is_on, "#")
-- 对数据求差集,得到不包含开启竞价的数据 适合于is_on == 2
local data_is_off = (data_is_on ~= nil and { table_diff(all_data, data_is_on) } or { all_data })[1]
local data
if is_on == 0 then
data = merge_data(data_is_off, data_is_on)
end
if is_on == 1 then
data = data_is_on
end
if is_on == 2 then
data = data_is_off
end
-- 对帐号ID进行筛选
if isEmpty(user) == false then
data = filter_user(user, data)
end
--if user == '' then
-- return data
--end
if next(domain_list) == nil then
-- 无域名查询条件 避免不需要的遍历,只取需要的部分,总数量后期处理
for k, v in pairs(data) do
i = i + 1
local res_record = get_hash_data(v)
local is_time_area = is_time_area(second, res_record)
if is_time_area then
wr_data(res_record, data_is_on)
end
end
end
if next(domain_list) ~= nil then
-- 有域名查询条件
for k, v in pairs(data) do
local res_record = get_hash_data(v)
for k2, v2 in pairs(domain_list) do
i = i + 1
--进行判断域名值比对 find 第二个参数是匹配模式,并非简单的字符串
local find_str = string.gsub(v2, "-", "%%-");
if string.find(res_record['domain'], find_str) ~= nil then
local is_time_area = is_time_area(second, res_record)
if is_time_area then
wr_data(res_record, data_is_on)
break
end
end
end
end
end
local page_record = {}
total = table_len(offList) + table_len(onList)
--按结束日期排序
local sort_onList = table_sort(onList, "-")
local sort_offList = table_sort(offList, "+")
-- 合并开启,与未开启数据
page_record = merge_data(sort_offList, sort_onList)
-- 分页处理
page_record = page_limit(start, page, limit, page_record)
-- 将字符转JSON格式
page_record = str_to_json(page_record)
return cjson.encode({ page = page, total = total, limit = limit, start = start, data = page_record, i = i });
end