Python Django Redis+Lua条件筛选达到极致响应速度

之前的一个自动化操作项目,因为涉及到资金的操作,操作又需要特别的频繁,所以对响应速度有很高的要求,使用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MetaEnchanter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值