• redis lua


      需求是在缓存最近一周内用户所有消息列表,考虑用Redis 存储;为每个存储一个独立Sorted Set,value 为消息体,Score 为MessageId,用以实现增量消息同步。

      问题就来了:Sorted Set 怎么清理?

        -设计内存容量只允许放一周内最新的,太久了缓存意义不大,太浪费。

        -再者存在百万级/s群发请求,不允许写入时触发清理。

      理想模型:如果使用磁盘则使用MyIsam堆表,数据按照顺序写入,再建立以uid为索引,删除却是完全顺序的。内存里面的话Hash 表 + RB 树两个维度索引,RB树可按照时间顺序清理。

      解决方案:

        - 写入第一条时,设置一周过期时间

          判断是否第一条:zadd key 0 0 value score 返回2 说明第一条,1 不是第一条,只是多一条0的数据

        - 用户每天第一次登陆,触发一次清理

          清理需要遍历Sorted Set上,消息一般不小,浪费io流量了,所以考虑采用lua 脚本实现。

        - 这样保证,通过pipeline只是高并发写入,同时保证活跃用户一周内消息都在内存(不活跃不保证),清理简单

      

    清理脚本如下:

    local ltime = 0
    local dels = 0
    local lefts = 0
    local list = redis.call("ZRANGE", KEYS[1], 1, -1)
    if(list[1] == nil) then
        return {-1, 0}
    end
    for _,v in ipairs(list) do
        if lefts == 0 then
            ltime = struct.unpack('<i', v)
            if ltime < tonumber(KEYS[3]) then
                dels = dels + 1
            else
                lefts = lefts + 1
            end
        else
            lefts = lefts + 1
        end
    end
    if lefts > tonumber(KEYS[2]) then
        dels = dels + (lefts - tonumber(KEYS[2]))
        lefts = tonumber(KEYS[2])
    end
    if lefts == 0 then
        ltime = 0
        redis.call("DEL", KEYS[1])
    elseif dels > 0 then
        redis.call("ZREMRANGEBYRANK", KEYS[1], 1, dels)
    end
    return {dels, ltime}

      写入的消息前4byte 为little-endian 的UnixTime,Redis lua 支持struct,很简单解析出(当然也支持cjson,但速度要差一些).

      清理过期数据,并返回最后一条写入的时间,应用根据返回时间适当延长过期时间。

      这里因为考虑每个人消息一般不会太多,所以全部遍历,多的话可考虑分部分遍历,如10条10条来,最新的就不会被不必要的取出来了,怎么说遍历大Set还是较慢的。

       

    clear_msgs(Uid, MaxLen, ExpireSec) ->
          ToExpires = utime() - ExpireSec,
          {ok, [Dels, LTime]} =
          eredis:q(pooler(Uid), [<<"EVAL">>, clear_script(), <<"3">>,
                                  ?KEY_LIST(Uid), MaxLen, ToExpires]),
          {ok, binary_to_integer(Dels), binary_to_integer(LTime)}.

      性能:此段脚本在我机器上速度2.5w/s(列表长度10),  相比get 7w/s。速度很快,也节省网络流量。

      

        script load

      此段脚本有700多字节,每次执行会带来不少网络流量;但对性能影响较小,内部对于eval 会先sha1 脚本,从缓存获取生成好的lua 方法执行。

      当然最好使用script load,节省脚本传输、脚本的sha1计算,就行存储过程一样执行。

      luajit:

      github讨论过 ,redis lua,相比nginx_lua 更像数据库存储过程,提供事务性的多个相关性操作,是否使用jit区别不大;

          支持的库也很有限base、table、string、math、debug、cjson、struct、cmsgpack,能够做的事情不多,也尽量别把太多逻辑用lua写。

      redis.log 方法:

      调试大段的lua脚本,这个方法还是挺管用的。

      相关参考:

      官方说明:http://oldblog.antirez.com/post/scripting-branch-released.html

      源码分析:http://blog.nosqlfan.com/html/4099.html

      

  • 相关阅读:
    根据实体中一个属性值查找实体数组中的所有实体并放到list中
    asp.net ajax 客户端框架未能加载 sys 未定义
    SYS_CONNECT_BY_PATH函数用法 ORACLE
    滚动条小结 平时容易忘记的小东西 JAVASCRIPT
    ORACLE 和 SQL 分别实现递归的方法
    JS 获取控件的绝对位置
    GridView内控件获取所在行的信息
    sql server 使用for xml path 将1列多行转换为字符串连接起来
    ORACLE 行转列 及函数定义
    子窗口刷新父窗口 javascript 并调用父窗口函数
  • 原文地址:https://www.cnblogs.com/lulu/p/4050176.html
Copyright © 2020-2023  润新知